python - 如何智能地将 DRY 原则应用于 tkinter OptionMenu?
问题描述
我有一个简单的 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
创建Label
s 和OptionMenu
s,因此您不妨使用dict
orlist
来帮助您管理和迭代对象。
设置好之后,您现在dict
可以list
分配/附加您创建的 tk 小部件并轻松引用回来。在您的示例中,我个人更喜欢dict
,因为每只动物都有一个有意义的名称,并且更容易调试(查找self.text_labels['Cat']
会比 更容易self.text_labels[0]
)
此外,您可以利用lambda
绕过command=...
tk 小部件的限制。通过这种方式,您可以将动物名称直接传递回函数,因此您无需为每只动物定义它。
顺便说一句,理想情况下,我建议您给您的对象起更有意义的名称。远离这样的术语textLabel4
,tkvar4
这样更容易理解代码。
重要的提示:
要lambda
在循环中工作,您需要将 iteratedanimal
作为默认参数,而不是直接在 内部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...)作为函数的第一个参数传递。command=...
OptionMenu
variable
希望这能澄清一些事情。
推荐阅读
- apache - .htaccess mod_rewrite 编码的 URL 路径不工作,而未编码的路径工作
- angular - 将外部组件作为 Angular2+ 中的属性传递
- css - 未使用 nginx 在不同子域从 CSS 加载的图像
- java - JPA 查询元素集合
- apache-flink - 为 Flink DataStream 执行选择节点
- swift - 默认情况下,如何阻止 Xcode 倒置启动模拟器并启动景观?
- reactjs - ReactJS _ 文本编辑器 - 现有的 ReactJS 文本编辑器与 libreoffice 编辑器一样完整?
- python-3.x - 无法在 python 3 中导入 urllib.request 模块
- javascript - 如何使用 Leaflet 单独设置 GeoJSON GeometryCollection 的每个几何图形
- batch-file - 批处理文件 - 如果不是“选择 01”到“选择 99”转到 XXX?