Концепция model-view-controller (MVC) — как работает
По мере роста вашего приложения его сложность также увеличивается. Чтобы сделать приложение Tkinter в Python более управляемым, вы можете использовать концепцию проектирования model-view-controller (MVC).
Что такое Tkinter MVC
Шаблон проектирования MVC позволяет разделить приложение на три основных компонента: модель, вид и контроллер. Эта структура помогает сосредоточиться на логике каждой части и сделать ее более поддерживаемой, особенно когда приложение растет.
Следующая диаграмма иллюстрирует шаблон проектирования MVC:
Модель
Модель в MVC представляет данные. Модель занимается получением данных из хранилища или записью данных в него, например, в базу данных или файл. Модель также может содержать логику для проверки данных, чтобы гарантировать целостность данных.
Модель не должна зависеть от представления и контроллера. Другими словами, вы можете повторно использовать модель в других не-Tkinter приложениях, таких как веб- и мобильные приложения.
Вид
Вид (представление) — это пользовательский интерфейс, представляющий данные в модели. Он не взаимодействует с моделью напрямую. В идеале вид должно иметь очень мало логики для отображения данных.
Вид напрямую взаимодействует с контроллером. В приложениях Tinker вид — это корневое окно, состоящее из виджетов.
Контроллер
Контроллер выступает в качестве посредника между видами и моделями. Контроллер маршрутизирует данные между ними.
Например, если пользователь нажимает кнопку сохранения в представлении, контроллер направляет действие «сохранить» в модель, чтобы сохранить данные в базе данных и уведомить вид о необходимости отображения сообщения.
Пример Tkinter MVC
Рассмотрим простой пример, иллюстрирующий применение шаблона проектирования MVC в приложении Tkinter:
Приложение, которое вы создадите, содержит запись для ввода адреса электронной почты. Когда вы нажимаете кнопку сохранения, контроллер вызывает модель для проверки адреса электронной почты.
Если адрес электронной почты действителен, модель сохраняет его в текстовом файле, а в представлении отображается сообщение об успешном завершении:
Если адрес электронной почты недействителен, в представлении отображается сообщение об ошибке:
Мы скроем сообщение через 3 секунды.
Класс Model
Ниже определяется класс Model, имеющий свойство электронной почты:
class Model: def __init__(self, email): self.email = email @property def email(self): return self.__email @email.setter def email(self, value): """ Validate the email :param value: :return: """ pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' if re.fullmatch(pattern, value): self.__email = value else: raise ValueError(f'Invalid email address: {value}') def save(self): """ Save the email into a file :return: """ with open('emails.txt', 'a') as f: f.write(self.email + '\n')
Как это работает:
- Сеттер email проверяет email перед назначением его атрибуту __email. Если email недействителен, он выдаст ValueError.
- Метод save() записывает email в простой текстовый файл. В реальных приложениях вы можете захотеть сохранить его в базе данных.
View
Ниже определено представление, отображающее форму для ввода адреса электронной почты:
class View(ttk.Frame): def __init__(self, parent): super().__init__(parent) # create widgets # label self.label = ttk.Label(self, text='Email:') self.label.grid(row=1, column=0) # email entry self.email_var = tk.StringVar() self.email_entry = ttk.Entry(self, textvariable=self.email_var, width=30) self.email_entry.grid(row=1, column=1, sticky=tk.NSEW) # save button self.save_button = ttk.Button(self, text='Save', command=self.save_button_clicked) self.save_button.grid(row=1, column=3, padx=10) # message self.message_label = ttk.Label(self, text='', foreground='red') self.message_label.grid(row=2, column=1, sticky=tk.W) # set the controller self.controller = None def set_controller(self, controller): """ Set the controller :param controller: :return: """ self.controller = controller def save_button_clicked(self): """ Handle button click event :return: """ if self.controller: self.controller.save(self.email_var.get()) def show_error(self, message): """ Show an error message :param message: :return: """ self.message_label['text'] = message self.message_label['foreground'] = 'red' self.message_label.after(3000, self.hide_message) self.email_entry['foreground'] = 'red' def show_success(self, message): """ Show a success message :param message: :return: """ self.message_label['text'] = message self.message_label['foreground'] = 'green' self.message_label.after(3000, self.hide_message) # reset the form self.email_entry['foreground'] = 'black' self.email_var.set('') def hide_message(self): """ Hide the message :return: """ self.message_label['text'] = ''
Как это работает.
- Сначала создайте виджеты в методе __init__().
- Во-вторых, определите метод set_controller() для установки контроллера.
- В-третьих, вызовите метод save() контроллера в обработчике событий нажатия кнопки сохранения.
- Наконец, определите методы show_error(), show_success() и hide_message() для отображения/скрытия сообщения.
Controller
Ниже дано определение контроллера:
class Controller: def __init__(self, model, view): self.model = model self.view = view def save(self, email): """ Save the email :param email: :return: """ try: # save the model self.model.email = email self.model.save() # show a success message self.view.show_success(f'The email {email} saved!') except ValueError as error: # show an error message self.view.show_error(error)
Как работает контроллер.
- Сначала назначьте модель и представление в методе __init__()
- Во-вторых, определите метод save(), который сохраняет модель в текстовый файл. Если модель успешно сохранена, покажите сообщение об успехе. В противном случае покажите сообщение об ошибке.
Приложение
Ниже определяется класс приложения, который использует классы модели, представления и контроллера:
class App(tk.Tk): def __init__(self): super().__init__() self.title('Tkinter MVC Demo') # create a model model = Model('[email protected]') # create a view and place it on the root window view = View(self) view.grid(row=0, column=0, padx=10, pady=10) # create a controller controller = Controller(model, view) # set the controller to view view.set_controller(controller) if __name__ == '__main__': app = App() app.mainloop()
Как это работает.
- Сначала создайте модель.
- Во-вторых, создайте представление и поместите его в корневое окно.
- В-третьих, создайте контроллер и настройте его на представление.
Соберите все вместе.
import re import tkinter as tk from tkinter import ttk class Model: def __init__(self, email): self.email = email @property def email(self): return self.__email @email.setter def email(self, value): """ Validate the email :param value: :return: """ pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' if re.fullmatch(pattern, value): self.__email = value else: raise ValueError(f'Invalid email address: {value}') def save(self): """ Save the email into a file :return: """ with open('emails.txt', 'a') as f: f.write(self.email + '\n') class View(ttk.Frame): def __init__(self, parent): super().__init__(parent) # create widgets # label self.label = ttk.Label(self, text='Email:') self.label.grid(row=1, column=0) # email entry self.email_var = tk.StringVar() self.email_entry = ttk.Entry(self, textvariable=self.email_var, width=30) self.email_entry.grid(row=1, column=1, sticky=tk.NSEW) # save button self.save_button = ttk.Button(self, text='Save', command=self.save_button_clicked) self.save_button.grid(row=1, column=3, padx=10) # message self.message_label = ttk.Label(self, text='', foreground='red') self.message_label.grid(row=2, column=1, sticky=tk.W) # set the controller self.controller = None def set_controller(self, controller): """ Set the controller :param controller: :return: """ self.controller = controller def save_button_clicked(self): """ Handle button click event :return: """ if self.controller: self.controller.save(self.email_var.get()) def show_error(self, message): """ Show an error message :param message: :return: """ self.message_label['text'] = message self.message_label['foreground'] = 'red' self.message_label.after(3000, self.hide_message) self.email_entry['foreground'] = 'red' def show_success(self, message): """ Show a success message :param message: :return: """ self.message_label['text'] = message self.message_label['foreground'] = 'green' self.message_label.after(3000, self.hide_message) # reset the form self.email_entry['foreground'] = 'black' self.email_var.set('') def hide_message(self): """ Hide the message :return: """ self.message_label['text'] = '' class Controller: def __init__(self, model, view): self.model = model self.view = view def save(self, email): """ Save the email :param email: :return: """ try: # save the model self.model.email = email self.model.save() # show a success message self.view.show_success(f'The email {email} saved!') except ValueError as error: # show an error message self.view.show_error(error) class App(tk.Tk): def __init__(self): super().__init__() self.title('Tkinter MVC Demo') # create a model model = Model('[email protected]') # create a view and place it on the root window view = View(self) view.grid(row=0, column=0, padx=10, pady=10) # create a controller controller = Controller(model, view) # set the controller to view view.set_controller(controller) if __name__ == '__main__': app = App() app.mainloop()