6

クリックすると、特定の文字列シーケンスを含むメニューが表示されるメニューボタンがあります。そのシーケンスに正確にどのような文字列があるかは実行時までわからないため、ポップアップするメニューはその時点で生成する必要があります。ここに私が持っているものがあります:

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' のコピーを使用してみましたが (「current_tag = tag」または copy モジュールを使用)、解決しません。理由はわかりません。

私の心は「eval」のようなものに向かってさまよい始めていますが、誰かがそのような恐ろしいことを伴わない賢い方法を考えてくれることを願っています.

どうもありがとう!

(関連がある場合、Tkinter.__version__ は '$Revision: 67083 $' を返します。私は Windows XP で Python 2.6.1 を使用しています。)

4

3 に答える 3

7

まず第一に、あなたの問題は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同じ変数を更新し、すべての関数が同じアイテムを参照しています。itemnew_command()

あなたがする必要があるのは、それぞれに新しいバインディングを備えた新しいレベルのスコープを導入することです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
于 2009-04-08T03:38:40.070 に答える
2

そのようなことは、Tkinter ではよくある問題だと思います。

これを試してください(適切な時点で):

def new_command(tag=tag):
    self.add_tag(tag)
于 2009-04-08T03:21:41.563 に答える