tkinter 在 for 循环中创建按钮传递命令参数

tkinter creating buttons in for loop passing command arguments

提问人:Marcel 提问时间:6/3/2012 最后编辑:Karl KnechtelMarcel 更新时间:8/16/2022 访问量:59597

问:

我正在尝试在循环中的tkinter中创建按钮。并且,在每个循环中,将计数值作为命令值中的参数传出。因此,当从值调用函数时,我可以判断按下了哪个按钮并采取相应的行动。foricommand

问题是,假设长度为 3,它将创建 3 个带有标题的按钮,但是当按下任何按钮时,打印值始终是 ,最后一次迭代。因此,按钮似乎是作为单独的实体制作的,但命令参数中的值似乎都是相同的。代码如下:Game 1Game 32i

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 回调的一般问题。

蟒蛇 tkinter

评论

0赞 Marcel 6/3/2012
非常感谢你们俩 LukaD 和 BrenBarn,我已经为此奋斗了好几天了,信不信由你。两种方式都完美无缺。我现在选择了 i=i 修复程序,但我肯定会阅读 functools。我很欣赏这两个答案。
0赞 lukad 6/3/2012
如果BrenBarns解决方案适合您,那么您应该将其标记为您接受的答案。
0赞 Delrius Euphoria 2/13/2022
@martineau 你认为重复的问题回答了这个问题吗?
1赞 martineau 2/13/2022
@Delrius:哎呀,不,我不小心把它标记为骗子。感谢您引起我的注意。
0赞 PM 2Ring 4/30/2022
另请参阅 stackoverflow.com/q/19693782/4014959

答:

12赞 lukad 6/3/2012 #1

这就是闭包在 python 中的工作方式。我自己也遇到过这个问题。 为此,可以使用 functools.partial

for i in range(3):
    self.button.append(Button(self, text='Game '+str(i+1), command=partial(self.open_this, i)))

评论

0赞 Lucem 3/29/2020
看起来很容易,如果您使用的是虚拟环境,您可能会遇到问题,请参阅第一个:self.button.append(Button(self, text='Game ' + str(i + 1), command=lambda x=i: self.open_this(x)))
2赞 Karl Knechtel 5/11/2022
@Lucem“如果你使用的是虚拟环境,你可能会遇到问题” 没有充分的理由,因为这是标准库的一部分,并且已经存在了很长时间。如果您遇到困难,那么您应该尝试诊断问题并提出自己的问题。functools.partial
158赞 BrenBarn 6/3/2012 #2

将 lambda 更改为 .lambda i=i: self.open_this(i)

这可能看起来很神奇,但这就是正在发生的事情。当您使用该 lambda 定义函数时,open_this 调用在定义函数时不会获取变量 i 的值。取而代之的是,它做了一个闭包,这有点像给自己一个注释,说“我应该在调用我的时候寻找变量 i 的值”。当然,函数是在循环结束后调用的,所以那时 i 将始终等于循环中的最后一个值。

使用技巧可以使函数在定义 lambda 时存储 i 的当前值,而不是等待稍后查找 i 的值。i=i

评论

2赞 Amen 10/15/2014
如果我们想将两个参数传递给像 open_this 这样的函数怎么办?
13赞 BrenBarn 10/15/2014
@Amen:这取决于你希望这些论点是什么。如果两者都来自某个外部循环,并且您想以上面所示的方式“冻结”它们,则只需执行 .lambda x=x, y=y: self.open_this(x, y)
4赞 Battleroid 3/2/2016
这是精彩、直截了当和很好的解释。这应该是答案。
1赞 velpandian 3/4/2018
@BrenBarn 刚才我遇到了同样的情况,真是太神奇了。我真的很奇怪你们是怎么遇到这种技术的。非常感谢。
1赞 BrenBarn 6/29/2020
@RufusVS:这只是默认的参数语法。这和你做的时候是一样的.只是这里的函数参数名称与封闭作用域中的变量名称相同。def foo(x, y=3)
3赞 Joel 1/29/2020 #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()

enter image description here enter image description here

enter image description here

1赞 AKX 8/23/2022 #4

这是因为名称的值已更改,并且未被 捕获。(你可以通过在循环之后添加并查看会发生什么来尝试该理论。ilambda:i = 1234

您需要编写一个函数以将其包装为本地名称,然后在该函数中返回一个捕获 .ii

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 的数字并在一个函数调用中获取行和列来简化事情:rangedivmod

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