Search code examples
pythonuser-interfacetkinterttkbootstrap

How can I prevent my program from freezing in tkinter when using the after() function?


import ttkbootstrap as tb


def timer():
    global hours, minutes, seconds
    hours = 0
    minutes = 0
    seconds = 0

    if hours_combo.get() != "":
        hours = hours_combo.get()
        hours = int(hours)
        
    if minutes_combo.get() != "":
        minutes = minutes_combo.get()
        minutes = int(minutes)
        
    if seconds_combo.get() != "":
        seconds = seconds_combo.get()
        seconds = int(seconds)
    
    root.after(((1000 * ((hours * 3600) + (minutes * 60) + seconds))), finish)
    countdown()

def countdown():
    for count in range(seconds, 0, -1):
        countdown_seconds = count % 60
        countdown_minutes = int(count / 60) % 60
        countdown_hours = int(count / 3600)
        print(f"{countdown_hours:02}:{countdown_minutes:02}:{countdown_seconds:02}")
        root.after(1000)

def finish():
    print("Done")

When I called the countdown function, the program executes correctly and the countdown prints but the Gui freezes. I thought previously that the after() function prevented this from happening. I removed the variable declarations as they aren't relevant. I attempted to use multiprocessing or threading but neither solution worked.


Solution

  • Here is how you use after to perform this task. You have after call that same function one second later, until the counter goes to zero. Remember that your event callbacks must run VERY quickly. You can't ever delay in an event callback.

    
    seconds = 0
    def timer():
        global seconds
        hours = 0
        minutes = 0
        seconds = 0
    
        if hours_combo.get() != "":
            hours = hours_combo.get()
            hours = int(hours)
            
        if minutes_combo.get() != "":
            minutes = minutes_combo.get()
            minutes = int(minutes)
            
        if seconds_combo.get() != "":
            seconds = seconds_combo.get()
            seconds = int(seconds)
    
        seconds = hours*3600 + minutes*60 + seconds
        
        countdown()
    
    def countdown():
        global seconds
        seconds -= 1
        countdown_seconds = seconds % 60
        countdown_minutes = seconds // 60 % 60
        countdown_hours = seconds // 3600
        print(f"{countdown_hours:02}:{countdown_minutes:02}:{countdown_seconds:02}")
    
        if seconds:
            root.after(1000, countdown)
        else:
            print("Done")