代码之家  ›  专栏  ›  技术社区  ›  Tyler

在Tkinter中动态创建菜单。(lambda表达式?)

  •  6
  • Tyler  · 技术社区  · 15 年前

    我有一个menubutton,单击它时应该显示一个包含特定字符串序列的菜单。确切地说,这个序列中的字符串是什么,直到运行时我们才知道,所以此时必须生成弹出的菜单。以下是我的资料:

    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_命令,此时变量“tag”只是“flow”,才会对其进行计算。所以添加的标签总是菜单上最后一个,不管用户点击什么。

    我最初使用的是lambda,我认为将函数显式定义为上面的函数可能会更好。不管怎样,问题都会发生。我尝试过使用变量“tag”的副本(要么使用“current_tag=tag”,要么使用复制模块),但这并不能解决问题。我不知道为什么。

    我的思想开始向“逃避”这样的事情徘徊,但我希望有人能想出一个聪明的方法,不涉及如此可怕的事情。

    非常感谢!

    (如果相关,tkinter.uuuuversion会返回“$revision:67083$”,我在WindowsXP上使用的是python 2.6.1。)

    3 回复  |  直到 7 年前
        1
  •  6
  •   Brian Campbell Dennis Williamson    15 年前

    首先,您的问题与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 ;所以它正在更新相同的 项目 每次通过循环的变量,以及 new_command() 函数引用的是同一项。

    您需要做的是为每个 项目 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
    
        2
  •  2
  •   John Fouhy    15 年前

    我认为,这种事情在特金特是很常见的问题。

    尝试此操作(在适当的位置):

    def new_command(tag=tag):
        self.add_tag(tag)
    
        3
  •  0
  •   FooBar167    7 年前

    我也有类似的错误。仅显示列表中的最后一项。通过设置固定

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