pythontkinter

Tkinter: Assigning binds to a large number of widgets (frames) created by a loop function


I am developing a gallery display tool with Python's Tkinter which involves creating a lot of frames at once, each containing an image. This is achieved with 'while' or 'for' loops:

x = 0
while x < 10:
     tk.Frame(r, width=50, height=50, borderwidth=1, relief='solid', bg='purple').pack()
     x+=1

This works well since I don't need to store any of in it in a variable. However the issue is that I also want to bind an event to each one of these frames which doesn't work as intended:

import tkinter as tk

r = tk.Tk()

# This function switches the Frame's color
def change_color(wid):
    # This print function shows that only '.!Frame10' is being affected
    print(wid)

    if wid['bg'] == 'purple':
        wid['bg'] = 'green'

    elif wid['bg'] == 'green':
        wid['bg'] = 'purple'

# Frames are created by functions to allow binding & packing using a temporary reference
def frame():
    main = tk.Frame(r, width=50, height=50, borderwidth=1, relief='solid', bg='purple')
    # This bind triggers the function which affects the frame being left-clicked
    main.bind_all('<Button-1>', lambda a: change_color(main) )
    main.pack()

# This loop creates 10 instances of the frame
x = 0
while x < 10:
     frame()
     x+=1

r.mainloop()

The intended result is that each individual frame can be left-clicked to switch it's colors but instead only the last frame, dubbed ".!Frame10" switches color if any on the frames are clicked.

I read that you can sort of automate storing objects/functions in variables using dictionaries but it doesn't seem to work with me. This example I made returns " KeyError: '3' " which I don't understand.

import tkinter as tk

r = tk.Tk()

# This function switches the Frame's color
def change_color(x):
    # This print function shows that only '.!Frame10' is being affected
    print(x)

    if x['bg'] == 'purple':
        x['bg'] = 'green'

    elif x['bg'] == 'green':
        x['bg'] = 'purple'

dic = {}

# This loop creates frames and stores them in the dictionary above.
x = 1
while x < 3:
    dic[str(x)] = tk.Frame(r, width=50, height=50, borderwidth=1, relief='solid', bg='purple')
    dic[str(x)].bind_all('<Button-1>', lambda a: change_color(dic[str(x)]) )
    dic[str(x)].pack()
    x+=1

r.mainloop()

I did get it to work with dictionaries a few times but it gave the same result as above; only the last frame is changed.

Any help would be appreciated.


Solution

  • This will solve your problem. Explanations given as comments inside the code

    import tkinter as tk
    r = tk.Tk()
    r.geometry('500x700')
    
    # This function switches the Frame's color
    def change_color(wid):
        current_bg = wid.cget('bg') # use wid.cget()
        if current_bg == 'purple':
            wid.config(bg = 'green') # Use wid.config
    
        elif current_bg ==  'green': # use wid.cget()
            wid.config(bg = 'purple') # Use wid.config
    
    def create_frames():       
        
        for x in range(10):
            frame = tk.Frame(r, width=50, height=50, borderwidth=1, relief='solid', bg='purple')
            frame.pack()
    
            # Pass the current frame as a default argument to fix late binding
            frame.bind('<Button-1>', lambda event, current_frame=frame: change_color(current_frame))
    
               
    
    create_frames()
    r.mainloop()