在Tkinter中动态创建菜单。 (lambda表达式?) [英] Dynamically creating a menu in Tkinter. (lambda expressions?)

查看:164
本文介绍了在Tkinter中动态创建菜单。 (lambda表达式?)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个menubutton,当点击应该显示一个菜单包含一个特定的字符串序列。正确的是什么字符串在该序列中,我们不知道,直到运行时,所以弹出的菜单必须在那一刻生成。这里是我有:

  class para_frame(Frame):
def __init __(self,para = None,* args ,** kwargs):
#...

#菜单按钮用于添加已经存在于其他段落的标签
self.add_tag_mb = Menubutton(self,text ='Add标签...')

#此菜单需要在每次点击时重新创建
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)#清除任何在菜单中的

all_tags = self.get_article()。all_tags()
#我们不希望菜单包含已经在这段中的标签
menu_tags = [如果标签不在self.para.tags中,标签在all_tags中的标签]

如果menu_tags:
用于menu_tags中的标记:
def new_command():
self.add_tag(tag)

self.add_tag_menu.add_command(label = tag,
命令= new_command)
else:
self.add_tag_menu.add_command(label =<无标签>)

$ b b

重要的部分是if menu_tags:下面的内容 - 假设menu_tags是list ['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'只是'flow'。因此,添加的标签总是菜单上的最后一个标签,无论用户点击什么。



我最初使用lambda,我想可能明确定义上面的函数可能会更好。无论哪种方式出现问题。我试过使用变量'tag'(使用current_tag =标签或使用复制模块)的副本,但不能解决它。我不知道为什么。



我的心开始向eval这样的东西走去,但我希望有人能想出一种聪明的方式涉及这些可怕的事情。



非常感谢!



(如果相关,Tkinter .__ version__修订:67083 $',我在Windows XP上使用Python 2.6.1。)

解决方案

与Tkinter没有任何关系;如果你把它减少到一个简单的代码展示你的问题,所以你可以更容易地实验它是最好的。这里是一个简单的版本,你正在做什么,我实验。

  items = [...] bbbbbbbbbbbbb 

map [item] = new_command

map [stack]()
map [over]()
map [flow ]()

现在,当我们执行这个命令时, >

  flow 
flow
flow

这里的问题是Python的范围概念。特别地, for 语句不会引入新的级别的范围,也不会为<$​​ c $ c> item 引入新的绑定;因此每次通过循环更新相同的变量,所有 new_command()函数是引用



您需要做的是为每个项引入一个新的范围, / code> s。最简单的方法是将它包装在一个新的函数定义中:

 用于项目中的项目:
def item_command(name):
def new_command():
print(name)
return new_command

map [item] = item_command(item)

现在,如果你把它替换成前面的程序,你会得到所需的结果:

  stack 
over
flow


I have a menubutton, which when clicked should display a menu containing a specific sequence of strings. Exactly what strings are in that sequence, we do not know until runtime, so the menu that pops up must be generated at that moment. Here's what I have:

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

The important part is the stuff under "if menu_tags:" -- Suppose menu_tags is the list ['stack', 'over', 'flow']. Then what I want to do is effectively this:

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)

where add_tag_stack() is defined as:

def add_tag_stack():
    self.add_tag('stack')

and so on.

The problem is, the variable 'tag' takes on the value 'stack' and then the value 'over' and so on, and it doesn't get evaluated until new_command is called, at which point the variable 'tag' is just 'flow'. So the tag that gets added is always the last one on the menu, no matter what the user clicks on.

I was originally using a lambda, and I thought maybe explicitly defining the function as above might work better. Either way the problem occurs. I've tried using a copy of the variable 'tag' (either with "current_tag = tag" or using the copy module) but that doesn't solve it. I'm not sure why.

My mind is starting to wander towards things like "eval" but I'm hoping someone can think of a clever way that doesn't involve such horrible things.

Much thanks!

(In case it's relevant, Tkinter.__version__ returns '$Revision: 67083 $' and I'm using Python 2.6.1 on Windows XP.)

解决方案

First of all, your problem doesn't have anything to do with Tkinter; it's best if you reduce it down to a simple piece of code demonstrating your problem, so you can experiment with it more easily. Here's a simplified version of what you're doing that I experimented with. I'm substituting a dict in place of the menu, to make it easy to write a small test case.

items = ["stack", "over", "flow"]
map = { }

for item in items:
    def new_command():
        print(item)

    map[item] = new_command

map["stack"]()
map["over"]()
map["flow"]()

Now, when we execute this, as you said, we get:

flow
flow
flow

The issue here is Python's notion of scope. In particular, the for statement does not introduce a new level of scope, nor a new binding for item; so it is updating the same item variable each time through the loop, and all of the new_command() functions are referring to that same item.

What you need to do is introduce a new level of scope, with a new binding, for each of the items. The easiest way to do that is to wrap it in a new function definition:

for item in items:
    def item_command(name):
        def new_command():
            print(name)
        return new_command

    map[item] = item_command(item)

Now, if you substitute that into the preceding program, you get the desired result:

stack
over
flow

这篇关于在Tkinter中动态创建菜单。 (lambda表达式?)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆