2009-04-08 76 views
6

我有一個menubutton,點擊時會顯示一個包含特定字符串序列的菜單。到底什麼是字符串,我們直到運行時才知道,所以彈出的菜單必須在那一刻生成。下面是我有:在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>") 

的重要組成部分,是下的東西,「如果menu_tags:」 - 假設menu_tags是列表[「堆」,「過」,「流」。那麼我想這樣做有效的是這樣的:

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__返回 '$修訂:67083 $',我使用Python 2.6.1在Windows XP上。)

回答

6

首先,你的問題沒有什麼關係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 
2

這種事情是相當Tkinter的一個常見問題, 我認爲。

試試這個(在適當的點):

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

我不知道爲什麼這個工程,但它呢!儘管我喜歡其他答案的詳細解釋,但我會使用這個,因爲它稍微短一些。謝謝! – MatrixFrog 2009-04-08 06:49:03

+0

我認爲它的工作原因是函數參數列表中的tag = tag默認參數。這樣做會在函數範圍內創建名稱'tag',指向調用者範圍內的值標記。 如果我錯了,請糾正我,我目前正在處理類似的範圍問題。 – 2009-05-19 06:46:38

0

我有一個類似的錯誤。只顯示列表中的最後一項。通過lambda後設置

command=lambda x=i: f(x)

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