Найкращий спосіб структурувати додаток tkinter?


136

Далі йде загальна структура моєї типової програми 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.


2
Ось чудовий підручник з дизайну графічного інтерфейсу tkinter з кількома прикладами - python-textbok.readthedocs.org/en/latest/… Ось ще один приклад із схемою дизайну MVC - sukhbinder.wordpress.com/2014/12/ 25 /…
Бондолін

12
Це питання може бути широким, але воно корисне і є як відносно популярна відповідь (щодо майже всіх інших відповідей [tkinter]). Я висуваю кандидатуру на повторне відкриття, оскільки бачу, що відкриття є кориснішим, ніж закриття.
Брайан Оуклі

Відповіді:


271

Я виступаю за об'єктно-орієнтований підхід. Це шаблон, з якого я починаю:

# 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"). Це дозволяє визначити простий інтерфейс між компонентами, допомагаючи зберегти з’єднання до мінімуму.


22
@Bryan Oakley Чи знаєте ви якісь хороші зразкові коди в Інтернеті, що я можу вивчити їх структуру?
Кріс Аун

2
Я другий об'єктно-орієнтований підхід. Однак, на мій досвід, утриматися від використання спадщини на своєму класі, який викликає графічний інтерфейс, є гарною ідеєю. Він пропонує вам більшу гнучкість, якщо і об'єкти Tk, і Frame є атрибутами класу, який не успадковує нічого. Таким чином ви можете отримати доступ до об'єктів Tk і Frame легше (і менш неоднозначно), а знищення не знищить усе у вашому класі, якщо ви цього не хочете. Я забув точну причину, чому це важливо в деяких програмах, але це дозволяє робити більше справ.
Brōtsyorfuzthrāx

1
не просто заняття дасть вам приватне простір імен? чому підвищити підкласинг Frame на цьому?
gcb

3
@gcb: так, будь-який клас надасть вам приватний простір імен. Чому підклас Frame? Я зазвичай збираюся створити кадр у будь-якому випадку, тому це один клас менш керований (підклас Frame, проти клас, що успадковується від об'єкта, з кадром як атрибут). Я трохи перефразував відповідь, щоб зробити це більш зрозумілим. Дякуємо за відгук.
Брайан Оуклі

2
@madtyn: не потрібно зберігати посилання на нього parent, якщо ви не збираєтесь його використовувати пізніше. Я не зберігав його, оскільки жоден з кодів у моєму прикладі не вимагав його збереження.
Брайан Оуклі

39

Розміщення кожного вікна верхнього рівня в його окремий клас дає можливість повторного використання коду та кращу організацію коду. Будь-які кнопки та відповідні методи, наявні у вікні, повинні бути визначені всередині цього класу. Ось приклад (взято звідси ):

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

Також дивіться:

Сподіваюся, що це допомагає.


6

Це не погана структура; це буде працювати чудово. Однак у функції потрібно виконувати команди, коли хтось натискає кнопку або щось таке

Тож, що ви можете зробити, це написати класи для них, тоді у класі є методи, які обробляють команди для натискання кнопки і таке інше.

Ось приклад:

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 = Window1(root)
    root.mainloop()

if __name__ == '__main__':
    main()

Зазвичай програми tk з декількома вікнами - це кілька великих класів, і __init__всі записи, мітки тощо створюються, і тоді кожен метод полягає в обробці подій натискання кнопки

Насправді це не правильний спосіб зробити це, що б не працювало для вас, і робота буде виконана до тих пір, поки вона не читається, і ви можете легко пояснити це, тому що якщо ви не можете легко пояснити свою програму, мабуть, є кращий спосіб це зробити. .

Погляньте на Мислення в Tkinter .


3
"Думаючи про Ткінтера" виступає за світовий імпорт, що, на мою думку, є дуже поганою порадою.
Брайан Оуклі

1
Щоправда, я не пропоную вам використовувати глобалісти лише деякі основні структури метосу класу ви праві :)
Серія

2

OOP повинен бути підходом і 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


2
Ви можете використовувати лише TKinterна Python 2. Я рекомендував би використовувати tkinterдля Python 3. Я також помістив би останні три рядки коду під main()функцію і зателефонував би в кінці програми. Я б точно не уникав використання, from module_name import *оскільки це забруднює глобальний простір імен і може зменшити читабельність.
Зак

1
Як ви могли сказати різницю між button1 = tk.Button(root, command=funA) і button1 = ttk.Button(root, command=funA)чи tkinterімпортувався модуль розширення? З *синтаксисом, здається, обидва рядки коду button1 = Button(root, command=funA). Я б не рекомендував використовувати цей синтаксис.
Зак

0

Впорядкування програми за допомогою класу полегшує вам та іншим, хто працює з вами, налагоджувати проблеми та вдосконалювати додаток легко.

Ви можете легко організувати свою заявку так:

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

-2

Напевно, найкращий спосіб навчитися структурувати свою програму - читаючи код інших людей, особливо якщо це велика програма, до якої внесли свій внесок багато людей. Переглянувши код багатьох проектів, ви повинні отримати уявлення про те, яким повинен бути стиль консенсусу.

Python, як мова, особливий тим, що є кілька чітких рекомендацій щодо того, як слід форматувати свій код. Перший - так званий "Дзен Пітона":

  • Красиве краще, ніж потворне.
  • Явне краще, ніж неявне.
  • Простий - краще, ніж складний.
  • Комплекс краще, ніж складний.
  • Квартира краще, ніж вкладена.
  • Розріджений краще, ніж щільний.
  • Читання рахується.
  • Особливі випадки недостатньо спеціальні для порушення правил.
  • Хоча практичність перемагає чистоту.
  • Помилки ніколи не повинні проходити мовчки.
  • Якщо явно мовчати.
  • В умовах неоднозначності відмовтеся від спокуси здогадатися.
  • Повинно бути один - і бажано лише один - очевидний спосіб це зробити.
  • Хоча цей спосіб може бути спочатку не очевидним, якщо ви не голландці.
  • Зараз краще, ніж ніколи.
  • Хоча ніколи часто краще , ніж прямо в даний час.
  • Якщо реалізацію важко пояснити, це погана ідея.
  • Якщо реалізацію легко пояснити, це може бути хорошою ідеєю.
  • Простори імен - це чудова ідея - давайте зробимо більше таких!

На більш практичному рівні існує PEP8 , посібник зі стилів для Python.

Маючи на увазі, я б сказав, що ваш стиль коду не дуже відповідає, особливо вкладеним функціям. Знайдіть спосіб вирівняти їх, використовуючи класи або переміщуючи їх в окремі модулі. Це полегшить розуміння структури вашої програми.


12
-1 за використання дзен Python. Хоча це все гарна порада, він безпосередньо не стосується питання, яке було задано. Вийміть останній абзац, і ця відповідь може стосуватися майже кожного питання python на цьому сайті. Це гарна позитивна порада, але це не відповідає на питання.
Брайан Оуклі

1
@BryanOakley Я з цим не згоден. Так, дзен Питона широкий і його можна використовувати для вирішення багатьох питань. У заключному абзаці він згадував про вибір класів або розміщення функцій в окремих модулях. Він також згадав PEP8, посібник зі стилів для Python, з посиланнями на нього. Хоча це не пряма відповідь, я вважаю, що ця відповідь є правдоподібною в тому, що в ній згадується багато різних маршрутів, які можна пройти. Це лише моя думка
Зак

1
Я прийшов сюди шукати відповіді на це конкретне питання. Навіть для відкритого запитання я нічого не можу зробити з цією відповіддю. -1'd від мене також.
Джонатан

Ні в якому разі, питання полягає в тому, щоб структурувати додаток tkinter , нічого про вказівки щодо стилізації / кодування / дзен. Легко, як цитувати @Arbiter "Хоча це не пряма відповідь", так що це НЕ відповідь. Це на кшталт "може, так, а може, і ні", з дзену, що є попередньо.
м3нда

-7

Я особисто не використовую заперечений орієнтований підхід, здебільшого тому, що він а) лише заважає; б) ти ніколи не будеш використовуєте це як модуль.

але щось, про що тут не йдеться, - це те, що потрібно використовувати нитку чи багатообробку. Завжди. інакше ваша заявка буде жахливою.

просто зробіть простий тест: запустіть вікно, а потім отримайте якусь URL-адресу чи щось інше. зміни - ваш інтерфейс користувача не буде оновлюватися, поки відбувається запит мережі. Значить, вікно вашої програми буде порушено. залежать від ОС, на якій ви перебуваєте, але в більшості випадків вона не перемальовується, все, що ви перетягнете через вікно, буде замазано на ній, поки процес не повернеться до основної версії TK.


4
Те, що ви говорите, просто не відповідає дійсності. Я писав безліч програм на основі tk, як особистих, так і комерційних, і майже ніколи не доводилося використовувати теми. Нитки мають своє місце, але це просто неправда, що ви повинні використовувати їх під час написання програм tkinter. Якщо у вас довго функціонують функції, можливо, вам знадобляться потоки або багатопроцесорна робота, але є багато, багато типів програм, які ви можете написати, які не потребують потоків.
Брайан Оуклі

Я думаю, якби ви перефразували свою відповідь, щоб бути трохи більш зрозумілою, це було б кращою відповіддю. Також було б справді допомогти канонічний приклад використання ниток з ткінтером.
Брайан Оуклі

Не хвилювало, як найкраща відповідь тут, бо це ніби не тема. але майте на увазі, що починати з нарізки / мультиплікації дуже просто. якщо вам доведеться додати пізніше, це програний бій. і в наш час абсолютно немає жодної програми, яка ніколи не спілкуватиметься з мережею. і навіть якщо ви ігноруєте і думаєте, що "у мене лише мало дискового вводу", завтра ваш клієнт вирішить, що файл буде жити на NFS, і ви чекаєте мережевого IO, і ваш додаток здається мертвим.
gcb

2
@ erm3nda: "кожен додаток, підключений до мережі або виконує написання вводу- виводу, буде набагато швидше за допомогою потоків або підпроцесів" - це просто не відповідає дійсності. Нитка не обов'язково зробить вашу програму швидшою, а в деяких випадках зробить її повільнішою. У програмуванні GUI головна причина використання потоків - це можливість запускати якийсь код, який інакше блокував би GUI.
Брайан Оуклі

2
@ Erm3nda: немає, я НЕ кажу потоки не потрібні взагалі . Вони, безумовно, потрібні (ну, нитки або багатопроцесорні) для багатьох речей. Просто існує дуже великий клас програм GUI, де tkinter підходить, а де теми просто не потрібні. І так, "інсталятори, блокноти,. Та інші прості інструменти" потрапляють до цієї категорії. Світ складається з більшості цих «легких інструментів», ніж таких речей, як word, excel, Photoshop тощо. Крім того, пам’ятайте, що контекст тут ткінтер . Tkinter зазвичай не використовується для дуже великих, складних застосувань.
Брайан Оуклі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.