Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Cấu trúc MVC trong Tkinter
Trong bài viết này, mình sẽ tìm hiểu cách áp dụng mẫu thiết kế Model-View-Controller (MVC) để cấu trúc một ứng dụng Tkinter. MVC là một trong những mẫu thiết kế phần mềm phổ biến, giúp tách biệt logic xử lý, giao diện người dùng, và dữ liệu, từ đó làm cho ứng dụng dễ bảo trì và mở rộng hơn. Khi ứng dụng của bạn phát triển, sự phức tạp cũng tăng lên, và việc áp dụng mẫu thiết kế này sẽ giúp bạn quản lý mã nguồn một cách hiệu quả hơn.
Giới thiệu về Tkinter MVC
Khi ứng dụng của bạn phát triển, độ phức tạp của nó cũng tăng lên. Để làm cho ứng dụng dễ quản lý hơn, bạn có thể sử dụng mẫu thiết kế Model-View-Controller (MVC).
Mẫu thiết kế MVC cho phép bạn chia ứng dụng thành ba thành phần chính: model (mô hình), view (giao diện hiển thị), và controller (bộ điều khiển). Cấu trúc này giúp bạn tập trung vào logic của từng phần và dễ dàng bảo trì hơn, đặc biệt khi ứng dụng phát triển lớn hơn.
Biểu đồ dưới đây minh họa cách thức hoạt động của mẫu thiết kế MVC:
Bài viết này được đăng tại [free tuts .net]
Model (Mô hình)
Model trong MVC đại diện cho dữ liệu của ứng dụng. Model chịu trách nhiệm lấy dữ liệu từ hoặc ghi dữ liệu vào các kho lưu trữ như cơ sở dữ liệu hoặc file . Ngoài ra, model cũng có thể chứa logic để kiểm tra tính hợp lệ của dữ liệu, đảm bảo tính toàn vẹn của dữ liệu.
Model không được phụ thuộc vào view và controller. Điều này có nghĩa là bạn có thể tái sử dụng model trong các ứng dụng không phải Tkinter, như ứng dụng web hoặc di động.
View (Giao diện hiển thị)
View là giao diện người dùng, thể hiện dữ liệu trong model. View không giao tiếp trực tiếp với model. Lý tưởng nhất, view nên có rất ít logic chỉ để hiển thị dữ liệu.
View giao tiếp trực tiếp với controller. Trong các ứng dụng Tkinter, view là cửa sổ gốc chứa các widget.
Controller (Bộ điều khiển)
Controller đóng vai trò trung gian giữa view và model. Controller truyền dữ liệu giữa view và model.
Ví dụ, khi người dùng nhấn nút "Save" (Lưu) trên view, controller sẽ chuyển yêu cầu "lưu" đến model để lưu dữ liệu vào cơ sở dữ liệu và thông báo cho view hiển thị một thông báo.
Ví dụ về Tkinter MVC
Chúng ta sẽ lấy một ví dụ đơn giản để minh họa cách áp dụng mẫu thiết kế MVC trong một ứng dụng Tkinter:
Ứng dụng mà bạn sẽ xây dựng có một ô nhập liệu để nhập email. Khi bạn nhấn nút "Save", controller sẽ gọi model để kiểm tra tính hợp lệ của email.
- Nếu email hợp lệ, model sẽ lưu email vào một file văn bản và view sẽ hiển thị thông báo thành công.
- Nếu email không hợp lệ, view sẽ hiển thị thông báo lỗi.
Mình sẽ ẩn thông báo sau 3 giây.
Lớp Model
Lớp Model dưới đây có thuộc tính email
:
class Model: def __init__(self, email): self.email = email @property def email(self): return self.__email @email.setter def email(self, value): """ Kiểm tra tính hợp lệ của 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'Email không hợp lệ: {value}') def save(self): """ Lưu email vào file :return: """ with open('emails.txt', 'a') as f: f.write(self.email + '\n')
Cách thức hoạt động:
- Setter
email
kiểm tra tính hợp lệ của email trước khi gán nó cho thuộc tính__email
. Nếu email không hợp lệ, nó sẽ sinh ra một ngoại lệValueError
. - Phương thức
save()
ghi email vào một file văn bản. Trong các ứng dụng thực tế, bạn có thể muốn lưu nó vào một cơ sở dữ liệu.
View
Dưới đây là lớp View hiển thị một form để nhập email:
class View(ttk.Frame): def __init__(self, parent): super().__init__(parent) # tạo các widget # nhãn self.label = ttk.Label(self, text='Email:') self.label.grid(row=1, column=0) # ô nhập email 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) # nút lưu self.save_button = ttk.Button(self, text='Save', command=self.save_button_clicked) self.save_button.grid(row=1, column=3, padx=10) # thông báo self.message_label = ttk.Label(self, text='', foreground='red') self.message_label.grid(row=2, column=1, sticky=tk.W) # thiết lập controller self.controller = None def set_controller(self, controller): """ Thiết lập controller :param controller: :return: """ self.controller = controller def save_button_clicked(self): """ Xử lý sự kiện nhấn nút :return: """ if self.controller: self.controller.save(self.email_var.get()) def show_error(self, message): """ Hiển thị thông báo lỗi :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): """ Hiển thị thông báo thành công :param message: :return: """ self.message_label['text'] = message self.message_label['foreground'] = 'green' self.message_label.after(3000, self.hide_message) # đặt lại form self.email_entry['foreground'] = 'black' self.email_var.set('') def hide_message(self): """ Ẩn thông báo :return: """ self.message_label['text'] = ''
Cách thức hoạt động:
- Tạo các widget trong phương thức
__init__()
. - Định nghĩa phương thức
set_controller()
để thiết lập một controller. - Gọi phương thức
save()
của controller trong sự kiện nhấn nút lưu. - Định nghĩa các phương thức
show_error()
,show_success()
, vàhide_message()
để hiển thị/ẩn thông báo.
Controller
Lớp Controller dưới đây thực hiện các chức năng sau:
class Controller: def __init__(self, model, view): self.model = model self.view = view def save(self, email): """ Lưu email :param email: :return: """ try: # lưu model self.model.email = email self.model.save() # hiển thị thông báo thành công self.view.show_success(f'Email {email} đã được lưu!') except ValueError as error: # hiển thị thông báo lỗi self.view.show_error(error)
Cách thức hoạt động của controller:
- Gán model và view trong phương thức
__init__()
. - Định nghĩa phương thức
save()
để lưu model vào file văn bản. Nếu model được lưu thành công, hiển thị thông báo thành công. Ngược lại, hiển thị thông báo lỗi.
Application
Lớp App dưới đây sử dụng các lớp Model, View, và Controller:
class App(tk.Tk): def __init__(self): super().__init__() self.title('Tkinter MVC Demo') # tạo model model = Model('hello@pythontutorial.net') # tạo view và đặt nó lên cửa sổ gốc view = View(self) view.grid(row=0, column=0, padx=10, pady=10) # tạo controller controller = Controller(model, view) # thiết lập controller cho view view.set_controller(controller)
Cách thức hoạt động:
- Tạo model.
- Tạo view và đặt nó lên cửa sổ gốc.
- Tạo controller và thiết lập nó cho view.
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('hello@pythontutorial.net') # 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()
Kết bài
Sử dụng mẫu thiết kế MVC để cấu trúc ứng dụng Tkinter không chỉ giúp tổ chức mã nguồn rõ ràng và dễ quản lý hơn mà còn tạo điều kiện thuận lợi cho việc bảo trì và mở rộng ứng dụng trong tương lai. Bằng cách tách biệt các thành phần model, view, và controller, bạn có thể phát triển ứng dụng một cách có hệ thống, giảm thiểu sự phụ thuộc lẫn nhau giữa các phần, từ đó nâng cao chất lượng và hiệu suất của ứng dụng.