creating multiple buttons with different names in tkinter [duplicate] - python

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

Related

tkinter: tag_bind in for-loop only passes the last tag as a parameter in the lambda event function [duplicate]

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!

Tkinter button not getting updated value [duplicate]

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))

Python: lambdas in a for loop [duplicate]

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)

How do I make buttons with a for-loop [duplicate]

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.

Python 3.3 : I do not understand Buttons and their lambdas in Tkinter() [duplicate]

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.

Categories

Resources