Hi all,
Ive been looking to convert one of my programs to include a GUI, and am having issues with the timing aspects of it. Ive included a mock scenario of what my problem is below:
Ive defined my GUI as an object via a class, and am using the buttons to call other methods in the class. These methods used to use the time.sleep() function to wait a certain time before completing a task, and am now using the root.after method instead. However, once the button is pressed and the method with the delay is caused, (delay 10s), the GUI hangs until this method is finished, and thus the "stop" and any other buttons are useless. Considering that this method will eventually be calling itself in a type of recursion, this would mean the GUI would be "frozen" untill the program is finished with no option to interrupt of change settings. Would there be a way to circumvent this?

class App:
    def __init__(self, master):

        self.master = master

        # start BUTTON
        self.start_button = Button(master, text="Start", command=lambda: self.test(), height=2, width=30,
                                          fg='blue')
        # STOP BUTTON
        self.stop_button = Button(master, text="Stop", command=lambda: self.standby('STOP'), height=2, width=25,
                                         fg='orange')
        # QUIT BUTTON
        self.quit_button = Button(master, text="Quit", command=self.quit, height=2, width=25, fg='red')
        ######LAYOUT#####

        self.start_button.grid(row=2, column=0, columnspan=2)
        self.stop_button.grid(row=2, column=2, columnspan=1)
        self.quit_button.grid(row=2, column=3, columnspan=1)

    def standby(self, text):
        print(text)

    def quit(self):
        print('QUIT')
        self.master.destroy()

    def test(self):
        print('start')
        root.after(10000)   #issue is here, Id like to have it wait 10sec and not freeze the entire GUI
        print('10sec')
        return

root = Tk()
my_gui = App(root)
root.mainloop()

Ultimately, Im looking for a way to be able to induce a 10-15s delay inside the methods while still being able to use the other buttons, ie stop and quit, aswell as radiobuttons and things in the future.
Many thanks in advance

Recommended Answers

All 5 Replies

You probably want to call after() with a method argument

class App:
    def test(self):
        print('hello')
        root.after(10000, self.endoftest)

    def endoftest(self):
        print('world')
commented: Ill definitely keep this in the toolbag, thanks again! +0

That makes sense, and ive tested it in this format and it works fine, the only problem is i have methods with lots of 1-2 second breaks within them. With this approach i'd need to split each method into about 10 others and pause between methods, rather than just being able to have a pause within a method? If theres no way to actually do that ill just split and have about 100 different functions, but its gonna get really messy. Thanks so much for the response!

(pseudo code with what im trying to accomplish, if not possible ill resort to the lower way)

#TRYING THIS 
def method(self, arg_a, arg_b, .....):
    do something with a and b
    wait for 2 seconds
    do more things
    wait again
    .
    .
    .
    return
##RATHER THAN
def method_1(self, arg_a, arg_b):
    do something with a and b
    root.after(10000, self.method_2(arg_a, arg_b):
def method_2(self, arg_a, arg_b):
    .
    .
    .

There are probably several ways to accomplish this, for example

def method(self, step, a, b):
    if step == 'start':
        do_something(a, b)
        root.after(2000, self.method, 'again',  a, b)
    elif step == 'again':
        something_else(a, b)
        root.after(3000, self.method, 'theend', a, b)
    elif step == 'theend':
        print('This is the end')
commented: Thats such a good idea, Im gonna give this a shot right now, thanks so much for your help! +0

A still better idea uses a generator (tested with python 3) :

from tkinter import *

class Helper:
    __name__ = 'Helper'
    def __init__(self, func, *args, **kwargs):
        self.gen = func(self.sleep, *args, **kwargs)
        self()

    def __call__(self):
        try:
            next(self.gen)
        except StopIteration:
            pass

    def sleep(self, ms):
        root.after(ms, self)

class App:
    def __init__(self, master):

        self.master = master

        # start BUTTON
        self.start_button = Button(master, text="Start", command=lambda: Helper(self.test, 'foo', 'bar'), height=2, width=30,
                                          fg='blue')
        # STOP BUTTON
        self.stop_button = Button(master, text="Stop", command=lambda: self.standby('STOP'), height=2, width=25,
                                         fg='orange')
        # QUIT BUTTON
        self.quit_button = Button(master, text="Quit", command=self.quit, height=2, width=25, fg='red')
        ######LAYOUT#####

        self.start_button.grid(row=2, column=0, columnspan=2)
        self.stop_button.grid(row=2, column=2, columnspan=1)
        self.quit_button.grid(row=2, column=3, columnspan=1)

    def standby(self, text):
        print(text)

    def quit(self):
        print('QUIT')
        self.master.destroy()

    def test(self, sleep, a, b):
        print('start')
        yield sleep(1000)
        print('A', a)
        yield sleep(1000)
        print('B', b)

root = Tk()
my_gui = App(root)
root.mainloop()

The new asynchronous calls from python 3 could also give a solution.

Edit: changed to include arguments for the test method
Edit2: better version with syntactic sugar

In this new version, the pausing method only generates integers to specify the number of milliseconds to pause. The calls to after() are handled by a wrapper class

from tkinter import *

class Helper:
    def __init__(self, widget, func, *args, **kwargs):
        self.widget = widget
        self.gen = func(args[0], *args[1:], **kwargs)
        self.next()

    def next(self):
        try:
            ms = next(self.gen)
        except StopIteration:
            pass
        else:
            if ms:
                self.widget.after(ms, self.next)

class App:
    def __init__(self, master):

        self.master = master

        # start BUTTON
        self.start_button = Button(master, text="Start", command=lambda: Helper(root, self.test, 'foo', 'bar'), height=2, width=30,
                                          fg='blue')
        # STOP BUTTON
        self.stop_button = Button(master, text="Stop", command=lambda: self.standby('STOP'), height=2, width=25,
                                         fg='orange')
        # QUIT BUTTON
        self.quit_button = Button(master, text="Quit", command=self.quit, height=2, width=25, fg='red')
        ######LAYOUT#####

        self.start_button.grid(row=2, column=0, columnspan=2)
        self.stop_button.grid(row=2, column=2, columnspan=1)
        self.quit_button.grid(row=2, column=3, columnspan=1)

    def standby(self, text):
        print(text)

    def quit(self):
        print('QUIT')
        self.master.destroy()

    def test(self,  a, b):
        print('start')
        yield 2000
        print('A', a)
        yield 1000
        print('B', b)

root = Tk()
my_gui = App(root)
root.mainloop()
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.