Лучший способ структурировать приложение tkinter
Ниже приведена общая структура моей типичной программы Python tkinter.
def funA():
def funA1():
def funA12():
# stuff
def funA2():
# stuff
def funB():
def funB1():
# stuff
def funB2():
# stuff
def funC():
def funC1():
# stuff
def funC2():
# stuff
root = tk.Tk()
button1 = tk.Button(root, command=funA)
button1.pack()
button2 = tk.Button(root, command=funB)
button2.pack()
button3 = tk.Button(root, command=funC)
button3.pack()
funA
funB
а также funC
воспитать другого Toplevel
окна с виджетами, когда пользователь нажимает на кнопки 1, 2, 3.
Мне интересно, если это правильный путь для написания программы Python Tkinter? Конечно, это сработает, даже если я напишу так, но лучше ли? Звучит глупо, но когда я вижу коды, написанные другими людьми, их код не перепутается с кучей функций, и в основном у них есть классы.
Есть ли какая-то конкретная структура, которой мы должны следовать в качестве хорошей практики? Как я должен планировать, прежде чем начать писать программу на Python?
Я знаю, что нет лучшей вещи в программировании, и я не прошу об этом. Я просто хочу несколько советов и объяснений, чтобы держать меня в правильном направлении, так как я изучаю Python самостоятельно.
10 ответов
Я защищаю объектно-ориентированный подход. Это шаблон, с которого я начинаю:
# Use Tkinter for python 2, tkinter for python 3
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
<create the rest of your GUI here>
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
Важные вещи, на которые стоит обратить внимание:
Я не использую импорт подстановочных знаков. Я импортирую пакет как "tk", который требует, чтобы я префикс всех команд
tk.
, Это предотвращает глобальное загрязнение пространства имен, а также делает код совершенно очевидным, когда вы используете классы Tkinter, классы ttk или некоторые другие.Основное приложение - это класс. Это дает вам личное пространство имен для всех ваших обратных вызовов и частных функций, и, как правило, упрощает организацию вашего кода. В процедурном стиле вы должны кодировать сверху вниз, определять функции перед их использованием и т. Д. С помощью этого метода вы этого не делаете, поскольку вы фактически не создаете главное окно до самого последнего шага. Я предпочитаю наследовать от
tk.Frame
просто потому, что я обычно начинаю с создания фрейма, но это ни в коем случае не обязательно.
Если в вашем приложении есть дополнительные окна верхнего уровня, я рекомендую сделать каждый из них отдельным классом, унаследованным от tk.Toplevel
, Это дает вам все те же преимущества, что и упомянутые выше: окна являются атомарными, имеют собственное пространство имен и хорошо организованный код. Кроме того, он позволяет легко помещать каждый в отдельный модуль, когда код начинает увеличиваться в размере.
Наконец, вы можете рассмотреть возможность использования классов для каждой основной части вашего интерфейса. Например, если вы создаете приложение с панелью инструментов, панелью навигации, строкой состояния и основной областью, вы можете создать каждый из этих классов. Это делает ваш основной код довольно маленьким и легким для понимания:
class Navbar(tk.Frame): ...
class Toolbar(tk.Frame): ...
class Statusbar(tk.Frame): ...
class Main(tk.Frame): ...
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.statusbar = Statusbar(self, ...)
self.toolbar = Toolbar(self, ...)
self.navbar = Navbar(self, ...)
self.main = Main(self, ...)
self.statusbar.pack(side="bottom", fill="x")
self.toolbar.pack(side="top", fill="x")
self.navbar.pack(side="left", fill="y")
self.main.pack(side="right", fill="both", expand=True)
Поскольку все эти экземпляры имеют общего родителя, родительский элемент фактически становится частью "контроллера" архитектуры модель-представление-контроллер. Так, например, главное окно может поместить что-то в строку состояния, вызвав self.parent.statusbar.set("Hello, world")
, Это позволяет вам определить простой интерфейс между компонентами, помогая поддерживать связь с минимальным.
Помещение каждого из ваших окон верхнего уровня в отдельный класс обеспечивает повторное использование кода и лучшую организацию кода. Все кнопки и соответствующие методы, присутствующие в окне, должны быть определены внутри этого класса. Вот пример (взятый отсюда):
import tkinter as tk
class Demo1:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.button1 = tk.Button(self.frame, text = 'New Window', width = 25, command = self.new_window)
self.button1.pack()
self.frame.pack()
def new_window(self):
self.newWindow = tk.Toplevel(self.master)
self.app = Demo2(self.newWindow)
class Demo2:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
self.quitButton = tk.Button(self.frame, text = 'Quit', width = 25, command = self.close_windows)
self.quitButton.pack()
self.frame.pack()
def close_windows(self):
self.master.destroy()
def main():
root = tk.Tk()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()
Также см:
- простой привет мир от tkinter docs
- Пример кода Tkinter для нескольких окон, почему кнопки не загружаются правильно?
- Tkinter: как показать / скрыть окно
Надеюсь, это поможет.
Это не плохая структура; это будет работать просто отлично. Тем не менее, вам нужно иметь функции в функции для выполнения команд, когда кто-то нажимает на кнопку или что-то
Так что вы могли бы написать классы для них, а затем иметь методы в классе, которые обрабатывают команды для нажатий кнопок и тому подобное.
Вот пример:
import tkinter as tk
class Window1:
def __init__(self, master):
pass
# Create labels, entries,buttons
def button_click(self):
pass
# If button is clicked, run this method and open window 2
class Window2:
def __init__(self, master):
#create buttons,entries,etc
def button_method(self):
#run this when button click to close window
self.master.destroy()
def main(): #run mianloop
root = tk.Tk()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()
Обычно программы с несколькими окнами представляют собой несколько больших классов и __init__
все записи, метки и т. д. создаются, а затем каждый метод обрабатывает события нажатия кнопки.
На самом деле не существует правильного способа сделать это, что бы ни работало для вас и выполняло работу, пока она читаема, и вы можете легко объяснить это, потому что, если вы не можете легко объяснить свою программу, возможно, есть лучший способ сделать это.,
Взгляните на мышление в Tkinter.
Вот отличное руководство по дизайну графического интерфейса tkinter с парой примеров - http://python-textbok.readthedocs.org/en/latest/Introduction_to_GUI_Programming.html
Вот еще один пример с шаблоном проектирования MVC - https://sukhbinder.wordpress.com/2014/12/25/an-example-of-model-view-controller-design-pattern-with-tkinter-python/
ООП должен быть подход и frame
должна быть переменной класса вместо переменной экземпляра.
from Tkinter import *
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.button = Button(frame,
text="QUIT", fg="red",
command=frame.quit)
self.button.pack(side=LEFT)
self.slogan = Button(frame,
text="Hello",
command=self.write_slogan)
self.slogan.pack(side=LEFT)
def write_slogan(self):
print "Tkinter is easy to use!"
root = Tk()
app = App(root)
root.mainloop()
Мой предпочтительный способ сделать это похож на ответ Брайана Окли. Вот пример, сделанный Sentdex на Youtube, проверьте его плейлист «Графические интерфейсы с Tkinter».
Я думаю, что очень уместно разместить его здесь, потому что это отличный пример для ОП, и поэтому он также отвечает на этот ответ, который был поднят 35 людьми, и на него не ответили;
@Bryan Oakley, знаете ли вы какие-нибудь хорошие примеры кодов в Интернете, которые я могу изучить их структуру? — Крис Аунг
import tkinter as tk
LARGE_FONT= ("Verdana", 12)
class SeaofBTCapp(tk.Tk):
"""
tkinter example app with OOP
"""
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for frame_class in (StartPage,PageOne, PageTwo):
frame = frame_class(container, self)
self.frames[frame_class] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
"""
Put specific frame on top
"""
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
"""
Starting frame for app
"""
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent,bg='grey')
label = tk.Label(self, text="Start Page", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button_page1 = tk.Button(self, text = 'Visit Page 1', command= lambda: controller.show_frame(PageOne))
button_page1.pack()
button_page2 = tk.Button(self, text = 'Visit Page 2', command= lambda: controller.show_frame(PageTwo))
button_page2.pack()
class PageOne(tk.Frame):
"""
First page of program
"""
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent,bg='light blue')
label = tk.Label(self, text="Page one", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button_home = tk.Button(self, text = 'Back to Home', command= lambda: controller.show_frame(StartPage))
button_home.pack()
button_home = tk.Button(self, text = 'Go to page2', command= lambda: controller.show_frame(PageTwo))
button_home.pack()
class PageTwo(tk.Frame):
"""
First page of program
"""
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent,bg='light green')
label = tk.Label(self, text="Page two", font=LARGE_FONT)
label.pack(pady=10,padx=10)
button_home = tk.Button(self, text = 'Back to Home', command= lambda: controller.show_frame(StartPage))
button_home.pack()
button_home = tk.Button(self, text = 'Go to page1', command= lambda: controller.show_frame(PageOne))
button_home.pack()
app = SeaofBTCapp()
app.mainloop()
Найдите код здесь: [https://pythonprogramming.net/change-show-new-frame-tkinter/]
В проектах, в которых у меня есть полномочия на проектирование, я разделяю часть работы с графическим интерфейсом на два набора кода:
Набор 1. Основной код, рисующий окно, содержащее виджеты и их привязки. Возможно, я сгенерировал их с помощью wxFormBuilder или Glade, поэтому мне не нужны никакие изменения в файлах вручную. Скорее всего, я с трудом вручную создавал окна с нуля и определенно не хочу никаких непреднамеренных манипуляций!
Набор 2. Код, в котором я пишу функциональность управления окном.
Отделение ядра определения графического пользовательского интерфейса от работы по созданию функциональности означает, что я не «случайно» нарушаю структуру основного окна, когда работаю над функциональностью, и код становится гораздо более управляемым и поддерживаемым.
Мы все знаем, что размер и сложность программных файлов очень быстро становятся настоящей проблемой. Итак, используя описанное выше разделение формы и функции (так сказать), я просто импортирую файл кода основного окна и подклассифицирую его в файле функционального кода.
Например, frmWindow_core_code.py сохраняет основные функции окна:
класс frmWindow(tk.TK):
button1 = ttk.Button....
entry1 = ttk.Entry....
etc
etc
Файл, который управляет функциональностью графического интерфейса, например MyWindowProgram.py, импортирует и разбивает на подклассы основной файл, например:
импортировать frmWindow_cored_code как диалог
класс ThisWindow(Dialogue.frmWindow): и т. д. и т. п.
Просто предложение, я надеюсь, может быть полезным.
Вероятно, лучший способ научиться структурировать свою программу - это читать код других людей, особенно если это большая программа, в которую многие люди внесли свой вклад. Посмотрев код многих проектов, вы должны получить представление о том, каким должен быть консенсусный стиль.
Python, как язык, особенный в том, что есть несколько строгих рекомендаций относительно того, как вы должны форматировать свой код. Первый - это так называемый "Zen of Python":
- Красиво лучше, чем безобразно.
- Явное лучше, чем неявное.
- Простое лучше, чем сложное.
- Сложный лучше, чем сложный.
- Квартира лучше, чем вложенная.
- Разреженный лучше, чем плотный.
- Читаемость имеет значение.
- Особые случаи не настолько особенные, чтобы нарушать правила.
- Хотя практичность превосходит чистоту.
- Ошибки никогда не должны проходить бесшумно.
- Если явно не молчать.
- Перед лицом двусмысленности откажитесь от соблазна гадать.
- Должен быть один - и желательно только один - очевидный способ сделать это.
- Хотя этот путь может быть неочевидным на первый взгляд, если вы не голландец.
- Сейчас лучше, чем никогда.
- Хотя никогда не бывает лучше, чем сейчас.
- Если реализацию сложно объяснить, это плохая идея.
- Если реализацию легко объяснить, это может быть хорошей идеей.
- Пространства имен - одна из отличных идей - давайте сделаем больше!
На более практическом уровне есть PEP8, руководство по стилю для Python.
Имея это в виду, я бы сказал, что ваш стиль кода не очень подходит, особенно для вложенных функций. Найдите способ сгладить их, используя классы или перемещая их в отдельные модули. Это сделает структуру вашей программы намного проще для понимания.
Организация вашего приложения с помощью класса облегчит вам и другим, кто работает с вами, отладку проблем и легкое улучшение приложения.
Вы можете легко организовать свое приложение следующим образом:
class hello(Tk):
def __init__(self):
super(hello, self).__init__()
self.btn = Button(text = "Click me", command=close)
self.btn.pack()
def close():
self.destroy()
app = hello()
app.mainloop()
Я лично не использую возражающий ориентированный подход, главным образом потому, что он a) только мешает; б) вы никогда не будете использовать это как модуль.
но то, что здесь не обсуждается, это то, что вы должны использовать многопоточность или многопоточность. Всегда. в противном случае ваше приложение будет ужасным.
просто сделайте простой тест: запустите окно, а затем загрузите какой-нибудь URL или что-нибудь еще. изменения в том, что ваш пользовательский интерфейс не будет обновляться во время выполнения сетевого запроса. То есть окно вашего приложения будет разбито. зависит от операционной системы, в которой вы работаете, но в большинстве случаев она не будет перерисовываться, все, что вы перетаскиваете через окно, будет намазано на нем, пока процесс не вернется к основному циклу TK.