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
Related
I'm trying to run some code inside of a GUI, where I run a function after I get a few text inputs. however the function I am trying to run is actually really complicated, so when it runs, it makes the entire gui freeze up for 10-15 seconds before continuing.
How can I make it so that when I hit the run button, it doesn't freeze up the entire GUI waiting for the function to complete?
I do understand that there is a way to make functions threaded, however, I don't know how to implement that?
An example of how I can wrap a function to make it a threaded one would be great.
The code below gives an example of the problem that I am dealing with.
import PySimpleGUI as sg
import time
def simple_gui():
layout = [ [sg.T('try clicking "do something" and move the window')],
[sg.Button('do something'), sg.Button('Exit')] ]
w = sg.Window('test', layout)
while True:
events, values = w.read()
if events == 'do something':
# If you hit the button "do something":
# run a function that takes 30 seconds to complete.
time.sleep(30)
if events == sg.WIN_CLOSED or events == 'Exit':
break
w.close()
simple_gui()
There are a number of examples of how you can use threads to perform "long operations" using PySimpleGUI. You'll find the demo programs located at http://Demos.PySimpleGUI.org . There are at least 6 sample programs labeled as being multi-threaded examples.
Some are also available on Trinket to run online. This one shows how to run a thread and then wait for it to complete:
https://pysimplegui.trinket.io/demo-programs#/multi-threaded/multi-threaded-long-task-simple
Here's the code that you'll find there.
#!/usr/bin/python3
import threading
import time
import PySimpleGUI as sg
"""
DESIGN PATTERN - Multithreaded Long Tasks GUI using shared global variables
Presents one method for running long-running operations in a PySimpleGUI environment.
The PySimpleGUI code, and thus the underlying GUI framework, runs as the primary, main thread
The "long work" is contained in the thread that is being started. Communicating is done (carefully) using global variables
There are 2 ways "progress" is being reported to the user.
You can simulate the 2 different scenarios that happen with worker threads.
1. If a the amount of time is known ahead of time or the work can be broken down into countable units, then a progress bar is used.
2. If a task is one long chunk of time that cannot be broken down into smaller units, then an animated GIF is shown that spins as
long as the task is running.
"""
total = 100 # number of units that are used with the progress bar
message = '' # used by thread to send back a message to the main thread
progress = 0 # current progress up to a maximum of "total"
def long_operation_thread(seconds):
"""
A worker thread that communicates with the GUI through a global message variable
This thread can block for as long as it wants and the GUI will not be affected
:param seconds: (int) How long to sleep, the ultimate blocking call
"""
global message, progress
print('Thread started - will sleep for {} seconds'.format(seconds))
for i in range(int(seconds * 10)):
time.sleep(.1) # sleep for a while
progress += total / (seconds * 10)
message = f'*** The thread says.... "I am finished" ***'
def the_gui():
"""
Starts and executes the GUI
Reads data from a global variable and displays
Returns when the user exits / closes the window
"""
global message, progress
sg.theme('Light Brown 3')
layout = [[sg.Text('Long task to perform example')],
[sg.Output(size=(80, 12))],
[sg.Text('Number of seconds your task will take'),
sg.Input(key='-SECONDS-', size=(5, 1)),
sg.Button('Do Long Task', bind_return_key=True),
sg.CBox('ONE chunk, cannot break apart', key='-ONE CHUNK-')],
[sg.Text('Work progress'), sg.ProgressBar(total, size=(20, 20), orientation='h', key='-PROG-')],
[sg.Button('Click Me'), sg.Button('Exit')], ]
window = sg.Window('Multithreaded Demonstration Window', layout)
thread = None
# --------------------- EVENT LOOP ---------------------
while True:
event, values = window.read(timeout=100)
if event in (None, 'Exit'):
break
elif event.startswith('Do') and not thread:
print('Thread Starting! Long work....sending value of {} seconds'.format(float(values['-SECONDS-'])))
thread = threading.Thread(target=long_operation_thread, args=(float(values['-SECONDS-']),), daemon=True)
thread.start()
elif event == 'Click Me':
print('Your GUI is alive and well')
if thread: # If thread is running
if values['-ONE CHUNK-']: # If one big operation, show an animated GIF
sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', transparent_color='white', time_between_frames=100)
else: # Not one big operation, so update a progress bar instead
window['-PROG-'].update_bar(progress, total)
thread.join(timeout=0)
if not thread.is_alive(): # the thread finished
print(f'message = {message}')
sg.popup_animated(None) # stop animination in case one is running
thread, message, progress = None, '', 0 # reset variables for next run
window['-PROG-'].update_bar(0,0) # clear the progress bar
window.close()
if __name__ == '__main__':
the_gui()
print('Exiting Program')
I am making a KIVY program in python and have a time.sleep(3) in my code so that it waits three seconds before changing the screen. But the function above it works after the 3 seconds and not before it. I am having no errors and I have tried everything but nothing seems to work.
Here is the snippet.
def input_button(self, instance): # creating the button that when pressed updates the label
query = "You Said {}".format(self.command()) # making the query
if query == "You Said None":
self.update_info('Please input a command')
else:
self.update_info(query) # updating the label
time.sleep(3)
pa_app.screen_manager.current = "Result"
The self.update_info(query) runs after three seconds but the time.sleep is after it.
I fixed this problem by using the from kivy.clock import as Clock module. I used Clock.schedule_once functions and passed self.change_screen, 10. I created the self.change_screen function as the Clock.schedule_once only takes a function and time as the parameters. Although I wanted to wait for 3 seconds, passing 3 in the function didn't make it wait for 3 seconds but less than it. So for having the same effect I passed in 10. This solved the problem.
Kivy is a GUI framework and you cannot add sleep statements safely.
when
self.update_info(query)
is called kivy will update the GUI only after the last line of your function is called, because this is when you give back the control to the Kivy gui engine.
You have to check whether kivy does have timers.
You can start a timer and tell it to call another function that does the
pa_app.screen_manager.current = "Result"
whenever the timer finished.
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.
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.
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.