【问题标题】:Dynamically creating a menu in Tkinter. (lambda expressions?)在 Tkinter 中动态创建菜单。 (lambda 表达式?)
【发布时间】:2010-10-18 05:17:03
【问题描述】:

我有一个菜单按钮,单击它时应显示一个包含特定字符串序列的菜单。那个序列中到底是什么字符串,直到运行时我们才知道,所以弹出的菜单必须在那一刻生成。这是我所拥有的:

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,我想也许像上面那样明确定义函数可能会更好。无论哪种方式都会出现问题。我尝试使用变量“tag”的副本(使用“current_tag = tag”或使用复制模块),但这并不能解决问题。我不知道为什么。

我的思绪开始徘徊在“eval”之类的东西上,但我希望有人能想出一个不涉及如此可怕的事情的聪明方法。

非常感谢!

(如果相关,Tkinter.__version__ 返回 '$Revision: 67083 $' 并且我在 Windows XP 上使用 Python 2.6.1。)

【问题讨论】:

    标签: python lambda copy tkinter


    【解决方案1】:

    我遇到了类似的错误。仅显示列表中的最后一项。通过设置修复

    command=lambda x=i: f(x)

    注意lambda 后面的x=i。这个赋值使你的局部变量i 直接进入你的commandf(x) 函数。希望这个简单的例子会有所帮助:

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

    【讨论】:

      【解决方案2】:

      首先,您的问题与 Tkinter 无关;最好将其简化为一段简单的代码来演示您的问题,这样您就可以更轻松地进行试验。这是我尝试过的您正在做的事情的简化版本。我用 dict 代替菜单,以便于编写小测试用例。

      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()函数都引用同一个项目。

      您需要做的是为每个items 引入一个新级别的范围和一个新的绑定。最简单的方法是将其包装在一个新的函数定义中:

      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 的解决方案。有人说“不不不,你这样做的方式是特殊函数 Tkinter.somethingOrOther()”谢谢你的帮助!
      • 没问题!我只是想指出一个好的第一步是尝试隔离问题的一个小例子,看看它是语言问题还是 API 问题。
      【解决方案3】:

      我认为这种事情在 Tkinter 中很常见。

      试试这个(在适当的时候):

      def new_command(tag=tag):
          self.add_tag(tag)
      

      【讨论】:

      • 我不太清楚为什么会这样,但确实如此!虽然我喜欢另一个答案的详细解释,但我会使用这个,因为它有点短。谢谢!
      • 我认为它之所以有效,是因为函数参数列表中的 tag=tag 默认参数。这样做会在函数范围内创建名称“标签”,指向调用者范围内的值标签。如果我错了,请纠正我,我目前正在努力解决类似的范围界定问题。
      • 基本上,'for x in ..' 构造是 python 中少数几个名称 (x) 被反弹的地方之一。因此,您需要在适当的位置创建一个与 x 具有相同值的新名称。您可以使用在函数定义时评估的 'tag=tag' 执行此操作,而不是稍后在函数执行时。
      猜你喜欢
      • 1970-01-01
      • 2015-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多