提问人:Marcel 提问时间:6/3/2012 最后编辑:Karl KnechtelMarcel 更新时间:8/16/2022 访问量:59597
tkinter 在 for 循环中创建按钮传递命令参数
tkinter creating buttons in for loop passing command arguments
问:
我正在尝试在循环中的tkinter中创建按钮。并且,在每个循环中,将计数值作为命令值中的参数传出。因此,当从值调用函数时,我可以判断按下了哪个按钮并采取相应的行动。for
i
command
问题是,假设长度为 3,它将创建 3 个带有标题的按钮,但是当按下任何按钮时,打印值始终是 ,最后一次迭代。因此,按钮似乎是作为单独的实体制作的,但命令参数中的值似乎都是相同的。代码如下:Game 1Game 32
i
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)
有没有办法让每次迭代都获得当前值以坚持使用该特定按钮?i
这个问题可以看作是在循环中创建函数的特例。还有 What does lambda function closures capture?,以获得更技术性的概述。
另请参阅如何在 Tkinter 中将参数传递给 Button 命令?,了解将参数传递给 Button 回调的一般问题。
答:
这就是闭包在 python 中的工作方式。我自己也遇到过这个问题。
为此,可以使用 functools.partial
。
for i in range(3):
self.button.append(Button(self, text='Game '+str(i+1), command=partial(self.open_this, i)))
评论
self.button.append(Button(self, text='Game ' + str(i + 1), command=lambda x=i: self.open_this(x)))
functools.partial
将 lambda 更改为 .lambda i=i: self.open_this(i)
这可能看起来很神奇,但这就是正在发生的事情。当您使用该 lambda 定义函数时,open_this 调用在定义函数时不会获取变量 i 的值。取而代之的是,它做了一个闭包,这有点像给自己一个注释,说“我应该在调用我的时候寻找变量 i 的值”。当然,函数是在循环结束后调用的,所以那时 i 将始终等于循环中的最后一个值。
使用技巧可以使函数在定义 lambda 时存储 i 的当前值,而不是等待稍后查找 i 的值。i=i
评论
lambda x=x, y=y: self.open_this(x, y)
def foo(x, y=3)
只需将按钮范围附加到 lambda 函数中,如下所示:
btn["command"] = lambda btn=btn: click(btn)
其中 是传入按钮本身的函数。
这将创建从按钮到函数本身的绑定范围。click(btn)
特征:
- 自定义网格大小
- 响应式调整大小
- 切换活动状态
#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()
这是因为名称的值已更改,并且未被 捕获。(你可以通过在循环之后添加并查看会发生什么来尝试该理论。i
lambda:
i = 1234
您需要编写一个函数以将其包装为本地名称,然后在该函数中返回一个捕获 .i
i
def make_button_click_command(i):
return lambda: button_click(i)
# ...
btn = Button(..., command=make_button_click_command(i))
另一个选项是 ,它有效地执行相同的操作:functools.partial
command=functools.partial(button_click, i)
总而言之,您还可以通过只使用获取从 0 到 10 的数字并在一个函数调用中获取行和列来简化事情:range
divmod
from tkinter import Tk, Button
def button_click(i):
print(i)
def make_button_click_command(i):
return lambda: button_click(i)
root = Tk()
for i in range(10):
value = (i + 1) % 10
row, col = divmod(i, 3)
btn = Button(root, text=value, padx=40, pady=20, command=make_button_click_command(value))
btn.grid(row=row + 1, column=col)
root.mainloop()
评论