إنشاء قائمة ديناميكيا في Tkinter. (تعبيرات لامدا؟)
سؤال
لدي Menubutton، والتي عند النقر عليها، يجب عرض قائمة تحتوي على تسلسل محدد من السلاسل. بالضبط السلاسل في هذا التسلسل، لا نعرف حتى وقت التشغيل، لذلك يجب إنشاء القائمة التي ينبثق في تلك اللحظة. إليك ما لدي:
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>")
الجزء المهم هو الاشياء تحت ثم ما أريد القيام به هو فعال:
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، عند نقطة المتغير "العلامة" هو " تدفق'. لذلك فإن العلامة التي تضاف هي دائما آخر واحد في القائمة، بغض النظر عن النقر فوق المستخدم.
كنت أصلا استخدام Lambda، وفكرت ربما بصرافة تحديد الوظيفة أعلاه قد تعمل بشكل أفضل. وفي كلتا الحالتين تحدث المشكلة. لقد حاولت استخدام نسخة من المتغير "العلامة" (إما مع "current_tag = علامة" أو استخدام وحدة النسخ) ولكن هذا لا يحلها. لست متأكدا لماذا.
بدأ رأيي في التجول نحو أشياء مثل "eval"، لكنني آمل أن يفكر شخص ما في طريقة ذكية لا تنطوي على مثل هذه الأشياء الرهيبة.
كثير الشكر!
(في حالة صلة، إرجاع النسخة Tkinter. __ Version__ Revision $: 67083 $ 'وأنا أستخدم Python 2.6.1 على نظام التشغيل Windows XP.)
المحلول
بادئ ذي بدء، مشكلتك ليس لها أي علاقة مع 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
القضية هنا هي فكرة بايثون للنطاق. على وجه الخصوص، for
بيان لا يعرض مستوى جديد من النطاق، ولا ملزمة جديدة ل item
; ؛ لذلك هو تحديث نفسه item
متغير في كل مرة من خلال حلقة، وجميع new_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
نصائح أخرى
هذا النوع من الأشياء مشكلة شائعة تماما في توكينتير، كما أعتقد.
جرب هذا (في النقطة المناسبة):
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()