Как разумно применять принципы СУХОЙ к tkinter OptionMenu? - PullRequest
0 голосов
/ 09 октября 2018

У меня есть простое приложение 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 голосов
/ 09 октября 2018

Используйте массивы или словари:

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, вы можете также использоватьdict или list, чтобы помочь вам управлять и перебирать объекты.

После того, как вы настроили dict или list, теперь вы можете назначать / добавлять созданные вами виджеты tk.и ссылаться обратно легко.Лично в вашем примере я предпочитаю dict, поскольку каждое животное имеет осмысленное имя, с которым его легче отлаживать (поиск self.text_labels['Cat'] будет проще, чем self.text_labels[0])

Также вы можете использоватьlambda, чтобы обойти ограничение command=... в виджетах tk.Таким образом, вы передаете имя животного обратно в функцию, поэтому вам не нужно определять его для каждого животного.

В качестве отступления в идеале я бы предложил вам дать своим объектам более осмысленные имена.Держитесь подальше от таких терминов, как 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].Причина в том, что когда лямбда назначается, она ссылается на объект i вместо значения [0, 1, 2] в каждой итерации.Следовательно, поскольку цикл завершился, i = 2 и ваши x функции будут всегда печатать 2.

Однако, если вы передали i в качестве параметра по умолчанию в лямбда-выражении, значение будет передано:

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

x[0]()
# 0

В связи с этим, причина, по которой я использовал lambda selected, my_animal=animal:..., заключается в том, что command=... в OptionMenu всегда передает его variable (который в данном случае является выбранным Animal1, Animal2 ...) в качестве первого параметра функции.

Надеюсь, это немного прояснит ситуацию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...