Sleep without lock Gtk.main() in a function - python

I have got a function in my gtk app. I want to wait 2 seconds in that function.
For example:
[...]
def do_something_and_wait(self):
#do something here
time.sleep(2)
# do something more
Gtk.main()
If I use time.sleep(2) directly, Gui freezes for 2 seconds.
How can I wait 2 seconds in a function without lock Gtk.main()?

You can use gtk.Main_iteration() to temporarily hand control back to the GUI, see:
http://www.pygtk.org/pygtk2reference/gtk-functions.html#function-gtk--main-iteration
and
http://book.huihoo.com/gtk+-gnome-application-development/sec-mainloop.html
This will not sleep for exactly 2s, but will pause for approximately 2s and still allow the GUI to respond to other events. As bazza points out, disabling the calling widget would also be a good idea.
def updateGUI():
'''Force update of GTK mainloop during a long-running process'''
while gtk.events_pending():
gtk.main_iteration(False)
def wait_2s():
for i in range(10):
time.sleep(0.2)
updateGUI()

You could add a gtk timeout. See
http://www.pygtk.org/pygtk2tutorial/ch-TimeoutsIOAndIdleFunctions.html
Your existing function would add the timeout instead of time.sleep(), and then return. A second function would 'do something more', and would be specified as the callback function for the timeout.
You'd probably want the first function to disable whichever GUI widget is used to set this part of your program running. This will stop the user pressing that button twice whilst you're waiting for the 2 seconds to elapse. The second function should re-enable it.

Related

A confirmation message that disappears pyqt5 [duplicate]

This question already has answers here:
Equivalent to time.sleep for a PyQt application
(5 answers)
Closed 1 year ago.
I would like to display a confirmation image like below, which disappears in 2s after being launched. I can display the image but I cannot make it disappear afterwards. I tried to use a sleep (2) but in this case the image turns all black for every 2 seconds. thank you for helping me please
Your application turns black, because the event loop is not running.
Qt runs by having an event loop run forever. It waits for events and processes them as they come in the queue. The event loop is getting an event, calling your function, and waiting for your function to finish until it moves on to running the next event. Your function shows the window/widget/dialog, then sits in time.sleep for 2 seconds. While it is sitting in time.sleep the Qt Event Loop is still waiting for your function to end to process more events.
There are 3 ways to handle this situation.
QTimer (recommended for your situation).
Show your widget and tell a timer to call your function in 2 seconds.
This will make your function exit right away, so the event loop can continue processing
After 2 seconds the timer will call the close function
Thread (threads are really for I/O like TCP Sockets).
QApplication.processEvents()
Show your dialog and run a loop waiting for 2 seconds.
While the loop is running and checking if 2 seconds has passed tell the event loop to process events.
QTimer - you simply show your window then have a timer call a function to close your window.
self.widg = ...
self.widg.show()
self.widg.raise_() # if already show bring to top
def close_and_delete_widg():
self.widg.close()
self.widg.setParent(None) # Remove reference
self.widg.deleteLater()
self.widg = None # Remove python reference count.
self.tmr = QtCore.QTimer()
self.tmr.setSingleShot(True)
self.tmr.timeout.connect(close_and_delete_widg)
self.tmr.start(2000) # 2 sec
I made a library to help run things on approximate timeouts qt_thread_updater. This library works by continuously running a timer and calling function that were posted. You basically tell it to run a function, and it will run a function in the main event loop later. The delay function is not accurate. This library was made more for threading. However, it makes it so you don't need to manage your timer.
from qt_thread_updater import get_updater
self.widg = ...
self.widg.show()
self.widg.raise_() # if already show bring to top
def close_and_delete_widg():
self.widg.close()
self.widg.setParent(None) # Remove reference
self.widg.deleteLater()
self.widg = None # Remove python reference count.
get_updater().delay(2, close_and_delete_widg) # After approximately 2 seconds call
Threading - I am going to skip, because you don't need it for your use case.
QApplication.processEvents() - This is not really recommended. It can cause issues, but may still work. Essentially, the event loop is waiting for your function to finish. If you call QApplication.processEvents() you are telling your application to process more events while you are currently waiting for this event to finish.
self.widg = ...
self.widg.show()
self.widg.raise_() # if already show bring to top
start = time.time()
while (time.time() - start) <= 2: # Sec or msec?
QtCore.QApplication.processEvents() # QtCore.QApplication.instance().processEvents()
# Close and delete the widget
self.widg.close()
self.widg.deleteLater()
self.widg = None

Python - wxPython Sequence of Command Execution

I have a properly working code, I understand I am not pasting enough code - but I will explain each command with proper comments. My question here is why is the code behaving rather than what I expected it to behave.
My code:
def OnReset(self, event): # A function event which works after a button is pressed
self.reset_pump.Disable() # Disables the button so it is clicked
self.WriteToController([0x30],'GuiMsgIn') # Sends the reset command
self.flag_read.set() # Set Event of the thread
time.sleep(0.25)
self.tr.join() # Joining a Thread
self.MessageBox('Pump RESET going on Click OK \n')
# Having the above step is useful
# The question I have is based on the commands from here:
self.offset_text_control.Clear()
self.gain_text_control.Clear()
self.firmware_version_text_control.Clear()
self.pump_rpm_text_control.Clear()
self.pressure_text_control.Clear()
self.last_error_text_control.Clear()
self.error_count_text_control.Clear()
self.pump_model_text_control.Clear()
self.pump_serial_number_text_control.Clear()
self.on_time_text_control.Clear()
self.job_on_time_text_control.Clear()
# The above commands clear various widgets on my GUI.
self.ser.close() # Closes my serial connection to MCU
time.sleep(5)
self.OnCorrectComPort(event) # An external function which gets executed. This function has a Message BOX - which says - PORT IS OPENED.
return
I expect, once the thread is joined - my commands will clear the GUI. Then close the serial connection using (ser.close()). Then the self.OnCorrectComPort(event) gets executed.
This is what I am seeing: Thread joins with tr.join() then self.OnCorrecComPort(event) gets executed as I can see the Message box with "PORT OPENED" appears, I click OK, then my GUI gets CLEARED. To my understanding this is wrong, anyone please correct me.
The problem is that you're calling time.sleep(5) and self.OnCorrectComPort() in the callback, before returning to the mainloop where the events will be processed.
The widgets will not reflect the effects of your Clear calls until you exit of the callback into the wx mainloop.
What happens is, the routines you call are executed (takes several seconds because of the time.sleep call, then wx gets to process the graphical commands, and the widgets are cleared at this very moment (which is too late and the GUI seems stuck with the previous state)
If you want it the other way round, you can use wx.CallAfter() to leave wx a chance to process its events before you call your routines.
In your case, since you want to wait 5 seconds, the risk is to freeze your interface again. It's even better to call wx.CallLater() with a 5 second delay in that case, leaving the time to wx to refresh all the widgets.
Modified code:
def OnReset(self, event): # A function event which works after a button is pressed
self.reset_pump.Disable() # Disables the button so it is clicked
self.WriteToController([0x30],'GuiMsgIn') # Sends the reset command
self.flag_read.set() # Set Event of the thread
time.sleep(0.25)
self.tr.join() # Joining a Thread
self.MessageBox('Pump RESET going on Click OK \n')
# Having the above step is useful
# The question I have is based on the commands from here:
self.offset_text_control.Clear()
self.gain_text_control.Clear()
self.firmware_version_text_control.Clear()
self.pump_rpm_text_control.Clear()
self.pressure_text_control.Clear()
self.last_error_text_control.Clear()
self.error_count_text_control.Clear()
self.pump_model_text_control.Clear()
self.pump_serial_number_text_control.Clear()
self.on_time_text_control.Clear()
self.job_on_time_text_control.Clear()
# The above commands clear various widgets on my GUI.
self.ser.close() # Closes my serial connection to MCU
# will call calledAfter after 5 seconds
wx.CallLater(5000,self.calledAfter,[ser,event])
def calledAfter(self,ser,event):
self.OnCorrectComPort(event) # An external function which gets executed. This function has a Message BOX - which says - PORT IS OPENED.

Python tkinter time.sleep()

How come when I run my code, it will sleep for 3 seconds first, then execute the 'label' .lift() and change the text? This is just one function of many in the program. I want the label to read "Starting in 3...2...1..." and the numbers changing when a second has passed.
def predraw(self):
self.lost=False
self.lossmessage.lower()
self.countdown.lift()
self.dx=20
self.dy=0
self.delay=200
self.x=300
self.y=300
self.foodx=self.list[random.randint(0,29)]
self.foody=self.list[random.randint(0,29)]
self.fillcol='blue'
self.canvas['bg']='white'
self.lossmessage['text']='You lost! :('
self.score['text']=0
self.countdown['text']='Starting in...3'
time.sleep(1)
self.countdown['text']='Starting in...2'
time.sleep(1)
self.countdown['text']='Starting in...1'
time.sleep(1)
self.countdown.lower()
self.drawsnake()
It does this because changes in widgets only become visible when the UI enters the event loop. You aren't allowing the screen to update after calling sleep each time, so it appears that it's sleeping three seconds before changing anything.
A simple fix is to call self.update() immediately before calling time.sleep(1), though the better solution is to not call sleep at all. You could do something like this, for example:
self.after(1000, lambda: self.countdown.configure(text="Starting in...3"))
self.after(2000, lambda: self.countdown.configure(text="Starting in...2"))
self.after(3000, lambda: self.countdown.configure(text="Starting in...1"))
self.after(4000, self.drawsnake)
By using after in this manner, your GUI remains responsive during the wait time and you don't have to sprinkle in calls to update.

Is there an instant-updating function for texts or patterns as button.config() in tkinter?

I wrote a modified program of the 'mines' game, and I hope it shows every step/click graphically. I use time.sleep(0.5) to make a pause. So, in general the main program is like:
check_block():
if mine == 0:
buttons[current].config(image = tile_clicked)
elif mine == 1:
buttons[current].config(image = tile[1])
...
while(1):
time.sleep(0.5)
check_block()
get_next()
if check_fail():
break
However, the buttons don't update every 0.5 second: they are all updated together when the game(loop) finishes.
I guess it's just like 'cout' in C++: if you don't flush they will get stacked. So, is there a method to get them updated step by step, or say, instantly?
Thanks!
In all GUI systems you have to allow the message loop to run so that Windowing events occur promptly. So do not use a while loop like this. Instead, create a method that calls check_block() and get_next() and use after to call that function after a delay. At the end of that function, you use after again to call the same function again so that this function is called every 0.5 second forever. The after function queues a timer event and then lets the message queue be processed. Once your timer event fires, the callback function is run which allows you to do things and keep the UI responsive.
You should never call sleep in a GUI program. This is because the GUI must be "awake" at all times so that it can service events (including internal events that cause the screen to update). Instead, leverage the already-running eventloop by using the after method to put events on the queue at regular intervals.
In your case, you would replace the while loop with something like:
def do_check():
check_block()
if not check_fail():
root.after(500, do_check)
# in your initialization code, start the loop by calling it directly:
do_check()
I don't know what your get_next function does, so I don't know if you need to call it periodically too. Probably not. I'm guessing it waits for the next button press, which you don't need to do with tkinter or most other GUI toolkits. Instead, you configure the button to call a function when clicked.
Regardless, the way to do the type of looping you want is to place events on the event queue at a regular interval.

Update a Tkinter text widget as it's written rather than after the class is finished

I'm in a bind, since this is being written on a classified machine I am unable to copy+paste here. Being somewhat a novice, my approach is probably unorthodox.
I have a GUI written in Tkinter with several buttons. Each button is linked to a class that, in effect, runs a short script. When the button is clicked, I inititalize a class log_window which is simply a Tkinter text widget. I then create a global variable linking log to the log_window I just created, and as the script runs I pipe sys.stdout/stderr to log (I created a write method specifically for this). Everything is kosher, except that the log_window text widget doesn't update with my piped stdout until after the class calling it is finished. However, if I simply print within the class, it will print in the order it is called.
Example
import Tkinter
from Tkinter import *
import time
class log_window:
def __init__(self,master):
self.textframe = Tkinter.Frame(master)
self.text = Text(self.textframe)
self.text.pack()
self.textframe.pack()
def write(self,text):
self.text.insert(END,text)
class some_func1: # This effectively waits 5 seconds then prints both lines at once
def __init__(self,master):
log.write("some text")
time.sleep(5)
log.write("some text")
class some_func2: # This prints the first object, waits 5 seconds, then prints the second
def __init__(self,master):
print "some text"
time.sleep(5)
print "some text"
if __name__ == '__main__':
global log
root = Tk()
log = log_window(root)
root.after(100,some_func1, root)
root.after(100,some_func2, root)
root.mainloop()
Sorry if my example is a little bit muffed, but I think it makes the point. The piping I do is through Popen and some system calls, but they aren't part of the issue, so I only highlighted what, I presume, is the LCD of the issue.
I don't know the details of Tkinter's concurrency, but fiddling around reveals that if you put
master.update_idletasks()
after each call to log.write, it updates on cue. You could give log a .flush() method to do that (like file handles have), or you could just make log.write call it after writing.
When you call sleep it causes your whole GUI to freeze. You must remember that your GUI runs an event loop, which is an infinite loop that wraps all your code. The event loop is responsible for causing widgets to redraw when they are changed. When a binding is fired it calls your code from within that loop, so as long as your code is running, the event loop can't loop.
You have a couple of choices. One is to call update_idletasks after adding text to the widget. This lets the event loop service "on idle" events -- things that are schedule to run when the program isn't doing anything else. Redrawing the screen is one such event, and there are others as well.
The other option is to run your functions in a thread or separate process. Because Tkinter isn't thread safe, these other threads or processes can't directly communicate with the GUI. What they must do is push a message onto a queue, and then your main (GUI) thread must poll the queue and pull messages off. It would be easy to build this code into your log class, and polling the queue can be done using the event loop -- just write a method that pulls messages off the queue and inserts them into the widget, the calls itself using after a few hundred milliseconds later.
You have to update your widget content by adding self.text.update() after self.text.insert(END,text)

Categories

Resources