'tkinter creating buttons in for loop passing command arguments
I am trying to create buttons in tkinter within a for
loop. And with each loop pass the i
count value out as an argument in the command value. So when the function is called from the command
value I can tell which button was pressed and act accordingly.
The problem is, say the length is 3, it will create 3 buttons with titles Game 1 through Game 3 but when any of the buttons are pressed the printed value is always 2
, the last iteration. So it appears the buttons are being made as separate entities, but the i
value in the command arguments seem to be all the same. Here is the code:
def createGameURLs(self):
self.button = []
for i in range(3):
self.button.append(Button(self, text='Game '+str(i+1),
command=lambda: self.open_this(i)))
self.button[i].grid(column=4, row=i+1, sticky=W)
def open_this(self, myNum):
print(myNum)
Is there a way to get the current i
value, each iteration, to stick with that particular button?
Solution 1:[1]
Change your lambda to lambda i=i: self.open_this(i)
.
This may look magical, but here's what's happening. When you use that lambda to define your function, the open_this call doesn't get the value of the variable i at the time you define the function. Instead, it makes a closure, which is sort of like a note to itself saying "I should look for what the value of the variable i is at the time that I am called". Of course, the function is called after the loop is over, so at that time i will always be equal to the last value from the loop.
Using the i=i
trick causes your function to store the current value of i at the time your lambda is defined, instead of waiting to look up the value of i later.
Solution 2:[2]
This is how closures work in python. I ran into this problem myself once.
You could use functools.partial
for this.
for i in range(3):
self.button.append(Button(self, text='Game '+str(i+1), command=partial(self.open_this, i)))
Solution 3:[3]
Simply attach your buttons scope within a lambda function like this:
btn["command"] = lambda btn=btn: click(btn)
where click(btn)
is the function that passes in the button itself.
This will create a binding scope from the button to the function itself.
Features:
- Customize gridsize
- Responsive resizing
- Toggle active state
#Python2
#from Tkinter import *
#import Tkinter as tkinter
#Python3
from tkinter import *
import tkinter
root = Tk()
frame=Frame(root)
Grid.rowconfigure(root, 0, weight=1)
Grid.columnconfigure(root, 0, weight=1)
frame.grid(row=0, column=0, sticky=N+S+E+W)
grid=Frame(frame)
grid.grid(sticky=N+S+E+W, column=0, row=7, columnspan=2)
Grid.rowconfigure(frame, 7, weight=1)
Grid.columnconfigure(frame, 0, weight=1)
active="red"
default_color="white"
def main(height=5,width=5):
for x in range(width):
for y in range(height):
btn = tkinter.Button(frame, bg=default_color)
btn.grid(column=x, row=y, sticky=N+S+E+W)
btn["command"] = lambda btn=btn: click(btn)
for x in range(width):
Grid.columnconfigure(frame, x, weight=1)
for y in range(height):
Grid.rowconfigure(frame, y, weight=1)
return frame
def click(button):
if(button["bg"] == active):
button["bg"] = default_color
else:
button["bg"] = active
w= main(10,10)
tkinter.mainloop()
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | BrenBarn |
Solution 2 | |
Solution 3 |