動的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がリストである[「スタック」、「以上」、「流れ」] - :重要な部分は「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')
のように。
問題は、変数「タグ」の値「スタック」を取るされ、「オーバー」を値というように、そしてnew_commandが呼び出されるまで、それは評価されません、変数を、その時点で「タグ」単に「流れ」です。だから、追加されるタグには関係なく、ユーザーがクリックしたものを、常にメニューの最後の1ではありません。
私はもともと、ラムダを使用していた、と私は多分、明示的に上記のような関数を定義することは良い仕事かもしれないと思いました。どちらの方法でも問題が発生します。私は、変数「タグ」のコピーを使用してみました(「current_tag =タグ」を持つまたはコピーモジュールを使用してのいずれか)それはそれを解決しません。私はよく分からない理由ます。
私の心は「evalの」のようなものに向けてさまよい始めているが、私は、誰かがこのような恐ろしいことを必要としない巧妙な方法を考えることができます願っています。
ありがとう!
(ケースでは、Tkinterの.__ version__戻り、関連するのです。 '$リビジョン:67083 $' と私はWindows XP上のPython 2.6.1を使用しています)。
解決
まず、あなたの問題は、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
他のヒント
そういうことはTkinterのではかなり一般的な問題であり、私は考えてます。
(適切なポイントで)これを試してください:
def new_command(tag=tag):
self.add_tag(tag)
私は、同様のエラーが発生しました。唯一のリストの最後の項目が示されました。
を設定することにより、固定 command=lambda x=i: f(x)
x=i
後lambda
に注意してください。この割り当ては、ローカル変数i
は、右あなたのf(x)
の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()