在 Tkinter 中动态创建菜单。(lambda 表达式?)
题
我有一个菜单按钮,单击该按钮时应显示一个包含特定字符串序列的菜单。确切地说,该序列中的字符串是什么,直到运行时我们才知道,因此弹出的菜单必须在此时生成。这是我所拥有的:
class para_frame(Frame):
def __init__(self, para=None, *args, **kwargs):
# ...
# menu button for adding tags that already exist in other para's
self.add_tag_mb = Menubutton(self, text='Add tags...')
# this menu needs to re-create itself every time it's clicked
self.add_tag_menu = Menu(self.add_tag_mb,
tearoff=0,
postcommand = self.build_add_tag_menu)
self.add_tag_mb['menu'] = self.add_tag_menu
# ...
def build_add_tag_menu(self):
self.add_tag_menu.delete(0, END) # clear whatever was in the menu before
all_tags = self.get_article().all_tags()
# we don't want the menu to include tags that already in this para
menu_tags = [tag for tag in all_tags if tag not in self.para.tags]
if menu_tags:
for tag in menu_tags:
def new_command():
self.add_tag(tag)
self.add_tag_menu.add_command(label = tag,
command = new_command)
else:
self.add_tag_menu.add_command(label = "<No tags>")
重要的部分是“if menu_tags:”下的内容——假设menu_tags是列表['stack', 'over', 'flow']。那么我想做的实际上是:
self.add_tag_menu.add_command(label = 'stack', command = add_tag_stack)
self.add_tag_menu.add_command(label = 'over', command = add_tag_over)
self.add_tag_menu.add_command(label = 'flow', command = add_tag_flow)
其中 add_tag_stack() 定义为:
def add_tag_stack():
self.add_tag('stack')
等等。
问题是,变量“tag”采用值“stack”,然后采用值“over”等,并且直到调用 new_command 才对其求值,此时变量“tag”只是“流动'。因此,无论用户点击什么,添加的标签始终是菜单上的最后一个。
我最初使用的是 lambda,我想也许像上面那样显式定义该函数可能会更好。无论哪种方式都会出现问题。我尝试过使用变量“标签”的副本(使用“current_tag = tag”或使用复制模块),但这并不能解决问题。我不知道为什么。
我的思绪开始徘徊在“评估”之类的事情上,但我希望有人能想出一种聪明的方法,不涉及如此可怕的事情。
非常感谢!
(如果相关,Tkinter.__version__ 返回 '$Revision:67083 $',我在 Windows XP 上使用 Python 2.6.1。)
解决方案
首先,你的问题与Tkinter无关;最好将其简化为一段简单的代码来演示您的问题,这样您就可以更轻松地进行试验。这是我尝试过的您正在做的事情的简化版本。我用字典代替菜单,以便轻松编写小测试用例。
items = ["stack", "over", "flow"]
map = { }
for item in items:
def new_command():
print(item)
map[item] = new_command
map["stack"]()
map["over"]()
map["flow"]()
现在,当我们执行此操作时,正如您所说,我们得到:
flow
flow
flow
这里的问题是 Python 的范围概念。特别是, for
声明没有引入新的范围级别,也没有引入新的绑定 item
;所以它正在更新相同的 item
每次循环时都有变量,并且所有的 new_command()
函数指的是同一个项目。
您需要做的是为每个范围引入新的范围级别和新的绑定 item
s。最简单的方法是将其包装在新的函数定义中:
for item in items:
def item_command(name):
def new_command():
print(name)
return new_command
map[item] = item_command(item)
现在,如果将其代入前面的程序中,您将得到所需的结果:
stack
over
flow
其他提示
这样的东西是Tkinter的一个相当普遍的问题,我想。
尝试此(在适当的点):
def new_command(tag=tag):
self.add_tag(tag)
我有一个类似的错误。只有在列表中的最后一项显示。通过设置固定
command=lambda x=i: f(x)
请注意x=i
后lambda
。这种分配使得局部变量i
去到您的权利f(x)
的command
功能。希望,这个简单的例子将有助于:
# Using lambda keyword to create a dynamic menu.
import tkinter as tk
def f(x):
print(x)
root = tk.Tk()
menubar = tk.Menu(root)
root.configure(menu=menubar)
menu = tk.Menu(menubar, tearoff=False)
l = ['one', 'two', 'three']
for i in l:
menu.add_command(label=i, command=lambda x=i: f(x))
menubar.add_cascade(label='File', menu=menu)
root.mainloop()