إنشاء قائمة ديناميكيا في Tkinter. (تعبيرات لامدا؟)

StackOverflow https://stackoverflow.com/questions/728356

  •  05-09-2019
  •  | 
  •  

سؤال

لدي 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()
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top