Search code examples
pythonpython-3.xtkinterpython-itertools

How do I use itertools.cycle with multiple objects?


I'm trying to cycle the colour of a button when pressed in Tkinter through the colours defined in the itertools.cycle iterator:

colour=itertools.cycle('blue', 'green', 'orange', 'red', 'yellow', 'white')

def changeColour(button):
    button.configure(bg = next(colour))

LT = Button(root, width=16, height=8, bg = 'white', command=lambda: changeColour(LT))
LT.place(x=10, y=10)

LM = Button(root, width=16, height=8, bg = 'white', command=lambda: changeColour(LM))
LM.place(x=10, y=150)

LB = Button(root, width=16, height=8, bg = 'white', command=lambda: changeColour(LB))
LB.place(x=10, y=290)

However, each button press affects the starting position in the iterable for the next button press, meaning each button will jump to the value of next(colour) after the one assigned to the previously clicked button. I'm trying to make each button perform the full cycle of background colours independant of the other button's current colour. How could I achieve this?


Solution

  • You need to have a separate cycle for each button if you want them to be independent. One way to do this would be to create a function factory for building the functions you need to call:

    COLOURS = ('blue', 'green', 'orange', 'red', 'yellow', 'white')
    
    def colour_changer(button, colours=COLOURS):
        """Creates a callback to change the colour of the button."""
        colour_cycle = itertools.cycle(colours)
        def command():
            """The callback to change a single button's colour."""
            button.configure(bg=next(colour_cycle))
        return command
    

    Then you call this after creating each button:

    LT = Button(root, width=16, height=8, bg = 'white')
    LT.configure(command=colour_changer(LT))
    

    You could also look into bindings, which will pass the button into the callback for you.