2009-04-08 11 views
6

私はクリックすると特定の文字列を含むメニューを表示する必要があるmenubuttonを持っています。そのシーケンス内の文字列はまさに実行時までわからないので、ポップアップするメニューはその時点で生成されなければなりません。仮定menu_tagsがリストである[「スタック」、「以上」、「流れ」] - ここに私が持っているものです::Tkinterのメニューを動的に作成します。 (ラムダ式?)

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」の下でのものです。そして、私がやりたいことを効果的にこれです:ように

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

と:

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は()として定義されます。

変数 'tag'は 'stack'の値をとり、 'over'などの値をとり、new_commandが呼び出されるまで評価されません。その時点で変数 'tag 'は単に「流れ」です。したがって、追加されるタグは、ユーザーが何をクリックしていても常にメニューの最後のタグです。

私はもともとラムダを使用していましたが、上記のように関数を明示的に定義するほうが効果的かもしれません。どちらの方法でも問題が発生します。私は変数 "タグ"( "current_tag =タグ"またはコピーモジュールを使用して)のコピーを使用しようとしましたが、それは解決しません。なぜ私は分からない。

私の心は「eval」のようなものに向かってさまよっていますが、私は誰かがそのような恐ろしいものを含まない賢い方法を考えることができると考えています。

多くの感謝!

(ケースでは、Tkinterの.__ version__戻り、関連するのです。 '$リビジョン:67083 $' と私はWindows XP上のPython 2.6.1を使用しています)

答えて

6

まず、あなたの問題は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を更新しており、すべての関数がその同じ項目を参照しています。

あなたがする必要があるのは、itemのそれぞれに対して、新しいバインディングを使用して、新しいレベルのスコープを導入することです。それを行うための最も簡単な方法は、新しい関数定義でそれをラップすることです:

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

    map[item] = item_command(item) 

さて、あなたは前のプログラムにそれを置き換えるならば、あなたが希望する結果を得る:

stack 
over 
flow 
+0

Tkinter特有の解決策があったかもしれないと思いました。誰かが「いいえ、いいえ、あなたがやる方法は特別な関数Tkinter.somethingOrOther()です」と言っています。助けてくれてありがとう! – MatrixFrog

+0

問題ありません!私はちょうどいい最初のステップは、問題の小さな例を切り離して、それが言語の問題かAPIの問題かを見極​​めることです。 –

2

そういうことはTkinterの中には非常に共通の問題です、 おもう。

は、(適切な時点で)これを試してみてください:すべての

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

私はこれがなぜ機能するのかよくわかりませんが、それはありますか?私は他の答えの詳細な説明が気に入っていますが、これはもう少し短いので、私はこれを使用します。ありがとう! – MatrixFrog

+0

私は関数の引数リストのtag = tagのデフォルト引数のために動作すると思います。これを実行すると、関数スコープ内に「タグ」という名前が作成され、呼び出し側のスコープ内のvalueタグが指し示されます。 私が間違っている場合は私を修正してください、私は現在同様のスコープの質問で取り組んでいます。 –

+0

基本的に 'for x in ..'構造は、名前(x)がリバウンドする数少ない場所です。したがって、適切なポイントでxと同じ値を持つ新しい名前を作成する必要があります。これは、関数定義時に評価される 'tag = tag'で行いますが、関数実行時には評価されません。 –

0

私が持っていたが同様のエラー。リストの最後の項目のみが表示されました。

command=lambda x=i: f(x)

を設定することにより、固定lambdax=iに注意してください。この割り当てによって、ローカル変数iがの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() 
関連する問題