Tkinter Assign Button Command in a For Loop With Lambda

Tkinter assign button command in a for loop with lambda

The problem is your lambda expression in the for loop. It is using the name variable, but the name variable gets reassigned each iteration of the for loop, so in the end, all of the buttons get the last value that name was assigned to in the for loop. To avoid this you can use default keyword parameters in your lambda expression like so:

user_button = Tkinter.Button(self.root, text=name,
command=lambda name=name: self.a(name))

This binds the current value of the name variable to the lambda's name keyword argument each time through the loop, producing the desired effect.

tkinter button created in loop command not working

Change the lambda to:
lambda i=i, j=j: show(i, j)

This will capture the values of i and j at the time the lambda is created. What's happening instead is it is looking up i and j in the scope of the lambda, but at the time the button is clicked, by which time the loop in the list comprehension is complete and i and j are both 8.

Tkinter Button Commands in For Loop

It is my understanding that e.g. i in a lambda within a loop like this is referring to the variable i itself, not the value of the variable on each iteration, so that when the command callback is called, it will use the value of i at that moment, which as you noticed is the value on the last iteration.

One way to solve this is with a partial. partial will, in effect "freeze" the arguments at their current state in the loop and use those when calling the callback.

Try using a partial instead of a lambda like this:

from functools import partial

buttonDictionary = {}
for i in range(0,len(currentVisitors)):
buttonDictionary[i] = Button(bottomFrame, text=currentVisitors[i], command=partial(signOut, topFrame, bottomFrame, currentVisitors[i]))
buttonDictionary[i].pack()

Another way I have seen this done, but haven't tried, is to assign i to a new variable in your lambda each time:

command=lambda i=i: signOut(topFrame, bottomFrame, currentVisitors[i])

I have gotten burned bad more than once when I first started with Python by using lambdas in loops (including a case very similar to this trying to assign a callback to dynamically generated buttons in a loop). I even created a snippet that would expand to think_about_it('are you sure you want to use a lambda?') whenever I typed lambda just to remind me of the pain I caused myself with that...

tkinter button different command for loop in dictionary not working

Replace

self.widgetdata[i] = Button(self, text=i, command=lambda: self.edit(i))

by

self.widgetdata[i] = Button(self, text=i, command=lambda i=i: self.edit(i))

Explanation: the body of the lambda function is executed when clicking the Button, so it uses the current value of i at execution time (i.e. the index of the last created Button), not at definition time. Creating an aliased argument, forces the creation of a local variable for each loop step, each having a different value, so refering to a different Button.

Creating Tkinter buttons in a loop and setting the command for each button to take in the indices of the loop

You need to create a local variable in lambda when using a loop

button = Button(root, text="0", height=2, width=10, command=lambda x_p=i,y_p=j: self.if_clicked_square(x_p,y_p))
button.place(x=25 + (100 * j), y=100 + (100 * i))

how to give different command to buttons created in a loop in tkinter?

Lambdas don't immediately bind the values they use. They are closures and look up the values needed when invoked. What this means is that it will only act on the last quick_button since that is what quick_button equals after your loop is finished. On top of that, you are trying to reference quick_button before it exists!

What if I told you that you could exploit this late binding behavior which is causing your problem to actually help solve your problem?

We can store a list of your buttons (buttons) and store the index of the button as a local variable within your lambda. You will then use that index to access the button that was pressed and pass that to your press_button function.

It would look something like this:

import tkinter

def press_button(button):
button.configure(bg="red")


root = tkinter.Tk()

buttons = [] # <-- Added list of buttons
for i in range(4):
quick_button = tkinter.Button(root, text=str(i) ,font=("courier", 30),
command=lambda i=i: press_button(buttons[i]))
quick_button.grid(row=i, pady=3, padx=3)
buttons.append(quick_button)

root.mainloop()

Notice the lambda i=i part. Without the i=i, you would experience the same behavior as you are experiencing now... When the lambda is called, it will look up the value of i and see that it's 3 no matter what button you press. Adding the i=i to the lambda binds it locally within the lambda.

Now, each button behaves "independently" from the rest:

red buttons

Using lambda function in command of Button widget in tkinter

The problem with the code you sent is that the value of j is changing with the loop, so as the loop ends, all of your buttons and lables take the value of j as 3 (thats because when your loop ends, j has the value "3") so that means all of your lables and buttons are using the last label.
An easy fix would be to manually set label[j] and entry[j] to some other variable, then apply the command.

Something like this :

lambda x=label[j], y=entry[j]: x.set(y.get())
Here I first set label[j] to x and entry[j] to y and then change the values inside lambda.

import tkinter as tk

main = tk.Tk()
label = [None]*3
entry = [None]*3
read = [None]*3

for j in range(3):
label[j] = tk.StringVar()
tk.Label(main, textvariable = label[j], relief = 'raised', width = 7).place(x = 5, y = 40+30*j)
entry[j] = tk.Entry(main, width=8)
entry[j].place(x=80, y=40 + 30 * j)
read[j] = tk.Button(main, text="READ", pady=0, padx=10, command= lambda x=label[j], y=entry[j]: x.set(y.get()))
read[j].place(x=150, y=40 + 30 * j)

main.mainloop()


Related Topics



Leave a reply



Submit