Bài 26 Tui đã trình bày chi tiết về kiến trúc Model View trong PyQt6 cùng với một minh họa về hiển thị danh sách Employee lên QListView. Trong bài này Tui sẽ tiếp tục mở rộng bài minh họa về Employee, bằng cách bổ sung các chức năng: Thêm, Sửa, Xóa model cũng như viết mã lệnh hiệu chỉnh định dạng font chữ, màu chữ cho các item trên QListView bằng kiến trúc Model View. Đặc biệt Tui cung cấp thêm 3 menu item để serialize và deserialize dữ liệu JSon Array cho danh sách Employee, và menu item Exit.
Mô tả chi tiết chức năng của phần mềm:
- Chương trình dùng để quản lý Employee, thông tin của Employee bao gồm: id, name, age. Chương trình sử dụng kiến trúc Model View
- Dữ liệu hiển thị lên QListView có 2 dạng: Nếu Employee có age>=18 thì có biểu tượng icon màu xanh. Nếu age<18 thì có biểu tượng icon màu đỏ, chữ đỏ và nền vàng.
- Nút “New”: Chương trình sẽ xóa dữ liệu trên QLineEdit và focus tới ô ID
- Nút “Save”: Có 2 chức năng là lưu mới và lưu cập nhật, nếu Employee đang chọn trên QListView thì chương trình sẽ cập nhật, còn nếu không chọn Employee trên QListView thì chương trình sẽ lưu mới. Nếu lưu thành công dữ liệu sẽ hiển thị lên QListView
- Nút “Delete”: Chương trình sẽ xóa Employee đang chọn trên QListView, có hiển thị cửa sổ xác nhận muốn xóa hay không.
- Menu item “Export to Json”: Chương trình sẽ mở cửa sổ SaveFileDialog để người dùng xuất dữ liệu Employee có định dạng JSON ARRY ra ổ cứng bất kỳ
- Menu item “Import from Json”: Chương trình sẽ mở cửa sổ OpenFileDialog để người dùng chọn file JSON ARRAY đã lưu và hiểu thị danh sách Employee trong file này lên QListView.
- Menu item “Exit”: Chương trình sẽ thoát
Bây giờ chúng ta đi vào chi tiết từng bước:
Bước 1: Tạo dự án “LearnModelViewPart2” trong Pycharm như cấu trúc dưới đây:
- Thư mục “images” lưu trữ các hình ảnh và icon của phần mềm
- “Employee.py” là file mã lệnh python cho mô hình lớp Employee có các thuộc tính id, name và age
- “EmployeeModel.py” là lớp Model kế thừa từ QAbstractListModel để sử dụng trong mô hình Model View nhằm hiển thị dữ liệu danh sách Employee.
- “FileFactory.py” là file mã lệnh Python để Serialize và Deserialize dữ liệu Employee với định dạng Json Array
- “MainWindow.ui” là file giao diện của phần mềm, sử dụng Qt Designer tích hợp trong Pycharm để thiết kế.
- “MainWindow.py” là file Generate Python code của giao diện MainWindow.ui
- “MainWindowEx.py” là file mã lệnh python để xử lý sự kiện người dùng cũng như gán Model View
- “MyApp.py” là file mã lệnh python để thực thi chương trình.
Bước 2: Viết mã lệnh cho Employee.py
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 ở trên có 3 thuộc tính: id, name và age
hàm __str__ để hiển thị chuỗi dữ liệu format cho đối tượng Employee, tùy nhu cầu mà bạn có thể chỉnh sửa hàm này.
Bước 3: Viết mã lệnh cho “EmployeeModel.py”
import typing
from PyQt6 import QtGui
from PyQt6.QtCore import QAbstractListModel, Qt, QModelIndex
from PyQt6.QtGui import QImage
class EmployeeModel(QAbstractListModel):
def __init__(self,employees=None):
super().__init__()
self.employees=employees
def data(self, index: QModelIndex, role: int = ...) -> typing.Any:
emp = self.employees[index.row()]
if role == Qt.ItemDataRole.DisplayRole:
return str(emp)
if role==Qt.ItemDataRole.DecorationRole:
if emp.age<18:
return QImage("images/ic_notvalid.png")
else:
return QImage("images/ic_valid.png")
if role==Qt.ItemDataRole.ForegroundRole:
if emp.age < 18:
return QtGui.QColor(Qt.GlobalColor.red)
if role==Qt.ItemDataRole.BackgroundRole:
if emp.age < 18:
return QtGui.QColor(Qt.GlobalColor.yellow)
def rowCount(self, parent: QModelIndex = ...) -> int:
return len(self.employees)
Nếu Employee có age>=18 thì có biểu tượng icon màu xanh. Nếu age<18 thì có biểu tượng icon màu đỏ, chữ đỏ và nền vàng.
Ở đây Tui giải thích thêm hàm override data(), nó dùng để hiển thị dữ liệu theo từng trường hợp đặc biệt:
Role | Value | Ý nghĩa chức năng |
---|---|---|
Qt.ItemDataRole.DisplayRole | 0 | Dùng để hiển thị dữ liệu, kiểu QString. |
Qt.ItemDataRole.DecorationRole | 1 | Dùng để hiển thị định dạng trang trí, biểu tượng, kiểu QColor, QIcon hoặc QPixmap |
Qt.ItemDataRole.EditRole | 2 | Dùng để hiển thị khi dữ liệu chỉnh sửa, kiểu QString |
Qt.ItemDataRole.ToolTipRole | 3 | Dùng để hiển thị Tooltip, kiểu QString |
Qt.ItemDataRole.StatusTipRole | 4 | Dùng để hiển thị cho staus bar, kiểu QString |
Qt.ItemDataRole.WhatsThisRole | 5 | Dùng để hiển thị “What’s this”, kiểu QString |
Qt.ItemDataRole.FontRole | 6 | Dùng để hiển thị Font chữ |
Qt.ItemDataRole.TextAlignmentRole | 7 | Dùng để căn lề chữ |
Qt.ItemDataRole.BackgroundRole | 8 | Dùng để hiển thị màu nền |
Qt.ItemDataRole.ForegroundRole | 9 | Dùng để hiển thị màu chữ |
Qt.ItemDataRole.CheckStateRole | 10 | Dùng để kiểm tra trạng thái |
Qt.ItemDataRole.AccessibleTextRole | 11 | Dùng để truy suất text |
Qt.ItemDataRole.AccessibleDescriptionRole | 12 | Dùng để truy suất mô tả |
Qt.ItemDataRole.SizeHintRole | 13 | Dùng để thiết lập size hint cho item |
Qt.ItemDataRole.InitialSortOrderRole | 14 | Dùng để khởi tạo sắp xếp |
Qt.ItemDataRole.UserRole | 15 | Dùng để xử lý dữ liệu object trong item |
Bước 3: Viết mã lệnh cho FileFactory.py
import json
import os
class FileFactory:
#path: path to serialize array of Employee
#arrData: array of Employee
def writeData(self,path,arrData):
jsonString = json.dumps([item.__dict__ for item in arrData],default=str)
jsonFile = open(path, "w")
jsonFile.write(jsonString)
jsonFile.close()
#path: path to deserialize array of Employee
#ClassName: Employee
def readData(self,path,ClassName):
if os.path.isfile(path) == False:
return []
file = open(path, "r")
# Reading from file
self.arrData = json.loads(file.read(), object_hook=lambda d: ClassName(**d))
file.close()
return self.arrData
Lớp FileFactory này các bạn đã làm quen nhiều lần ở các bài học trước, nhiệm vụ của writeData là Serialize danh sách Employee xuống ổ cứng với định dạng JSON Array. Hàm readData để Deserialize chuỗi JSON Array dưới ổ cứng lên bộ nhớ bằng mô hình hóa hướng đối tượng các Employee.
Bước 4: Thiết kế giao diện MainWindow.ui bằng Qt Designer (Hoặc Qt Creator)
Ta kéo thả các Widget và đặt tên cho chúng như hình tổng quan dưới đây:
Các widget QLineEdit, QPushButton, QListView các bạn đã làm nhiều lần rồi nên Tui không trình bày lại, các bạn chỉ cần kéo thả ra giao diện và đặt tên như trên là được.
Tui sẽ trình bày Menu vì bạn chưa được học. Cấu trúc Menu của PyQt6 gồm:
- Thanh nằm ngang chứa các menu gọi là QMenuBar
- Bên trong QMenu chứa các Menu được gọi là QMenu
- Bên trong QMenu khi nhấn vào nó xổ danh sách xuống gọi là các QAction
Tức với giao diện ở trên thì:
- Thanh nằm ngang ở trên cùng là QMenuBar
- “System” là QMenu
- “Export to JSon” là QAction
- “Import from JSon” là QAction
- “Exit” là QAction
Và signal ta dùng cho các QAction là triggered
Ta tiếp tục thiết kế QMenuBar nhé. Nhìn vào nhóm Menu Bar ở bên trên giao diện, ngay chỗ “Type Here” bạn gõ vào System và nhấn Enter thì ta có kết quả như hình bên dưới:
Tiếp tục chỗ “Type Here” ở hình trên ta gõ vào “Export to JSon” rồi nhấn Enter để tạo QAction, ta có kết quả:
Để tạo thanh nằm ngang ngăn cách các QAction ta nhấn “Add Separator”, kết quả sẽ xuất hiện đường nằm ngang:
Tiếp tục, chỗ “Type Here” ta gõ “Import from JSon” để tạo QAction rồi nhấn Enter, kết quả:
Để tạo thêm đường ngang giữa các QAction ta nhấn “Add Separator”, kết quả:
Cuối cùng ta gõ “Exit” vào mục Type Here, kết quả:
Sau khi tạo xong các QAction cho QMenu ta tiến hành gán các Icon cho các QAction để nó thẩm mỹ hơn, để tạo Icon thì ta cứ chọn QAction trước, sau đó trong thuộc tính Icon ta chọn “Choose File…” để trỏ tới hình mình mong muốn:
Cứ như vậy, các bạn lặp lại thao tác gán Icon cho các QAction còn lại, kết quả cuối cùng:
Bước 5: Generate Python code “MainWindow.py” cho giao diện “MainWindow.ui“. Cách Generate code đã được học kỹ ở các bài đầu tiên
# Form implementation generated from reading ui file 'MainWindow.ui'
#
# Created by: PyQt6 UI code generator 6.4.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(361, 349)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap("images/ic_logo.jpg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
MainWindow.setWindowIcon(icon)
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.listViewEmployee = QtWidgets.QListView(parent=self.centralwidget)
self.listViewEmployee.setGeometry(QtCore.QRect(10, 10, 331, 161))
self.listViewEmployee.setObjectName("listViewEmployee")
self.label = QtWidgets.QLabel(parent=self.centralwidget)
self.label.setGeometry(QtCore.QRect(20, 180, 81, 16))
self.label.setObjectName("label")
self.lineEditId = QtWidgets.QLineEdit(parent=self.centralwidget)
self.lineEditId.setGeometry(QtCore.QRect(130, 180, 201, 22))
self.lineEditId.setObjectName("lineEditId")
self.lineEditName = QtWidgets.QLineEdit(parent=self.centralwidget)
self.lineEditName.setGeometry(QtCore.QRect(130, 210, 201, 22))
self.lineEditName.setObjectName("lineEditName")
self.label_2 = QtWidgets.QLabel(parent=self.centralwidget)
self.label_2.setGeometry(QtCore.QRect(20, 210, 101, 16))
self.label_2.setObjectName("label_2")
self.label_3 = QtWidgets.QLabel(parent=self.centralwidget)
self.label_3.setGeometry(QtCore.QRect(20, 240, 101, 16))
self.label_3.setObjectName("label_3")
self.lineEditAge = QtWidgets.QLineEdit(parent=self.centralwidget)
self.lineEditAge.setGeometry(QtCore.QRect(130, 240, 201, 22))
self.lineEditAge.setObjectName("lineEditAge")
self.pushButtonNew = QtWidgets.QPushButton(parent=self.centralwidget)
self.pushButtonNew.setGeometry(QtCore.QRect(40, 270, 71, 28))
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap("images/ic_new.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.pushButtonNew.setIcon(icon1)
self.pushButtonNew.setIconSize(QtCore.QSize(24, 24))
self.pushButtonNew.setObjectName("pushButtonNew")
self.pushButtonSave = QtWidgets.QPushButton(parent=self.centralwidget)
self.pushButtonSave.setGeometry(QtCore.QRect(150, 270, 71, 28))
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap("images/ic_save.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.pushButtonSave.setIcon(icon2)
self.pushButtonSave.setIconSize(QtCore.QSize(24, 24))
self.pushButtonSave.setObjectName("pushButtonSave")
self.pushButtonDelete = QtWidgets.QPushButton(parent=self.centralwidget)
self.pushButtonDelete.setGeometry(QtCore.QRect(260, 270, 71, 28))
icon3 = QtGui.QIcon()
icon3.addPixmap(QtGui.QPixmap("images/ic_delete.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.pushButtonDelete.setIcon(icon3)
self.pushButtonDelete.setObjectName("pushButtonDelete")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 361, 26))
self.menubar.setObjectName("menubar")
self.menuSystem = QtWidgets.QMenu(parent=self.menubar)
self.menuSystem.setLayoutDirection(QtCore.Qt.LayoutDirection.LeftToRight)
self.menuSystem.setTearOffEnabled(False)
self.menuSystem.setSeparatorsCollapsible(False)
self.menuSystem.setToolTipsVisible(False)
self.menuSystem.setObjectName("menuSystem")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.actionExport_to_JSon = QtGui.QAction(parent=MainWindow)
icon4 = QtGui.QIcon()
icon4.addPixmap(QtGui.QPixmap("images/ic_export.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.actionExport_to_JSon.setIcon(icon4)
self.actionExport_to_JSon.setObjectName("actionExport_to_JSon")
self.actionImport_from_JSon = QtGui.QAction(parent=MainWindow)
icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap("images/ic_import.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.actionImport_from_JSon.setIcon(icon5)
self.actionImport_from_JSon.setObjectName("actionImport_from_JSon")
self.actionExit = QtGui.QAction(parent=MainWindow)
icon6 = QtGui.QIcon()
icon6.addPixmap(QtGui.QPixmap("images/ic_exit.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.actionExit.setIcon(icon6)
self.actionExit.setObjectName("actionExit")
self.menuSystem.addAction(self.actionExport_to_JSon)
self.menuSystem.addSeparator()
self.menuSystem.addAction(self.actionImport_from_JSon)
self.menuSystem.addSeparator()
self.menuSystem.addAction(self.actionExit)
self.menubar.addAction(self.menuSystem.menuAction())
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"))
self.label.setText(_translate("MainWindow", "Employee ID:"))
self.label_2.setText(_translate("MainWindow", "Employee Name:"))
self.label_3.setText(_translate("MainWindow", "Age of Employee:"))
self.pushButtonNew.setText(_translate("MainWindow", "New"))
self.pushButtonSave.setText(_translate("MainWindow", "Save"))
self.pushButtonDelete.setText(_translate("MainWindow", "Delete"))
self.menuSystem.setTitle(_translate("MainWindow", "System"))
self.actionExport_to_JSon.setText(_translate("MainWindow", "Export to JSon"))
self.actionImport_from_JSon.setText(_translate("MainWindow", "Import from JSon"))
self.actionExit.setText(_translate("MainWindow", "Exit"))
Bước 6: Viết mã lệnh “MainWindowEx.py“, mã lệnh trong này kế thừa từ lớp được Generate trong bước trước để xử lý sự kiện người dùng và nó giúp ta không bị ảnh hưởng khi trong tương lai Generate lại python code.
Constructor của lớp này Tui định nghĩa 2 biến đối tượng như dưới đây:
from PyQt6.QtWidgets import QFileDialog, QMessageBox
from Employee import Employee
from EmployeeModel import EmployeeModel
from FileFactory import FileFactory
from MainWindow import Ui_MainWindow
class MainWindowEx(Ui_MainWindow):
def __init__(self):
self.employees=[]
self.selectedEmployee=None
self.fileFactory=FileFactory()
- Biến employees để lưu danh sách đối tượng Employee
- Biến selectedEmployee để lưu Employee hiện tại đang lựa chọn trên giao diện QListView, nó được dùng để xử lý lưu mới hay lưu cập nhật Employee
- Biến fileFactory là đối tượng để Serialize và Deserialize danh sách Employee với định dạng JSON ARRAY
Tiếp theo là hàm setupUi() được override để xử lý nạp giao diện cũng như xử lý signal mà người dùng tương tác:
def setupUi(self, MainWindow):
super().setupUi(MainWindow)
self.MainWindow=MainWindow
self.model=EmployeeModel(self.employees)
self.listViewEmployee.setModel(self.model)
self.pushButtonNew.clicked.connect(self.processNew)
self.pushButtonSave.clicked.connect(self.processSave)
#self.listViewEmployee.clicked.connect(self.processClicked)
self.listViewEmployee.selectionModel().selectionChanged.connect(self.processSelection)
self.pushButtonDelete.clicked.connect(self.processDelete)
self.actionExport_to_JSon.triggered.connect(self.processExportJson)
self.actionImport_from_JSon.triggered.connect(self.processImportJson)
Mã lệnh xử lý các Signal ở trên có 2 cái mới:
- Xử lý sự kiện người dùng lựa chọn trên QListView (Bao gồm cả click chuột và di chuyển phím) ta dùng singal selectionChanged nằm trong selectionModel()
- Xử lý sự kiện người dùng lựa chọn QAction ta dùng singal triggered
Hàm processNew() sẽ xóa các dữ liệu trên QLineEdit và focus vào ô ID để giúp người dùng nhập liệu nhanh chóng, đồng thời gán selectedEmployee=None để đánh dấu đây là khởi đầu cho thao tác lưu mới một Employee.
def processNew(self):
self.lineEditId.setText("")
self.lineEditName.setText("")
self.lineEditAge.setText("")
self.lineEditId.setFocus()
self.selectedEmployee=None
Tiếp theo là hàm processSave().
def processSave(self):
id=self.lineEditId.text()
name=self.lineEditName.text()
age=int(self.lineEditAge.text())
emp=Employee(id,name,age)
if self.selectedEmployee==None:
self.employees.append(emp)
self.selectedEmployee=emp
else:
index=self.employees.index(self.selectedEmployee)
self.selectedEmployee=emp
self.employees[index]=self.selectedEmployee
self.model.layoutChanged.emit()
Hàm Save sẽ có 2 chức năng là lưu mới và lưu cập nhật Employee.
Nếu selectedEmployee = None thì chương trình lưu mới, còn khác None thì chương trình lưu cập nhật.
Sau khi dữ liệu được cập nhật trong biến employees, lúc này ta sẽ nói ModelView cập nhật lại giao diện bằng hàm layoutChanged.emit()
Tiếp theo là hàm processSelection(). Hàm này sẽ lấy dòng dữ liệu mà người dùng chọn trên QListView bằng hàm selectedIndexes(). Hàm selectedIndexes() trả về danh sách các QIndex được lựa chọn, ở đây ta chỉ xử lý chọn 1 dòng dữ liệu, do đó ta có indexes[0].row() là trả về vị trí kiểu integer để lấy chính xác đối tượng Employee tại vị trí đang chọn:
def processSelection(self):
indexes=self.listViewEmployee.selectedIndexes()
if indexes:
row=indexes[0].row()
emp=self.employees[row]
self.lineEditId.setText(str(emp.id))
self.lineEditName.setText(emp.name)
self.lineEditAge.setText(str(emp.age))
self.selectedEmployee=emp
Sau khi có đối tượng Employee đang chọn, ta hiển thị lên các QLineEdit đồng thời gán biến selectedEmployee cho biến đối tượng này, mục đích để hỗ trợ cho thao tác lưu cập nhật, ví dụ dưới đây là người dùng chọn Vitor:
Tiếp tới nữa là hàm “processDelete()“, hàm này tiến hành xóa Employee đang chọn trên QListView:
def processDelete(self):
dlg = QMessageBox(self.MainWindow)
if self.selectedEmployee == None:
dlg.setWindowTitle("Deleteing error")
dlg.setIcon(QMessageBox.Icon.Critical)
dlg.setText("You have to select a Product to delete")
dlg.exec()
return
dlg.setWindowTitle("Confirmation Deleting")
dlg.setText("Are you sure you want to delete?")
buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
dlg.setStandardButtons(buttons)
button = dlg.exec()
if button == QMessageBox.StandardButton.Yes:
self.employees.remove(self.selectedEmployee)
self.model.layoutChanged.emit()
self.selectedEmployee = None
self.processNew()
Lệnh trên Tui tiếp tục dùng QMessage dialog để xác thực xem người dùng có thực sự muốn xóa hay không, chọn Yes thì xóa:
tương tự như thao tác lưu mới hay cập nhật, thì thao tác xóa cũng gọi hàm emit() để model view cập nhật lại giao diện.
Để Serialize và Deserialize dữ liệu ra JSON ARRAY ta có 2 hàm được Tui viết dưới đây.
Thứ nhất là hàm processExportJson() dùng để xuất toàn bộ dữ liệu Employee ra JSON ARRAY:
def processExportJson(self):
# setup for QFileDialog
filters = "Dataset (*.json);;All files(*)"
filename, selected_filter = QFileDialog.getSaveFileName(
self.MainWindow,
filter=filters,
)
self.fileFactory.writeData(filename,self.employees)
Mã lệnh trên dùng QFileDialog mà dạng SaveFile, lúc này chương trình sẽ hiển thị cửa sổ lưu file cho ta lựa chọn nơi lưu trữ tùy ý.
Ta vào menu System rồi chọn “Export to JSon”:
Sau đó chọn nơi lưu trữ, đặt tên file và nhấn nút Save:
Sau khi lưu thành công ta có cấu dữ liệu của database.json như sau:
[{"id": "1", "name": "Peter", "age": 25}, {"id": "2", "name": "John", "age": 20}, {"id": "3", "name": "Tom", "age": 17}, {"id": "4", "name": "Bjorn", "age": 24}, {"id": "5", "name": "Vitor", "age": 27}, {"id": "6", "name": "Manuel", "age": 16}, {"id": "7", "name": "Daisy", "age": 20}]
Cuối cùng là hàm “processImportJson()” dùng để đọc dữ liệu từ JSON và phục hồi lên bộ nhớ đồng thời đưa về mô hình hóa hướng đối tượng và hiển thị lên QListView:
def processImportJson(self):
# setup for QFileDialog
filters = "Dataset (*.json);;All files(*)"
filename, selected_filter = QFileDialog.getOpenFileName(
self.MainWindow,
filter=filters,
)
arr=self.fileFactory.readData(filename,Employee)
self.employees.clear()
for i in range(len(arr)):
self.employees.append(arr[i])
self.model.layoutChanged.emit()
Mã lệnh ở trên sẽ dùng QFileDialog.getOpenFileName để mở cửa sổ File Dialog, cho người dùng lựa chọn file dữ liệu để nạp lên giao diện.
Sau khi nạp xong dữ liệu lên bộ nhớ, ta gọi hàm self.model.layoutChanged.emit() để hiển thị dữ liệu lên giao diện:
Từ giao diện trống, ta chọn “Import from JSon”, sau đó chọn file “database.json” rồi bấm “Open”, ta sẽ có kết quả như mong muốn, các Emloyee được nạp lên QListView.
Dưới đây là mã lệnh đầy đủ của “MainWindowEx.py”:
from PyQt6.QtWidgets import QFileDialog, QMessageBox
from Employee import Employee
from EmployeeModel import EmployeeModel
from FileFactory import FileFactory
from MainWindow import Ui_MainWindow
class MainWindowEx(Ui_MainWindow):
def __init__(self):
self.employees=[]
self.selectedEmployee=None
self.fileFactory=FileFactory()
def setupUi(self, MainWindow):
super().setupUi(MainWindow)
self.MainWindow=MainWindow
self.model=EmployeeModel(self.employees)
self.listViewEmployee.setModel(self.model)
self.pushButtonNew.clicked.connect(self.processNew)
self.pushButtonSave.clicked.connect(self.processSave)
#self.listViewEmployee.clicked.connect(self.processClicked)
self.listViewEmployee.selectionModel().selectionChanged.connect(self.processSelection)
self.pushButtonDelete.clicked.connect(self.processDelete)
self.actionExport_to_JSon.triggered.connect(self.processExportJson)
self.actionImport_from_JSon.triggered.connect(self.processImportJson)
def processNew(self):
self.lineEditId.setText("")
self.lineEditName.setText("")
self.lineEditAge.setText("")
self.lineEditId.setFocus()
self.selectedEmployee=None
def processSave(self):
id=self.lineEditId.text()
name=self.lineEditName.text()
age=int(self.lineEditAge.text())
emp=Employee(id,name,age)
if self.selectedEmployee==None:
self.employees.append(emp)
self.selectedEmployee=emp
else:
index=self.employees.index(self.selectedEmployee)
self.selectedEmployee=emp
self.employees[index]=self.selectedEmployee
self.model.layoutChanged.emit()
def processSelection(self):
indexes=self.listViewEmployee.selectedIndexes()
if indexes:
row=indexes[0].row()
emp=self.employees[row]
self.lineEditId.setText(str(emp.id))
self.lineEditName.setText(emp.name)
self.lineEditAge.setText(str(emp.age))
self.selectedEmployee=emp
def processDelete(self):
dlg = QMessageBox(self.MainWindow)
if self.selectedEmployee == None:
dlg.setWindowTitle("Deleteing error")
dlg.setIcon(QMessageBox.Icon.Critical)
dlg.setText("You have to select a Product to delete")
dlg.exec()
return
dlg.setWindowTitle("Confirmation Deleting")
dlg.setText("Are you sure you want to delete?")
buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
dlg.setStandardButtons(buttons)
button = dlg.exec()
if button == QMessageBox.StandardButton.Yes:
self.employees.remove(self.selectedEmployee)
self.model.layoutChanged.emit()
self.selectedEmployee = None
self.processNew()
def processExportJson(self):
# setup for QFileDialog
filters = "Dataset (*.json);;All files(*)"
filename, selected_filter = QFileDialog.getSaveFileName(
self.MainWindow,
filter=filters,
)
self.fileFactory.writeData(filename,self.employees)
def processImportJson(self):
# setup for QFileDialog
filters = "Dataset (*.json);;All files(*)"
filename, selected_filter = QFileDialog.getOpenFileName(
self.MainWindow,
filter=filters,
)
arr=self.fileFactory.readData(filename,Employee)
self.employees.clear()
for i in range(len(arr)):
self.employees.append(arr[i])
self.model.layoutChanged.emit()
def show(self):
self.MainWindow.show()
Bước 7: Cuối cùng ta viết mã lệnh “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 chương trình ta có kết quả như mong muốn:
Như vậy Tui đã trình bày chi tiết và kỹ lưỡng xong phần ứng dụng kiến trúc Model View để xử lý dữ liệu với QListView. Hướng dẫn các bạn cách dùng QMenuBar, QMenu, QAction cũng như các signal triggered tương ứng, đã minh họa đầy đủ và chi tiết các chức năng xem, thêm, sửa, xóa dữ liệu. Các bạn chú ý lập trình lại bài này nhiều lần để ứng dụng vào triển khai dự án trong thực tế.
Mã nguồn của dự án các bạn tải ở đây:
https://www.mediafire.com/file/e40x1sw76s71jxl/LearnModelViewPart2.rar/file
Bài học sau Tui sẽ tiếp tục trình bày ứng dụng Model View vào QTableView để hiển thị và tương tác dữ liệu dạng bảng, một trong những kỹ thuật rất quan trọng và hữu ích. Các bạn chú ý theo dõi
Chúc các bạn thành công.