Waiting certain amount of time with Tkinter
Question:
I have a Tkinter program which I want to pause for 3 seconds.
time.sleep
doesn’t work and the after
method doesn’t do exactly what I want to.
here is an example code:
from Tkinter import *
def waithere():
print "waiting..."
root = Tk()
print "1"
root.after(3000,waithere)
print "2"
root.mainloop()
output:
1
2
*3 seconds*
waiting...
the output i want to have:
1
waiting...
*3 seconds*
2
thanks.
Answers:
Just for future reference, refrain from using long or infinite loops in Tkinter
; they will prevent the UI from responding to user events (AKA freezing). The method I was taught was to periodically update the field using the after()
function.
The after()
function creates an alarm-callback meaning when called (with the right parameters) it will queue a call to the target method (in the example below def update(self)
with our entered delay. You can use a boolean in the class to exit the loop. Create on on __init__
and then when set to False
don’t call after()
anymore.
Here is an example creating a class inheriting Tkinter.Frame to inherit the functionality.
try:
import tkinter as tk
except:
import Tkinter as tk
import datetime
class DelayedUpdateUI(tk.Frame):
def __init__(self, master=None, **kw):
# Create widgets, if any.
tk.Frame.__init__(self, master=master, **kw)
self.timeStr = tk.StringVar()
self.lblTime = tk.Label(self, textvariable=self.timeStr)
self.lblTime.grid()
# Call update to begin our recursive loop.
self.update()
def update(self):
self.timeStr.set(datetime.datetime.now())
# We use after( milliseconds, method_target ) to call our update
# method again after our entered delay. :)
self.after(1000, self.update)
if __name__ == '__main__':
root = tk.Tk()
DelayedUpdateUI(root).grid()
root.mainloop()
Normally it’s a very bad idea to have a GUI wait for something. That’s imply not how event-based programs work. Or more accurately, GUIs are already in a perpetual wait state, and you don’t want to block that with your own waiting.
That being said, tkinter has a way to wait until certain things happen. For example, you can use one of the “wait” functions, such as wait_variable, wait_window, or wait_visibility.
Assuming that you wanted waithere
to do the waiting, you could use wait_variable to do the waiting, and after
to set the variable after a given amount of time.
Here’s the solution based on your original code:
from Tkinter import *
def waithere():
var = IntVar()
root.after(3000, var.set, 1)
print("waiting...")
root.wait_variable(var)
root = Tk()
print "1"
waithere()
print "2"
root.mainloop()
The advantage to using these methods is that your code is still able to respond to events while it is waiting.
A suggestion based on Bryan’s answer:
I understand the recommended way from an event-based perspective, but it does not feel very intuitive to me. I have to look up the trick every time I need it. Therefore, I have created a small mixin class that makes usage a bit more intuitive:
import tkinter as tk
class TkWaitMixin:
"""Simple wait timer that makes Tk waiting functionality
more intiutive. Applies the recommended way according to
https://stackoverflow.com/a/51770561/12646289.
"""
def start_wait_timer(self, milliseconds):
self.resume = tk.BooleanVar(value=False)
self.master.after(milliseconds, self.resume.set, True)
# Assume master attribute is available:
# https://stackoverflow.com/a/53595036/12646289
def wait_on_timer(self):
self.master.wait_variable(self.resume)
Example usage:
import tkinter as tk
class MyWindow(tk.Tk, TkWaitMixin):
def __init__(self, master):
self.master = master
self.message_label = tk.Label('')
self.message_label.pack(padx=50, pady=50)
def show_message(self, message, milliseconds):
self.start_wait_timer(milliseconds)
self.message_label['text'] = message
self.wait_on_timer()
self.message_label['text'] = ''
root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()
Obviously, this will only be of use if you use classes in your tkinter code. Also note that the master
attribute should be available in the main class to which the mixin is added.
Edit
Alternatively, usage can be made even easier with a context manager:
import tkinter as tk
class TkWait:
def __init__(self, master, milliseconds):
self.duration = milliseconds
self.master = master
def __enter__(self):
self.resume = tk.BooleanVar(value=False)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.master.after(self.duration, self.resume.set, True)
self.master.wait_variable(self.resume)
Note that the waiting starts when the context manager is exited.
Example usage:
import tkinter as tk
class MyWindow(tk.Tk):
def __init__(self, master):
self.master = master
self.message_label = tk.Label('')
self.message_label.pack(padx=50, pady=50)
def show_message(self, message, milliseconds):
with TkWait(self.master, milliseconds):
self.message_label['text'] = message
self.message_label['text'] = ''
root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()
You forgot to do the () at root.after(3000,waithere <<<<-)
from tkinter import *
def waithere():
print("waiting...")
root = Tk()
print("1")
root.after(3000,waithere())
print("2")
root.mainloop()
I found a way like that, i hope it helps you:
from tkinter import *
def waitToShow():
index = 1
while index < 11:
l1.config(text=index)
l1.after(1000)
l1.update()
index += 1
win = Tk()
l1 = Label(win)
l1.pack()
waitToShow()
win.mainloop()
I have a Tkinter program which I want to pause for 3 seconds.
time.sleep
doesn’t work and the after
method doesn’t do exactly what I want to.
here is an example code:
from Tkinter import *
def waithere():
print "waiting..."
root = Tk()
print "1"
root.after(3000,waithere)
print "2"
root.mainloop()
output:
1
2
*3 seconds*
waiting...
the output i want to have:
1
waiting...
*3 seconds*
2
thanks.
Just for future reference, refrain from using long or infinite loops in Tkinter
; they will prevent the UI from responding to user events (AKA freezing). The method I was taught was to periodically update the field using the after()
function.
The after()
function creates an alarm-callback meaning when called (with the right parameters) it will queue a call to the target method (in the example below def update(self)
with our entered delay. You can use a boolean in the class to exit the loop. Create on on __init__
and then when set to False
don’t call after()
anymore.
Here is an example creating a class inheriting Tkinter.Frame to inherit the functionality.
try:
import tkinter as tk
except:
import Tkinter as tk
import datetime
class DelayedUpdateUI(tk.Frame):
def __init__(self, master=None, **kw):
# Create widgets, if any.
tk.Frame.__init__(self, master=master, **kw)
self.timeStr = tk.StringVar()
self.lblTime = tk.Label(self, textvariable=self.timeStr)
self.lblTime.grid()
# Call update to begin our recursive loop.
self.update()
def update(self):
self.timeStr.set(datetime.datetime.now())
# We use after( milliseconds, method_target ) to call our update
# method again after our entered delay. :)
self.after(1000, self.update)
if __name__ == '__main__':
root = tk.Tk()
DelayedUpdateUI(root).grid()
root.mainloop()
Normally it’s a very bad idea to have a GUI wait for something. That’s imply not how event-based programs work. Or more accurately, GUIs are already in a perpetual wait state, and you don’t want to block that with your own waiting.
That being said, tkinter has a way to wait until certain things happen. For example, you can use one of the “wait” functions, such as wait_variable, wait_window, or wait_visibility.
Assuming that you wanted waithere
to do the waiting, you could use wait_variable to do the waiting, and after
to set the variable after a given amount of time.
Here’s the solution based on your original code:
from Tkinter import *
def waithere():
var = IntVar()
root.after(3000, var.set, 1)
print("waiting...")
root.wait_variable(var)
root = Tk()
print "1"
waithere()
print "2"
root.mainloop()
The advantage to using these methods is that your code is still able to respond to events while it is waiting.
A suggestion based on Bryan’s answer:
I understand the recommended way from an event-based perspective, but it does not feel very intuitive to me. I have to look up the trick every time I need it. Therefore, I have created a small mixin class that makes usage a bit more intuitive:
import tkinter as tk
class TkWaitMixin:
"""Simple wait timer that makes Tk waiting functionality
more intiutive. Applies the recommended way according to
https://stackoverflow.com/a/51770561/12646289.
"""
def start_wait_timer(self, milliseconds):
self.resume = tk.BooleanVar(value=False)
self.master.after(milliseconds, self.resume.set, True)
# Assume master attribute is available:
# https://stackoverflow.com/a/53595036/12646289
def wait_on_timer(self):
self.master.wait_variable(self.resume)
Example usage:
import tkinter as tk
class MyWindow(tk.Tk, TkWaitMixin):
def __init__(self, master):
self.master = master
self.message_label = tk.Label('')
self.message_label.pack(padx=50, pady=50)
def show_message(self, message, milliseconds):
self.start_wait_timer(milliseconds)
self.message_label['text'] = message
self.wait_on_timer()
self.message_label['text'] = ''
root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()
Obviously, this will only be of use if you use classes in your tkinter code. Also note that the master
attribute should be available in the main class to which the mixin is added.
Edit
Alternatively, usage can be made even easier with a context manager:
import tkinter as tk
class TkWait:
def __init__(self, master, milliseconds):
self.duration = milliseconds
self.master = master
def __enter__(self):
self.resume = tk.BooleanVar(value=False)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.master.after(self.duration, self.resume.set, True)
self.master.wait_variable(self.resume)
Note that the waiting starts when the context manager is exited.
Example usage:
import tkinter as tk
class MyWindow(tk.Tk):
def __init__(self, master):
self.master = master
self.message_label = tk.Label('')
self.message_label.pack(padx=50, pady=50)
def show_message(self, message, milliseconds):
with TkWait(self.master, milliseconds):
self.message_label['text'] = message
self.message_label['text'] = ''
root = tk.Tk()
mywin = MyWindow(master=root)
mywin.show_message('Hello world', 2000)
root.mainloop()
You forgot to do the () at root.after(3000,waithere <<<<-)
from tkinter import *
def waithere():
print("waiting...")
root = Tk()
print("1")
root.after(3000,waithere())
print("2")
root.mainloop()
I found a way like that, i hope it helps you:
from tkinter import *
def waitToShow():
index = 1
while index < 11:
l1.config(text=index)
l1.after(1000)
l1.update()
index += 1
win = Tk()
l1 = Label(win)
l1.pack()
waitToShow()
win.mainloop()