Python 如何智能地将干式原理应用于tkinter OptionMenu?

Python 如何智能地将干式原理应用于tkinter OptionMenu?,python,tkinter,Python,Tkinter,我有一个简单的tkinter应用程序,目前运行良好,但代码编写得不好。我的主要问题是,似乎每个OptionMenu都需要有自己的tkvar和testFunc,才能让应用程序按我希望的方式运行。我似乎无法在命令部分调用其他变量,这就是为什么我很难合并此代码的原因 该应用程序的目的是允许用户选择动物的顺序,并立即显示该顺序。希望有人能为我点亮这盏灯,让这段代码更加枯燥、更加智能 import tkinter as tk from tkinter import ttk class SampleApp

我有一个简单的tkinter应用程序,目前运行良好,但代码编写得不好。我的主要问题是,似乎每个OptionMenu都需要有自己的tkvar和testFunc,才能让应用程序按我希望的方式运行。我似乎无法在命令部分调用其他变量,这就是为什么我很难合并此代码的原因

该应用程序的目的是允许用户选择动物的顺序,并立即显示该顺序。希望有人能为我点亮这盏灯,让这段代码更加枯燥、更加智能

import tkinter as tk
from tkinter import ttk

class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.wm_title("Choose Multiple Animals")
        self._frame = None

class AnimalPage(ttk.Frame):
    def __init__(self, master, controller):
        tk.Frame.__init__(self, master)
        self.master = master
        self.config(relief='sunken', borderwidth=2)
        self.pack(fill = "both", expand = False)
        self.grid_rowconfigure(0, weight = 1)
        self.grid_columnconfigure(0, weight = 1)

        self.animalList = ['Cat', 'Dog', 'Bear']
        self.choices = ['None', 'Animal1', 'Animal2', 'Animal3']
        self.tkvar1 = tk.StringVar(master)
        self.tkvar1.set('None')
        self.tkvar2 = tk.StringVar(master)
        self.tkvar2.set('None')
        self.tkvar3 = tk.StringVar(master)
        self.tkvar3.set('None')
        self.tkvar4 = tk.StringVar()

        self.textLabel1 = ttk.Label(self, text=self.animalList[0])
        self.textLabel1.grid(column=0, row = 5, sticky = (tk.W), padx=5, pady=5)
        self.popupMenu1 = ttk.OptionMenu(self, self.tkvar1, *self.choices, command=self.testFunc1)
        self.popupMenu1.grid(column=1, row = 5, sticky = (tk.W, tk.E), padx=5, pady=5)
        self.textLabel2 = ttk.Label(self, text=self.animalList[1])
        self.textLabel2.grid(column=0, row = 6, sticky = (tk.W), padx=5, pady=5)
        self.popupMenu2 = ttk.OptionMenu(self, self.tkvar2, *self.choices, command=self.testFunc2)
        self.popupMenu2.grid(column=1, row = 6, sticky = (tk.W, tk.E), padx=5, pady=5)
        self.textLabel3 = ttk.Label(self, text=self.animalList[2])
        self.textLabel3.grid(column=0, row = 7, sticky = (tk.W), padx=5, pady=5)
        self.popupMenu3 = ttk.OptionMenu(self, self.tkvar3, *self.choices, command=self.testFunc3)
        self.popupMenu3.grid(column=1, row = 7, sticky = (tk.W, tk.E), padx=5, pady=5)
        self.chosenAnimals = {}

        self.textLabel4 = ttk.Label(self, text=self.tkvar4.get())
        self.textLabel4.grid(column=0, row = 8, sticky = (tk.W, tk.E), padx=5, pady=5)

    def testFunc1(self, value):
        self.chosenAnimals.update({value: self.animalList[0]})
        self.configure()

    def testFunc2(self, value):
        self.chosenAnimals.update({value: self.animalList[1]})
        self.configure()

    def testFunc3(self, value):
        self.chosenAnimals.update({value: self.animalList[2]})
        self.configure()

    def configure(self):
        self.printout = ["{} is the {}".format(k, v) for (k,v) in self.chosenAnimals.items()]
        self.tkvar4.set(self.printout)
        self.textLabel4.config(text = self.tkvar4.get())

if __name__ == "__main__":
    app = SampleApp()
    newFrame = AnimalPage(app, app)
    app.geometry("500x200")
    app.mainloop()

使用数组或字典:

import tkinter as tk
from tkinter import ttk

class SampleApp(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        self.wm_title("Choose Multiple Animals")
        self._frame = None

class AnimalPage(ttk.Frame):
    def __init__(self, master, controller):
        tk.Frame.__init__(self, master)
        self.master = master
        self.config(relief='sunken', borderwidth=2)
        self.pack(fill = "both", expand = False)
        self.grid_rowconfigure(0, weight = 1)
        self.grid_columnconfigure(0, weight = 1)

        self.animalList = ['Cat', 'Dog', 'Bear']
        self.choices = ['None', 'Animal1', 'Animal2', 'Animal3']

        self.animal_vars = dict()
        self.text_labels = dict()
        self.popup_menus = dict()
        self.chosenAnimals = {}
        self.tkvar4 = tk.StringVar()

        for i, animal in enumerate(self.animalList):
            self.animal_vars[animal] = tk.StringVar(master)
            self.animal_vars[animal].set('None')
            self.text_labels[animal] = ttk.Label(self, text=animal)
            self.text_labels[animal].grid(column=0, row = 5 + i, sticky = (tk.W), padx=5, pady=5)
            self.popup_menus[animal] = ttk.OptionMenu(self, self.animal_vars[animal], *self.choices, command=lambda selected, my_animal=animal: self.testFunc(my_animal, selected))
            self.popup_menus[animal].grid(column=1, row = 5 + i, sticky = (tk.W, tk.E), padx=5, pady=5)

        self.textLabel4 = ttk.Label(self, text=self.tkvar4.get())
        self.textLabel4.grid(column=0, row = 8, sticky = (tk.W, tk.E), padx=5, pady=5)      

    def testFunc(self, animal, selection):
        self.chosenAnimals.update({animal: selection})
        self.configure()

    def configure(self):
        self.printout = ["{} is the {}".format(k, v) for (k,v) in self.chosenAnimals.items()]
        self.tkvar4.set(self.printout)
        self.textLabel4.config(text = self.tkvar4.get())
由于您基本上是在迭代
animalList
以动态创建
标签和
选项菜单
s,因此您最好使用
dict
列表
来帮助您管理和迭代对象

一旦您设置了
dict
列表
,您现在就可以分配/附加您创建的tk小部件并方便地引用回来。在你的例子中,我个人更喜欢
dict
,因为每种动物都有一个有意义的名字,而且更容易调试(寻找
self.text\u标签['Cat']
self.text\u标签[0]

此外,您还可以使用
lambda
绕过tk小部件中
command=…
的限制。通过这种方式,您可以将动物名称直接传递回函数,这样就不需要为每个动物定义名称

顺便说一句,我建议你给你的物品起个更有意义的名字。不要使用诸如
textLabel4
tkvar4
之类的术语,这样代码更容易理解

重要提示: 要使
lambda
在循环中工作,您需要将迭代的
animal
作为默认参数,而不是直接在
lambda
中,一个快速演示:

def func(v):
    print(v)

x = list(range(3))
for i in range(len(x)):
    x[i] = lambda: func(i)

x[0]
# 2
您可能期望
x[0]
将导致打印
0
,但实际上它将是
2
,并且在
x[0:2]
上的结果相同。原因是,分配lambda时,它在每次迭代中引用的是
i
对象,而不是其值
[0,1,2]
。因此,由于循环结束,
i=2
,您的
x
函数将始终打印
2

但是,如果将
i
作为lambda中的默认参数传递,则将传递该值:

x[i] = lambda y=i: func(y)

x[0]()
# 0
结合这一事实,我之所以使用
lambda selected,my_animal=animal:…
是因为
选项菜单中的
命令=…
总是将其
变量
(在本例中是所选的Animal1,Animal2…)作为函数的第一个参数传递


希望这能让事情明朗一点。

你好,懒汉,太棒了,非常感谢!!!我希望我能给你们多投一票,因为你们很好地解决了我所有的问题,并给出了一个我(以及我希望的其他人)可以用来构建自己的下拉菜单的答案。我不知道lambda会给你那种能力。我知道需要一个for循环,但不确定如何实现它,但将它与字典相结合是定义它的一种完美的逻辑和优美的方式,以便所有元素以一种干巴巴的方式组合在一起。非常感谢。不客气。不久前我碰巧做了一个类似的练习。如果你深入研究的话,有很多关于tkinter的技巧和窍门。继续编码@LionelYu很抱歉我没有在
lambda
上偷懒。我已经更新了
命令=…
行和
testFunc
。它现在应该与您现有的功能相同。请更新。我之前注意到了!我想你不会真的为这一部分烦恼,因为它没有太多地关注问题的本质。但谢谢你,现在你的解决方案是完美的。我还意识到这个示例不需要tkvar4,我认为在我的原始代码中,我保存它是为了其他目的。您的代码基本上将60行湿代码缩减为10行干代码,并允许无限的下拉表+基于animalList中项目数的后续函数。今天你决定回答我的问题,我感到非常幸运!是技术上不需要
tkvar4
,因为
Label
不接受var,所以您可以在
self.configure()中将文本直接设置为
标签。如果不是我,总会有人愿意帮忙,不过我很高兴能帮上忙!小提示:我更新了
lambda
,以澄清传递的
params
,因为表示作为默认参数传递的内容很重要(除了可以看到的变量作为
所选的
传递之外)。如果您只是将
animal
放在
lambda
中,它将不起作用,因为所有三个对象的对象引用都是相同的。测试一下,自己看看!