Search code examples
pythonpython-3.xtkinterttk

tkinter root.after to run until condition is met, freezes window nav bar until the condition is met. Why?


So I have tried extensively to figure out the best way to run my code. The best suggestions have been to run a root.after recursively until a condition is met. This works but it freezes the window until the condition is met. I cannot figure out what is wrong or how to fix this. I would like to display the tkinter dialog window, check every 1000 ms if a condition has been met, once met allow the "NEXT" button to become clickable. This all works except if the condition is never met, there is no way to exit the program because the nav bar is stuck in "not responding". I really need this nav bar to not be messed up. I prefer it over a close button. Here is the code

def checkForPortConnection(root, initial_ports):
    new_ports = listSerialPorts()
    root.after(1000)
    if initial_ports == new_ports:
        checkForPortConnection(root, initial_ports)
    else:
        return
    

def welcomeGui():
    
    root = tk.Tk()
    root.title('Setup Wizard')
    canvas1 = tk.Canvas(root, relief = 'flat')
    welcome_text='Welcome to the setup wizard for your device'
    text2 =  'Please plug your device into a working USB port'
    text3 = 'If you have already plugged it in, please unplug it and restart the wizard. \n Do not plug it in until the wizard starts. \n The "NEXT" button will be clickable once the port is detected'
    label1 = tk.Label(root, text=welcome_text, font=('helvetica', 18), bg='dark green', fg='light green').pack()
    label2 = tk.Label(root, text=text2, font=('times', 14), fg='red').pack()
    label3 = tk.Label(root, text=text3, font=('times', 12)).pack()

    nextButton = ttk.Button(root, text="NEXT", state='disabled')
    nextButton.pack()
    initial_ports = listSerialPorts()
    
    root.update()
    checkForPortConnection(root, initial_ports)
    new_ports = listSerialPorts()
    correct_port = [x for x in initial_ports + new_ports if x not in initial_ports or x not in new_ports]
    print(correct_port)
    nextButton.state(["!disabled"])
    root.mainloop()

Solution

  • root.after(1000) is effectively the same as time.sleep(1) - it freezes the UI until the time has expired. It doesn't allow the event loop to process events.

    If you want to call checkForPortConnection every second, this is the proper way to do it:

    def checkForPortConnection(root, initial_ports):
        new_ports = listSerialPorts()
        if initial_ports == new_ports:
            root.after(1000, checkForPortConnection, root, initial_ports)
    

    That will call checkForPortConnection one second in the future (more or less), passing root and initial_ports as arguments. Each time it runs, it will schedule itself to be run again in the future until the condition is no longer met.

    Until the time period has expired, mainloop is able to continue to process events as normal.