Bài 26: Kiến trúc Model View-PyQt6–Part 1

Trong lập trình xử lý tương tác dữ liệu có rất nhiều kỹ thuật, các bài học trước ta dùng các kỹ thuật thông thường và trực tiếp tương tác các đối tượng trên các Widget. Ngoài ra các kỹ sư đã phát triển kiến trúc liên quan tới Model View Controller, hay thường được gọi tắt là mô hình MVC để việc tương tác giao diện người dùng với dữ liệu cũng như các xử lý nghiệp vụ được dễ dàng hơn, dễ tái sử dụng hơn. PyQt6 cũng tích hợp mô hình MVC này và nó được gọi là Qt’s Model/Views Architecture. Chúng ta cùng điểm qua mô hình này:

Các bạn có thể hiểu nôm na mô hình Model View Controller nó hoạt động như sau:

  • Model: Nắm giữ cấu trúc dữ liệu trong ứng dụng mà nó đang làm việc. Ví dụ danh sách dữ liệu hướng đối tượng Product, Employee, Customer, Order, Provider… Khi người sử dụng thao tác trên View thì nó sẽ thông qua Controller để truy suất tới các Model tương ứng, các Model này sẽ hiển thị lên View thông qua Controller, có thể hiểu Controller là trung gian. Ví dụ trên Giao diện bạn muốn xem Danh sách PRODUCT, thì nó sẽ truyền tín hiện cho Controller PRODUCT, và controller này sẽ chọn đúng mô hình đối tượng PRODUCT, Model sẽ tạo ra danh sách Product và trả về cho Controller, sau đó Controller đẩy danh sách Product này lên View.
  • View: Là thành phần để hiển thị giao diện người dùng, nó cũng có nhiệm vụ nhận tương tác của người dùng và gọi Controller tương ứng. Nhiều View khác nhau có thể cùng hiển thị một loại dữ liệu, ví dụ bạn có 1 QTableWidget, 1 QListWidget, 1 QComboBox…các Widgets này có thể cùng hiển thị 1 danh sách model Product. Do đó ở đây bạn thấy tính tái sử dụng model rất cao.
  • Controller: Là thành phần trung gian, nó sẽ nhận các tương tác người dùng trên View, sau đó truyền đổi các tương tác này thành các lệnh để tương tác với Model để nhận các model tương ứng rồi nó sẽ hiển thị kết quả lên View hoặc tương tác với Model khác.

Với PyQt thì các kỹ sư có sự cải biên một xíu, đặc biệt là sự phân biệt giữa Model và View đôi khi nó khá mờ nhạt, khó nhận ra. Đồng thời View và Controller được hợp nhất lại với nhau để tao ra kiến trúc Model/ViewController hay nói tắt là Model View. PyQt chấp nhận các sự kiện từ người dùng thông qua hệ đều hành và ủy quyền những sự kiện này cho các widget để xử lý. Cái hay của Model View đó là giảm bớt sự cồng kềnh:

  • Model nắm giữ dữ liệu hoặc tham chiếu dữ liệu và trả về đối tượng đơn hoặc danh sách đối tượng. Khi model được thanh đổi dữ liệu nó sẽ ngay lập tức cập nhật lên giao diện
  • View sẽ yêu cầu dữ liệu từ Model và hiển thị lên các Widget tương ứng
  • Cơ chế Delegate giúp chương trình cập nhật dữ liệu mô hình tương ứng lên View một cách tự động.

Bài minh họa dưới đây, Chúng Ta sẽ làm một dự án hiển thị danh sách Employee đơn giản lên QListView sử dụng kiến trúc Model View của PyQt:

Ở hình trên Tui có minh họa là Chúng ta tạo một mô hình lớp Employee, và dùng kiến trúc Model View để hiển thị danh sách Employee lên QListView.

Ta thực hiện từng bước như sau:

Bước 1: Tạo dự án “LearnModelViewPart1” trong Pycharm có cấu trúc như hình dưới đây:

  • Employee.py là mô hình lớp hướng đối tượng định nghĩa cấu trúc dữ liệu cho Employee gồm id, name, age
  • EmployeeModel.py là lớp model view cho Employee, nó kế thừa từ “QAbstractListModel” lớp này sẽ lưu trữ danh sách dữ liệu mô hình Employee và hiển thị lên QListView
  • MainWindow.ui là màn hình giao diện được thiết kế bằng Qt Designer
  • MainWindow.py là lớp Generate Python của MainWindow.ui
  • MainWindowEx.py là lớp mã lệnh xử lý sự kiện người dùng cũng như lắp ráp dữ liệu trong mô hình Model View
  • MyApp.py là lớp thực thi chương trình

Bước 2: Tạo lớp Employee.py có mã lệnh như dưới đây:

class Employee:
    def __init__(self,id,name,age):
        self.id=id
        self.name=name
        self.age=age
    def __str__(self):
        return str(self.id)+"-"+self.name+"-"+str(self.age)

Lớp Employee có constructor nhận các đối số id, name, age và nó là các thuộc tính của Employee.

đồng thời hàm __str__ Tui thiết kế để nó tự động hiển thị lên giao diện như mong muốn, bạn có thể sửa lại.

Bước 3: Tạo lớp mô hình “EmployeeModel.py” và có mã lệnh như dưới đây:

from PyQt6.QtCore import QAbstractListModel, Qt

class EmployeeModel(QAbstractListModel):
    def __init__(self,employees=None):
        super().__init__()
        self.employees=employees
    def data(self, index, role):
        if role == Qt.ItemDataRole.DisplayRole:
            emp = self.employees[index.row()]
            return str(emp)
    def rowCount(self, index):
        return len(self.employees)

Lớp EmployeeModel sẽ kế thừa từ lớp QAbstractListModel, chúng ta cần overridfe 3 hàm (bắt buộc):

  • hàm __init__ để khởi tạo các biến ban đầu cho model, ở đây Tui thiết kế cho nó nhận vào một danh sách Employee
  • hàm data có 2 đối số chính, đối số index (Có kiểu QIndex) là vị trí của dòng dữ liệu sẽ hiển thị lên QListView, do đó ra sẽ lấy index.row() để trả về integer để truy suất chính xác vị trí của dữ liệu trong danh sách employees. Ở đây chúng ta sử dụng Enum Qt.ItemDataRole để điều hướng cách thức hiển thị dữ liệu, Tui sẽ nó chi tiết ở bài học tiếp theo, trong bài này Chúng ta quan tâm tới DisplayRole có nghĩa là enum dùng để hiển thị dữ liệu. Hàm này dùng str(emp) để nó tự động gọi hàm __str__ mà ta thiết kế trong lớp Employee.py
  • hàm rowCount trả về số phần tử trong danh sách, bắt buộc chúng ta phải Override hàm này.

Bước 4: Thiết kế giao diện MainWindow.ui bằng Qt Designer

Ta chỉ cần kéo thả ListView trong nhóm Model-Based vào giao diện là xong.

Bước 5: Generate Python code cho MainWindow.ui

# Form implementation generated from reading ui file 'MainWindow.ui'
#
# Created by: PyQt6 UI code generator 6.5.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt6 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(351, 251)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.listView = QtWidgets.QListView(parent=self.centralwidget)
        self.listView.setGeometry(QtCore.QRect(10, 10, 311, 201))
        self.listView.setObjectName("listView")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 351, 22))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh - Model View"))

Bước 6: Tạo MainWindowEx để xử lý sự kiện, gán model cho widget

from Employee import Employee
from EmployeeModel import EmployeeModel
from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        employees=[]
        employees.append(Employee(1,"John",37))
        employees.append(Employee(2, "Peter", 24))
        employees.append(Employee(3, "Tom", 25))
        self.model=EmployeeModel(employees)
        self.listView.setModel(self.model)
    def show(self):
        self.MainWindow.show()

Ta thấy trong hàm override setupUi, Tui tạo ra 3 đối tượng employee và lưu trữ vào danh sách employees, sau đó nó được đưa vào EmployeeModel(employees)

cuối cùng ta gán model này cho QListVIew bằng hàm: setModel(model)

Bước 7: Viết MyApp.py để thực thi chương trình

from PyQt6.QtWidgets import QApplication, QMainWindow

from MainWindowEx import MainWindowEx

app=QApplication([])
myWindow=MainWindowEx()
myWindow.setupUi(QMainWindow())
myWindow.show()
app.exec()

Thực thi MyApp.py ta có kết quả như mong muốn.

Như vậy là tới đây Tui đã trình bày xong mô hình MVC, Qt Model/ViewController. Các bạn biết được lợi ích của mô hình và đặc biệt triển khai được một ví dụ minh họa cụ thể bằng Model View Architecture để hiển thị danh sách Employee lên QListView.

Sourcode đầy đủ của bài Model View này các bạn tải ở đây:

https://www.mediafire.com/file/7p75di7gwokzpa6/LearnModelViewPart1.rar/file

Bài học sau Tui tiếp tục trình bày chi tiết và chuyên sâu về model trong QListView, cung cấp thêm các chức năng: Thêm, Sửa, Xóa trên model. Các bạn chú ý theo dõi

Chúc các bạn thành công

One thought on “Bài 26: Kiến trúc Model View-PyQt6–Part 1”

Leave a Reply