如何智能地将DRY原理应用于tkinter OptionMenu?

时间:2018-10-09 16:21:54

标签: python tkinter

我有一个简单的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()

1 个答案:

答案 0 :(得分:3)

使用数组或字典:

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来动态创建LabelOptionMenu,因此您最好使用dict或{{1} },以帮助您管理和遍历对象。

设置好listdict之后,您现在就可以将创建的tk小部件分配/附加到其中,并轻松地进行引用。在您个人的示例中,我更喜欢list,因为每个动物都有一个有意义的名称,并且易于调试(查找dictself.text_labels['Cat']容易)

此外,您可以使用self.text_labels[0]绕过tk小部件中lambda的限制。这样,您就可以将动物名称直接传回函数,因此无需为每个动物定义它。

顺便说一句,理想情况下,我建议您为对象指定更有意义的名称。远离诸如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中将是相同的结果。原因是分配了lambda时,它是在每次迭代中引用x[0:2]对象而不是其值i。因此,由于循环结束,[0, 1, 2]和您的i = 2函数将始终打印x

但是,如果您通过了2作为lambda中的默认参数,则会传递 value

i

结合这一事实,我之所以使用x[i] = lambda y=i: func(y) x[0]() # 0 是由于lambda selected, my_animal=animal:...中的command=...总是通过其OptionMenu(在这种情况下是选择的Animal1,Animal2 ...)作为函数的第一个参数。

希望这会清除一些东西。