This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 4 years ago.
I have this code to create a series of bindings in a loop:
from Tkinter import *
keys = {0:'m', 1:'n', 2:'o'}
def SomeFunc(event=None,number=11):
print keys[number], number
root = Tk()
field = Canvas(root, height = 200, width = 200, bg = "gray")
for i in range(2):
root.bind("<KeyPress-%c>" % keys[i],lambda ev:SomeFunc(ev,i))
field.pack()
root.mainloop()
my problem is that when I press 'm' or 'n' the function SomeFunc gets called with the vairable 'i' as an argument. I would like it to be called with a 0 as argument (the numerical value 'i' had when 'bind' was used) when I press 'm' and with 1 when I press 'n'. Can this be done?
Your problem here is that the variable i gets captured by the lambda, but you can get around that by creating a little helper function for example:
for i in range(2):
def make_lambda(x):
return lambda ev:SomeFunc(ev,x)
root.bind("<KeyPress-%c>" % keys[i], make_lambda(i))
This creates a new scope for each binding you create, thus executing the for loop and changing of i during the loop does not influence your already lambda functions.
Related
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 1 year ago.
I am making GUI and have some problems on the button command which is used to clear the input value .When i input a value into the 1st entry and try to click the 1st button , the value is not erased which should be cleared, Same as the 2nd button.But for the 3rd button ,it can clear the 3rd input value ,and the 1st,2nd button can also erase the value on 3rd entry.I want the 1st entry to 1st button and can erase data .What's wrong with my code .How should i modify it .Your help is very gratefully appreciated.
Here is my code:
from tkinter import *
root = Tk()
screenwidth = root.winfo_screenwidth()
screenheight = root.winfo_screenheight()
root.title('PN List Box')
root.geometry("500x300+%d+%d"%((screenwidth-400)/2,(screenheight-230)/2-100))
mycolor = '#%02x%02x%02x' % (101, 119, 141)
root.configure(bg=mycolor)
cv = Canvas(root,bg=mycolor)
dict1={}
dict2={}
list1=[]
label_rely=0
for a in range(3):
label_rely+=0.1
list1.append(StringVar())
dict1[a]=Entry(root,textvariable=list1[a]).place(relx=0.27, rely=0.33 + label_rely)
dict2[a]= Button(root,text='clear',command=lambda :list1[a].set('')).place(relx=0.86,rely=0.32+label_rely)
root.mainloop()
sys.exit()
This is a tricky little corner of Python. Remember that your lambda function isn't evaluated until the lambda is actually executed. By the time the lambda is executed, a has the value 2, for all three callbacks. What you need to do is "capture" the loop value, by passing it as a default parameter to the function:
dict2[a]= Button(root,text='clear',command=lambda a=a :list1[a].set('')).place(relx=0.86,rely=0.32+label_rely)
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 months ago.
I need to create multiple buttons with different names (each new name is equal to the name of the previous button + the iterating value at that moment.) Please help me out, here is my code.
buttons = [0]*len(gg.allStudents)
for j in range(len(gg.allStudents)):
buttons[j] = tk.Button(wind, text=gg.allStudents[j].name, height = 2, width = 20, command=lambda: plotMarks(j))
buttons[j].pack()
The looping conditions that I have used our correct. The only help I need is to find a way to store each new button with a new name into the 'buttons' list.
Your issue is not what you think it is. It can be easily solved by changing:
command=lambda: plotMarks(j)
to
command=lambda j=j: plotMarks(j).
The reason this works is because, in your version, you are sticking the variable j in all of your commands, and they will all use whatever the final value of j is. In the second version you are sticking the current value of j in your command.
To understand this better all we have to do is expand the lambdas.
def add2(n):
return n+2
#equivalent of your current version
j = 6
def currentLambdaEquivalent():
global j
print(add2(j))
currentLambdaEquivalent() #8
#equivalent of second version
def alternateLambdaEquivalent(j):
print(add2(j))
alternateLambdaEquivalent(2) #4
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 5 years ago.
I'm unsure where I'm going wrong with the below - I'm sure it's something basic but I'm still a bit unsure what the issue is. I'm trying to have a button change the range of the car when clicked, but it will only set the last one.
def draw:
car_column = 0
for car in boarding_cars:
tk.Label(transport_frame, text="{}".format(car.name)).grid(row=0, column=car_column)
tk.Button(transport_frame, text=car.range, command= lambda: change_range(car)).grid(row=1, column=car_column)
car_column += 1
def change_range(car):
print car.name
if car.range == "mid":
car.range = "close"
elif car.range == "close:
car.range = "mid"
I understand it's just setting everything as the last one in the list, but I'm unsure how to stop it doing that. Any help would be appreciated.
This is a common problem when people don't understand that lambda is late-binding. You need to use partial instead for this.
from functools import partial
tk.Button(transport_frame, text=car.range, command= partial(change_range, car)).grid(row=1, column=car_column)
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 years ago.
It's in tkinter. The last line adds x = len(FRAME_LIST) "Buttons" into the dropdown menu. The problem is they all reference to the same frame (the last in the FRAME_LIST). How to I make it that every Button references a diffrent frame from FRAME_LIST?
for F in FRAME_LIST:
frame = ChallengePage(mainframe,self, F)
self.frames[F] = frame
frame.grid(row = 0, column = 0, sticky = "nsew")
subMenu1.add_command(label = F.id, command = lambda: self.show_frames(F))
EDIT:
So to be more precise, when I come across this problem i thought ok, the problem is I need to declare a local variable, so I tried this:
A = F
subMenu1.add_command(label = F.id, command = lambda: self.show_frames(A))
But it didnt work even though the A is declared INSIDE the loop, and redeclared in every loop, it still yields the same result.
I came now across the link: https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result
where it shows the solution:
subMenu1.add_command(label = F.id, command = lambda A = F: self.show_frames(A))
Which somehow magically works, but I don't get why it is any diffrent from my local A.
This is a common issue when using lambda in loops. I usually use a partial instead:
from functools import partial
for F in FRAME_LIST:
frame = ChallengePage(mainframe, self, F)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
subMenu1.add_command(label=F.id, command=partial(self.show_frames, F))
Because: closures.
To summarize: the lambda in your loop will remember the name of the variable (F), but not the value it points to on each iteration of the loop. By the time you are clicking the buttons and triggering the command, the name F points to the last value in the iteration of your for loop, and so that is what will be passed to your lambda. Partials on the other hand are aware of the object that F points to at each iteration, and keeps track of it so that self.show_frames() is called with the appropriate argument.
This question already has answers here:
Local variables in nested functions
(4 answers)
Closed 9 years ago.
I am having some trouble with the button widgets.
Here's my code. Basically, what I would like to do is to print "0,0" when I press the first button, print "0,1" when I press the second button and so on. But what happens is that it always prints "1,1", which are last values in my for loops. How could I fix that?
Thanks a lot for helping
from tkinter import *
def show(x,y):
print(x,y)
root = Tk()
a = 0
for i in range(2):
for j in range(2):
Button(root, text=a, command=lambda:show(i,j)).grid(column=j, row=i)
a += 1
root.mainloop()
It is because of the closure property of Python. To fix this, change
Button(root, text=a, command=lambda:show(i,j)).grid(column=j, row=i)
to
def inner(i=i, j=j):
Button(root, text=a, command=lambda:show(i,j)).grid(column=j, row=i)
inner()
We are just wrapping the values of i and j with a function. Since the function show is not executed immediately, it just accepts the values of the two variables i and j. So, all the buttons now, will have the same i and j and they will get the values which are at the end of the loop.
Now, by defining a new function, we are getting a default parameter of the same names i and j and we get the values of i and j at the particular moment. So, the current values will be retained.