Лучший способ структурировать приложение 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()

funAfunB а также 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()

Также см:

Надеюсь, это поможет.

Это не плохая структура; это будет работать просто отлично. Тем не менее, вам нужно иметь функции в функции для выполнения команд, когда кто-то нажимает на кнопку или что-то

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

Вот пример:

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()

введите описание изображения здесь

Ссылка: http://www.python-course.eu/tkinter_buttons.php

Мой предпочтительный способ сделать это похож на ответ Брайана Окли. Вот пример, сделанный 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.

Другие вопросы по тегам