wowzaaa
wowzaaa

Reputation: 191

How to link button with labels in tkinter without explicitly declaring them as variables

I want to link my buttons with labels, however I am not allowed to explicitly declare them as variables because I will use loop for it (e.g. number of button-label pair will depend on user input).

for i in range(desired_number):
    button = tk.Button(self.mainframe)
    button.grid(row = i, column = 0)
    label = tk.Label(self.mainframe)
    label.grid(row = i, column = 1)

    button.config(command = lambda: self.changetext(label))

@staticmethod
def changetext(label):
    label.config(text="button on my left has been pressed")

this however doesn't work. It seems that I can't just pass the object, but also have to pass the explicit name for it. Any idea on tackling this?

Upvotes: 0

Views: 194

Answers (3)

OysterShucker
OysterShucker

Reputation: 5541

If you are using python 3.8+ you can use a walrus (:=) and accomplish what you want on 2 lines. It doesn't matter that the Label is being added to the grid before the Button. Their grid positions were defined and those are the positions they will reside in.

Using names like desired_count and change_label is too generic. I changed those names to better reflect what they refer to.

import tkinter as tk

def indicate_button(label):
    label['text'] = 'corresponding button pressed'
    
root = tk.Tk()
combo_count = 5

for i in range(combo_count):
    (lbl := tk.Label(root, width=25)).grid(row=i, column=1)
    tk.Button(root, text=f"Button {i}", command=lambda L=lbl: indicate_button(L)).grid(row=i, column=0)

root.mainloop()

Another way to do this would be to store the Label in a list. I realize this answer was already given, but their version is messy, incomplete and has errors. I also extended the example to show how this could be useful.

import tkinter as tk

root = tk.Tk()
combo_count = 5

labels  = [None]*combo_count

def indicate_button(i):
    if i < len(labels) and labels[i]:
        for n, lbl in enumerate(labels):
            #assign target label and clear the others
            lbl['text'] = 'corresponding button pressed' if n == i else ''
    else:
        #do something about an out of range or empty index
        pass    

for i in range(combo_count):
    labels[i] = tk.Label(root, width=25)
    labels[i].grid(row=i, column=1)
    tk.Button(root, text=f'Button {i}', command=lambda i=i: indicate_button(i)).grid(row=i, column=0)


root.mainloop()

Upvotes: 0

Mike67
Mike67

Reputation: 11342

For dynamically created widgets, you can store them in a array for later access. In this case, you can pass the label index to the button handler.

lstbtn = []
lstlbl = []

for i in range(desired_number):
    button = tk.Button(self.mainframe)
    button.grid(row = i, column = 0)
    lstbtn.append(button)  # save for later
    label = tk.Label(self.mainframe)
    label.grid(row = i, column = 1)
    lstlbl.append(label)  # save for later

    button.config(command = lambda: self.changetext(i)) # pass label index

@staticmethod
def changetext(idx):  # label index
    lstlbl[idx].config(text="button on my left has been pressed")

Upvotes: 0

scotty3785
scotty3785

Reputation: 7006

Try adding a parameter to your lambda. This way it should use each button seperately rather than just the last button/label defined. Simple executable example below

import tkinter as tk

def changetext(label):
    label.config(text="button on my left has been pressed")

root = tk.Tk()
desired_number = 5

for i in range(desired_number):
    button = tk.Button(root,text=f"Button {i}")
    button.grid(row = i, column = 0)
    label = tk.Label(root)
    label.grid(row = i, column = 1)
    button.config(command = lambda x=label: changetext(x))


root.mainloop()

Upvotes: 1

Related Questions