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)
Related
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 6 months ago.
I've been struggling with this portion of my code for the past few hours, so I decided to ask on stackoverflow.
First off, here's my code:
def place_checkers_init(self):
for i,player_piece in enumerate(self.player_pieces):
tag = "p"+str(i)
square = self.frame.grid_slaves(player_piece.row, player_piece.col)[0]
piece = square.create_oval(10,10,90,90,fill=player_piece.color)
square.itemconfig(piece, tags = tag)
print(square.gettags(piece))
square.tag_bind(tag, '<1>', lambda event: self.player_move(tag))
To briefly explain the background, self.player_pieces is a list of CheckerPiece objects, which only serves to store information about row, column, color, etc. of a checker piece. Self.frame is a Frame object containing 36 Canvas objects stored as grid. What I'm trying to do on this block of code here is to create checker pieces (ovals) on these individual canvases (checkerboard tiles) using the row and column information stored in CheckerPiece object and bind each of these pieces to Button and execute the class method self.player_move.
So here's my problem: even though I assigned each of these checker pieces their own tags, for some reason all pieces end up with the tag of the last piece in the for loop. That is, when I try printing (on the terminal) the row and column of a checker piece I click on (in my gui) using this definition of self.player_move:
def player_move(self, tag):
index = int(tag[1])
print(self.player_pieces[index].row, self.player_pieces[index].col)
it only prints (4,5) no matter which piece I click, which is the last checker piece that was in the list self.player_pieces.
My guess is that something went wrong in the last parameter of my tag_bind (the lambda function), but I just don't know what to do anymore.
Could I get some help? Thank you!
The minimal formulation of your question can be written as:
for f in [lambda: print(n, end=' ') for n in range(5)]:
f()
Output:
4 4 4 4 4 # why do the lambdas only print the last value of 'n' ?
Now replace by:
for f in [lambda n=n: print(n, end=' ') for n in range(5)]:
f()
Output
0 1 2 3 4 # yeah!
This question already has answers here:
Creating functions (or lambdas) in a loop (or comprehension)
(6 answers)
How can I identify buttons, created in a loop?
(6 answers)
Closed 4 years ago.
I'm having a problem where I create a for loop of buttons and I want each one of other to have different column, row and text.
I noticed only the text and the column (because i used lambda on it) are actually the updated values given to the function.
(btw the row and column are for something else, not the button - in a different function).
This is my code, I can't seem to understand how to get the most updated value for a button since all of my buttons are getting the values of the first button except for the text name and their column
for order in self.orders:
if counter2 >= 2 and counter2 % 2 == 0:
x += 1
print x, counter2
bt = Button(window, command=lambda i=counter2: self.display_order(self.orders[order], window, x, i))
bt.configure(text='order ' + str(counter2+1), fg='black', bg='steel blue', width=20)
bt.grid(sticky='W', row=0, column=counter2, columnspan=1) # increase row number for every button
counter2 += 1
x has also a problem with late binding. You've done it right for counter2. Use the following change (by adding x=x as additional lambda argument):
bt = Button(window, command=lambda i=counter2, x=x: self.display_order(self.orders[order], window, x, i))
I am writing a simple 3d simulator with tkinter and I am encountering a problem with passing arguments through a function bound to an event.
my function is
def left(event):
cam1 = 0
cam2 = 0
def render( cam1, cam2):
w.delete("all")
cam1 = cam1 + 5
cam2 = cam2 + 2
lol = 0
w.create_rectangle(10+cam1,10+cam1,190+cam1,190+cam1)
w.create_rectangle(40+cam2,40+cam2,160+cam2,160+cam2)
w.create_line(10+cam1,10+cam1,40+cam2,40+cam2)
w.create_line(40+cam2,160+cam2,10+cam1,190+cam1)
w.create_line(160+cam2,160+cam2,190+cam1,190+cam1)
w.create_line(190+cam1,10+cam1,160+cam2,40+cam2)
w.after(10,render(cam1,cam2))
i want to be able to have cam1 and cam2 not be 0 every time i call the function but it wont let me pass in more arguments with the event, i use
w.bind("<Right>", right)
to bind it
The after call is incorrect. You must give it a reference to a function. Additional arguments can be given. In your case it would be:
w.after910, render, cam1, cam2)
As for the binding, the most common solution is to use lambda or functools.partial. For example:
w.bind("<Right>", lambda event, arg1="something", arg2="something else": right(arg1, arg2))
Variations of this question have been asked dozens of times on stackoverflow. Try searching this site for [tkinter] bind argument. Your question should probably be marked as a duplicate, but it's hard to know which question is the best original question.
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.