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.

Asked By: Doron Sever

||

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()
Answered By: Noah M.

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.

Answered By: Bryan Oakley

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()
Answered By: Jan-Willem Lankhaar

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()
Answered By: Tymam

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()
Answered By: episometnal
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.