Search code examples
tkintersleepmainloop

tkinter: how do I insert a delay between widget changes within the same handler?


I asked a similar question about two years ago when I was trying to emulate LEDs on a Tkinter canvas. The solution then was to use the canvas after() method instead of the sleep() function to introduce a delay between widget updates.

Since then, I have discovered the tk_tools module which has a buit-in function to create LEDs, great! But I now have the same issue as before, which is: How to have a 1-second delay between the turning on (change to green) of each LED?

What actually happens when running the code below is that the LEDs are displayed in their OFF state (gray), then when I click the 'Start' button, there's a 4-second delay after which all LEDs turn on simultaneously.

Thank you. Johnnym

# LED array simulation

from tkinter import *
import tk_tools as tkt
from time import *

# turn on LEDs (change to green) with a 1-second delay between each
def turn_on():
    for led in range(4):
        led_array[led].to_green()
        sleep(1)

# list to hold a 4-LED array
led_array = []

# GUI
root = Tk()

# create 4 LED widgets, store them in led_array[], display them
for i in range(4):
    led_array.append(tkt.Led(root, size=30))
    led_array[i].grid(row=0, column=i)

# create button to initiate LED turn-on sequence
start = Button(root, text='Start', padx=20, command=turn_on)
start.grid(row=1, columnspan=4)



root.mainloop()

Solution

  • It's best to avoid sleep which stops the application and use after. Either chain the calls to the after callback so each one calls the next or call them in a loop for each led but at 1, 2, 3 then 4 seconds delay.

    Chained version:

    # LED array simulation
    
    from tkinter import *
    import tk_tools as tkt
    
    # list to hold a 4-LED array
    led_array = []
    
    # GUI
    root = Tk()
    
    # create 4 LED widgets, store them in led_array[], display them
    for i in range(4):
        led_array.append(tkt.Led(root, size=30))
        led_array[i].grid(row=0, column=i)
    
    # Both answers are common to here.
    
    def on_after( led_list ):
        led_list[0].to_green() # Set first item in the list.
        if len( led_list ) > 1:
            # Call on_after again with a shortened list.
            root.after( 1000, on_after, led_list[1:] )
        else:
            # Enable the start button
            start.config( state = NORMAL )
    
    # turn on LEDs (change to green) with a 1-second delay between each
    def turn_on():
        start.config( state = DISABLED ) # Disable the button
        root.after( 1000, on_after, led_array)
    
    # create button to initiate LED turn-on sequence
    start = Button(root, text='Start', padx=20, command=turn_on)
    start.grid(row=1, columnspan=4)
    
    root.mainloop()
    

    Looped version:

    # Both answers are common to here.
    def on_after( led ):
        led.to_green()
    
    def enable():
        start.config( state = NORMAL )
    
    # turn on LEDs (change to green) with a 1-second delay between each
    def turn_on():
        start.config( state = DISABLED ) # Disable the button
        for ix, led in enumerate( led_array ):
            # Call on_after 4 times with 1 to 4 second delays
            root.after( 1000 * (1+ix), on_after, led )
        root.after( 1000*(ix+1), enable )
    
    # create button to initiate LED turn-on sequence
    start = Button(root, text='Start', padx=20, command=turn_on)
    start.grid(row=1, columnspan=4)
    
    root.mainloop()
    

    I'd probably use the chained version although in this context the looped version is probably easier to understand.