Tkinter mixes bindings of different widgets when the widgets are created in a loop, but when the widgets are created separately, the bindings are correct. I cannot figure out why this is the case.
Wrong bindings
Here's a working example to reproduce the problem. Two widgets (images that should work as buttons that are highlighted when hovering over) are created in a loop and packed in a second loop.
import tkinter as tk
import tkinter.ttk as ttk
IMG_DEFAULT = 'add_default.png'
IMG_HOVER = 'add_hover.png'
class Application(tk.Tk):
def __init__(self, master=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title('WRONG!')
self.img_default = tk.PhotoImage(file=IMG_DEFAULT)
self.img_hover = tk.PhotoImage(file=IMG_HOVER)
self.create_widgets()
self.pack_widgets()
def create_widgets(self):
self.buttons = []
for i in range(2):
button = ttk.Label(image=self.img_default)
button.bind('<Button-1>', ...) # Do something
button.bind('<Enter>', lambda e: button.config(image=self.img_hover))
button.bind('<Leave>', lambda e: button.config(image=self.img_default))
self.buttons.append(button)
def pack_widgets(self):
for button in self.buttons:
button.pack(padx=100, pady=10)
app = Application()
app.mainloop()
As can be seen from the image below, the bindings are wrong: when I hover over the upper button, the lower button is highlighted.
Correct binding
When the widgets are created outside a loop, the key bindings are correct.
import tkinter as tk
import tkinter.ttk as ttk
IMG_DEFAULT = 'add_default.png'
IMG_HOVER = 'add_hover.png'
class Application(tk.Tk):
def __init__(self, master=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title('RIGHT!')
self.img_default = tk.PhotoImage(file=IMG_DEFAULT)
self.img_hover = tk.PhotoImage(file=IMG_HOVER)
self.create_widgets()
self.pack_widgets()
def create_widgets(self):
button1 = ttk.Label(image=self.img_default)
button1.bind('<Button-1>', ...) # Do something
button1.bind('<Enter>', lambda e: button1.config(image=self.img_hover))
button1.bind('<Leave>', lambda e: button1.config(image=self.img_default))
button2 = ttk.Label(image=self.img_default)
button2.bind('<Button-1>', ...) # Do something
button2.bind('<Enter>', lambda e: button2.config(image=self.img_hover))
button2.bind('<Leave>', lambda e: button2.config(image=self.img_default))
self.buttons = [button1, button2]
def pack_widgets(self):
for button in self.buttons:
button.pack(padx=100, pady=10)
app = Application()
app.mainloop()
As can be seen from the image, now the bindings are correct: when I hover over the upper button, the upper button is highlighted and when I hover over the lower, it is highlighted.
Can anyone explain why the second case works as intended but the first does not? In my application, I have a more general use case with an unknown number of buttons. How can I get the correct bindings in place?
EDIT
As pointed out by Bryan Oakley in his comment, my question is a duplicate of Tkinter assign button command in loop with lambda. Both the solutions of Henry (https://stackoverflow.com/a/66663554/12646289) and acw1668 provide the solution.
Thanks a lot for your help! I have upvoted the comments and accepted Henry's answer.