Bài 21: QDateTimeEdit – Basic Widgets – PyQt6

Bài học QDateEdit chúng ta đã được học về widget xử lý Ngày-Tháng-Năm. Còn bài học QTimeEdit chúng ta đã được học về widget xử lý Giờ-Phút-Giây. Các bài học đều trình bày kỹ lưỡng về lý thuyết, cách lập trình và áp dụng các widget vào thực tế. Trong bài học này, Tui sẽ tiếp tục trình bày về QDateTimeEdit, nó là một widget kết hợp giữa QDateEdit và QTimeEdit.

Ví dụ dưới đây là phần mềm quản lý sản phẩm hơi phức tạp một xíu, phần mềm có sử dụng QDateTimeEdit , cuối bài học này Tui sẽ trình bày chi tiết cách thiết kế và lập trình phần mềm này:

Trước khi vào coding ta xem một số thuộc tính, phương thức, signal thường dùng của widget này:

Thuộc tính, phương thức, signalÝ nghĩa chức năng
QDateTimeEdit(self)Constructor để tạo đối tượng QDateTimeEdit. 
QDateTimeEdit(self,calendarPopup=True)Constructor để tạo đối tượng QDateTimeEdit. Nếu dùng constructor này thì người sử dụng sẽ dùng Calendar popup để lựa chọn
date()Hàm trả về ngày tháng năm mà người sử dụng nhập trên giao diện, nó có kiểu QDate, do đó muốn chuyển về date trong python thuần túy thì ta gọi thêm hàm toPyDate()
time()Hàm trả về đối tượng QTime. Do đó để chuyển về Python datetime.time ta dùng hàm toPyTime()
dateTime()Hàm trả về đối tượng QDateTime. Do đó để chuyển về Python datetime.datetime ta dùng hàm toPyDateTime()
setDate(date)Hàm thiết lập Ngày – Tháng – Năm cho widget
setTime(time)Hàm thiết lập Giờ-Phút-Giây cho widget
setDateTime(dateTime)Hàm thiết lập Ngày – Tháng – Năm và Giờ-Phút-Giây cho widget
minimumDateThuộc tính thiết lập ngày nhỏ nhất
maximumDateThuộc tính thiết lập ngày lớn nhất
minimumTimeThuộc tính thiết lập time nhỏ nhất
maximumTimeThuộc tính thiết lập time lớn nhất
minimumDateTimeThuộc tính thiết lập date time nhỏ nhất
maximumDateTimeThuộc tính thiết lập date time lớn nhất
setDisplayFormat(format)Phương thức xác định cách thức hiện thị dữ liệu Ngày – Tháng – Năm, giờ phút giây lên giao diện
Ví dụ:
setDisplayFormat(“dd/MM/yyyy HH:mm:ss AP”)
editingFinishedSignal để xử lý khi người dùng hoàn tất việc nhập liệu Ngày – Tháng – Năm – Giờ- Phút- Giây (đã chọn và nhấn phím Enter)
dateTimeChangedSignal để xử lý khi người dùng chọn lựa Ngày – Tháng – Năm-Giờ-Phút-Giây trên Widget

Dưới đây là các bước khai báo và sử dụng QDateTimeEdit:

Bước 1: Khai báo và khởi tạo đối tượng QDateTimeEdit

self.datetime_edit = QDateTimeEdit(self, calendarPopup=True)

Bước 2: Thiết lập các giá trị cho các thuộc tính

time = QTime(10, 15, 35)
date = QDate(2024, 1, 1)
self.datetime_edit.setTime(time)
self.datetime_edit.setDate(date)
self.datetime_edit.setDisplayFormat("dd/MM/yyyy HH:mm:ss AP")

Mã lệnh ở trên thiết lập QTime (giờ , phút, giây) và QDate(Năm, tháng, ngày) cho QDateTimeEdit thông qua các hàm setTime(time) và setDate(date).

Hoặc ta thiết lập QDateTime (năm, tháng, ngày, giờ , phút, giây):

date_time=QDateTime(2024,1,1,10,15,35)
self.datetime_edit.setDateTime(date_time)

Chi tiết format “dd/MM/yyyy HH:mm:ss AP” đã được trình bày ở bài 19 bài 20.

Bước 3: Thiết lập signal cho QDateTimeEdit khi người sử dụng lựa chọn xong

self.datetime_edit.editingFinished.connect(self.update)
def update(self):
    value = self.datetime_edit.dateTime()
    print(str(value.toPyDateTime()))

Hàm dateTime() sẽ trả về dữ liệu có kiểu QDateTime, nên muốn đưa về Python datetime thì ta gọi thêm hàm toPyDateTime()

Tuy nhiên editingFinished signal thì người dùng cần nhấn phím Enter để xác nhận. Do đó để tiện lại ta có thể dùng dateTimeChanged signal, bất cứ khi nào người dùng lựa chọn date-time trên giao diện thì ta sẽ lấy được dữ liệu này:

self.datetime_edit.dateTimeChanged.connect(self.update)
def update(self):
    value = self.datetime_edit.dateTime()
    print(str(value.toPyDateTime()))

Lưu ý nhiều signal có thể triệu gọi cùng 1 hàm (ở trên Tui ví dụ signal editingFinished dateTimeChanged cùng triệu gọi hàm update).

Dưới đây là Full code minh họa:

from PyQt6.QtCore import QDateTime, QTime, QDate
from PyQt6.QtWidgets import QApplication, QWidget, QDateTimeEdit, QLabel, QFormLayout


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle('PyQt QDateTimeEdit')
        self.setMinimumWidth(200)

        layout = QFormLayout()
        self.setLayout(layout)

        self.datetime_edit = QDateTimeEdit(self, calendarPopup=True)
        time = QTime(10, 15, 35)
        date = QDate(2024, 1, 1)
        self.datetime_edit.setTime(time)
        self.datetime_edit.setDate(date)
        date_time=QDateTime(2024,1,1,10,15,35)
        self.datetime_edit.setDateTime(date_time)
        self.datetime_edit.setDisplayFormat("dd/MM/yyyy HH:mm:ss AP")

        self.datetime_edit.editingFinished.connect(self.update)

        self.datetime_edit.dateTimeChanged.connect(self.update)

        self.result_label = QLabel('', self)

        layout.addRow('Date:', self.datetime_edit)
        layout.addRow(self.result_label)

        self.show()

    def update(self):
        value = self.datetime_edit.dateTime()
        print(str(value.toPyDateTime()))
        self.result_label.setText(value.toString("yyyy-MM-dd HH:mm"))

if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    app.exec()

Chạy mã lệnh trên ta có kết quả:

Ta chọn ngày tháng năm trong QDateTimeEdit thì ngay lập tức nó sẽ được hiển thị vào QLabel ở bên dưới.

Bây giờ Tui sẽ hướng dẫn các bạn cách thức áp dụng QDateTimeEdit vào việc phát triển phần mềm quản lý sản phẩm như dưới đây:

Mô tả các chức năng của phần mềm:

  • Chương trình gồm có DANH MỤC và SẢN PHẨM. 1 Danh Mục có nhiều Sản phẩm, và một sản phẩm chỉ thuộc về một danh mục.
  • Thông tin chi tiết của một Danh Mục bao gồm: Mã Danh Mục, Tên Danh Mục
  • Thông tin chi tiết của một Sản Phẩm bao gồm: Mã sản phẩm, tên sản phẩm, giá, thời gian tracking (cứ hiểu đại là thời gian muốn theo làm mốc theo dõi, bạn có thể tạo đại một thuộc tính nào đó chứa ngày tháng năm giờ phút giây).
  • Chương trình sẽ nạp dữ liệu có sẵn trong “database.json”, mô hình hóa vào các lớp đối tượng Dataset (là đối tượng chứa nhiều Category), Category (Là đối tượng chứa nhiều Product) và Product.
  • Danh sách các Category sẽ được nạp vào QCombobox.
  • Khi chọn Category nào trong QCombobox thì danh sách Product của Category này sẽ được hiển thị vào QListWidget
  • Khi chọn Product trong QListWidget thì thông tin chi tiết của nó sẽ được hiển thị vào nhóm chi tiết bên màn hình “Product Details”.
  • Nút “New” sẽ xóa trắng các ô nhập liệu và focus tới ô ID.
  • Nút “Save” sẽ lưu Product vào các selected Category tương ứng. Đặc biệt nút “Save” sẽ xử lý 2 tác vụ: Lưu thêm mới Product và lưu Cập nhật Product. Đồng thời cập nhật lại cơ sở dữ liệu
  • Nút “Delete” sẽ xóa Product đang chọn, đồng thời cập nhật lại cơ sở dữ liệu.

Ta tạo một dự án tên “LearnQDateTimeEdit” trong Pycharm, có cấu trúc tập tin và thư mục như dưới đây:

  • Thư mục “images” lưu trữ các icon của phần mềm như Window Icon, New Icon, Save Icon, Delete Icon.
  • Product.py Là file lưu mã lệnh của lớp Product để định nghĩa một Product bao gồm: id,name,price,timetracking
  • Category.py Là file lưu mã lệnh của lớp Category gồm các thuộc tính: id, name. Lớp này sẽ cung cấp List và các phương thức để lưu trữ và xử lý danh sách Product, chẳng hạn các chức năng: thêm, truy suất, sửa, xóa Product
  • Dataset.py là file lưu mã lệnh của lớp Dataset, lớp này cung cấp list và các phương thức để lưu trữ và xử lý danh sách Category, chẳng hạn các chức năng: thêm, truy suất, sửa, xóa Category. Vì đặc thù khi mô hình hóa lại dữ liệu chưa triệt để do nó lưu dạng Dictionary nên Tui có bổ sung thêm hàm reModel() để mô hình hóa toàn bộ lại hướng đối tượng.
  • FileFactory.py” là mã lệnh dùng để ghi toàn bộ dữ liệu xuống ổ cứng với định dạng JSonArray, cũng như phục hồi và mô hình hóa lại dữ liệu hướng đối tượng
  • MainWindow.ui” là file thiết kế giao diện bằng Qt Designer
  • MainWindow.py” là file python code được generate tự động từ “MainWindow.ui”. Cách thiết kế và tích hợp công cụ tự động Generate đã được hướng dẫn rất chi tiết ở những bài học đầu tiên, các bạn cần xem lại
  • MainWindowEx.py” là mã lệnh ta bổ sung, kế thừ từ lớp được generate trong MainWindow.py để xử lý các sự kiện người dùng
  • MyApp.py” là mã lệnh để thực thi chương trình
  • database.json” là file JSonArray để lưu trữ các dữ liệu mà người dùng thao tác trên phần mềm.

Bây giờ chúng ta đi vào chi tiết của từng thành phần:

Bước 0: Cấu trúc dữ liệu của “database.json” để chúng ta sử dụng thông qua mô hình hóa:

{
    "categories": [
        {
            "id": 1,
            "name": "Drinking",
            "products": [
                {
                    "id": 1,
                    "name": "Coca",
                    "price": 15,
                    "timetracking": "2023-12-25 14:30:00"
                },
                {
                    "id": 2,
                    "name": "Pepsi",
                    "price": 30,
                    "timetracking": "2023-11-18 4:3:5"
                },
                {
                    "id": 3,
                    "name": "Sting",
                    "price": 20,
                    "timetracking": "2023-12-27 2:4:5"
                }
            ]
        },
        {
            "id": 2,
            "name": "Fast Food",
            "products": [
                {
                    "id": 4,
                    "name": "Hamburger",
                    "price": 70,
                    "timetracking": "2023-12-15 14:23:46"
                },
                {
                    "id": 5,
                    "name": "Sandwich",
                    "price": 80,
                    "timetracking": "2023-12-18 14:13:45"
                },
                {
                    "id": 6,
                    "name": "Cheeseburger",
                    "price": 25,
                    "timetracking": "2023-12-14 15:43:5"
                },
                {
                    "id": 7,
                    "name": "Hot dog",
                    "price": 35,
                    "timetracking": "2023-12-12 12:30:5"
                },
                {
                    "id": 8,
                    "name": "Fried chicken",
                    "price": 68,
                    "timetracking": "2023-12-22 14:13:15"
                },
                {
                    "id": 9,
                    "name": "Baguette",
                    "price": 20,
                    "timetracking": "2023-12-25 8:7:20"
                },
                {
                    "id": 10,
                    "name": "Pretzel",
                    "price": 17,
                    "timetracking": "2023-12-27 9:13:15"
                },
                {
                    "id": 11,
                    "name": "Pizza",
                    "price": 70,
                    "timetracking": "2023-12-26 12:17:15"
                },
                {
                    "id": 12,
                    "name": "Sausage",
                    "price": 65,
                    "timetracking": "2023-12-26 10:08:15"
                },
                {
                    "id": 13,
                    "name": "Bacon",
                    "price": 100,
                    "timetracking": "2023-12-28 12:12:10"
                },
                {
                    "id": 14,
                    "name": "ANIMAL STYLE BURGER",
                    "price": 100,
                    "timetracking": "2023-12-28 12:12:10"
                },
                {
                    "id": 15,
                    "name": "SPICY CHICKEN SANDWICH",
                    "price": 100,
                    "timetracking": "2023-12-28 12:12:10"
                },
                {
                    "id": 16,
                    "name": "BISCUITS POPEYES",
                    "price": 100,
                    "timetracking": "2023-12-28 12:12:10"
                }
            ]
        },
        {
            "id": 3,
            "name": "Fruits",
            "products": [
                {
                    "id": 14,
                    "name": "Orange",
                    "price": 26,
                    "timetracking": "2023-12-25 9:30:0"
                },
                {
                    "id": 15,
                    "name": "Strawberry",
                    "price": 30,
                    "timetracking": "2023-11-18 4:3:5"
                },
                {
                    "id": 16,
                    "name": "Water Melon",
                    "price": 28,
                    "timetracking": "2023-11-15 14:3:5"
                }
            ]
        }
    ]
}

Bước 1: Viết mã lệnh cho “Product.py”, mã lệnh được thể hiện như dưới đây:

class Product:
    def __init__(self,id,name,price,timetracking):
        self.id=id
        self.name=name
        self.price=price
        self.timetracking=timetracking
    def __str__(self):
        return str(self.id)+"-"+self.name+"("+ str(self.price)+" $)"

Ta khai báo lớp Product cùng với constructor và các đối số chính xác như trên để việc serialize và deserialize JSon data được chính xác, Từng Product sẽ là 1 JSonObject, và mỗi một Category sẽ có 1 danh sách JSonArray các Product.

hàm __str__() để truy suất dữ liệu đối tượng và đưa về chuỗi hiển thị.

Bước 2: Viết mã lệnh cho “Category.py” để lưu trữ và xử lý danh sách các Product, mã lệnh được thể hiện như dưới đây:

from Product import Product

class Category:
    def __init__(self,id,name,products=None):
        self.id=id
        self.name=name
        self.products=products
        if self.products==None:
            self.products=[]
    def item(self,index)->Product:
        return self.products[index]
    def add(self,p):
        self.products.append(p)
    def addAll(self,products):
        for i in range(len(products)):
            p=products[i]
            self.add(p)
    def index(self,p):
        i=self.products.index(p)
        return i
    def update(self,index,p):
        self.products[index]=p
        return self.products[index]
    def removeByIndex(self,index):
        return self.products.pop(index)
    def removeByItem(self,item):
        self.products.remove(item)
    def clear(self):
        self.products.clear()
    def size(self):
        return len(self.products)
    def __str__(self):
        return  str(self.id)+"-"+self.name

Lớp Category ở trên có id và name, đồng thời Tui thiết kế các phương thức để ta có thể sử dụng lưu danh sách Product, cũng như tương tác các danh sách chẳng hạn:

  • Hàm item(self,index) là hàm trả về Product theo index
  • Hàm index(self,p) là hàm trả về vị trí của Product trong Catalog
  • Hàm add(self,p) để thêm mới một Product vào Catalog
  • Hàm addAll(self,products) để thêm mới nhiều Product vào Catalog
  • Hàm update(self,index,p) để cập nhật dữ liệu cho Product theo index
  • Hàm update(self,p) để cập nhật dữ liệu cho Product theo đối tượng Product
  • Hàm removeByIndex(self,index) để xóa 1 Product ra khỏi Catalog tại ví trí index
  • Hàm removeByItem(self,item) để xóa 1 Product ra khỏi danh sách tại ví trí item
  • Hàm clear(self) xóa toàn bộ Product ra khỏi Catalog
  • Hàm size(self) trả về kích thước (số lượng) Product trong Catalog

Bước 3: Viết mã lệnh cho “Dataset.py” để lưu trữ và xử lý danh sách các Category, mã lệnh được thể hiện như dưới đây:

from Category import Category
from Product import Product

class Dataset:
    def __init__(self,categories=None):
        if categories==None:
            self.categories=[]
        else:
            self.categories = categories
    def item(self,index)->Category:
        return self.categories[index]
    def add(self,c):
        self.categories.append(c)
    def addAll(self,categories):
        for i in range(len(categories)):
            c=categories[i]
            self.add(c)
    def index(self,c):
        i=self.categories.index(c)
        return i
    def update(self,index,c):
        self.categories[index]=c
        return self.categories[index]
    def removeByIndex(self,index):
        return self.categories.pop(index)
    def removeByItem(self,item):
        self.categories.remove(item)
    def clear(self):
        self.categories.clear()
    def size(self):
        return len(self.categories)
    def printAll(self):
        for i in range(self.size()):
            c=self.item(i)
            print(c)
            for j in range(len(c.products)):
                p=c.products[j]
                print(p)
    def reModel(self):
        categories = []
        for i in range(self.size()):
            dict_c = self.item(i)
            products = []
            for j in range(len(dict_c["products"])):
                dict_p = dict_c["products"][j]
                p = Product(dict_p["id"], dict_p["name"], dict_p["price"],dict_p["timetracking"])
                products.append(p)
            c = Category(dict_c["id"], dict_c["name"], products)
            categories.append(c)
        self.categories=categories

Lớp Dataset gồm có các phương thức để lưu danh sách Category, cũng như tương tác các danh sách chẳng hạn:

  • Hàm item(self,index) là hàm trả về Catalog theo index
  • Hàm index(self,c) là hàm trả về vị trí của Catalog trong Dataset
  • Hàm add(self,c) để thêm mới một Catalog vào Dataset
  • Hàm addAll(self,categories) để thêm mới nhiều Catalog vào Dataset
  • Hàm update(self,index,c) để cập nhật dữ liệu cho Catalog theo index
  • Hàm removeByIndex(self,index) để xóa 1 Catalog ra khỏi Dataset tại ví trí index
  • Hàm removeByItem(self,item) để xóa 1 Catalog ra khỏi danh sách tại ví trí item
  • Hàm clear(self) xóa toàn bộ Catalog ra khỏi Dataset
  • Hàm size(self) trả về kích thước (số lượng) Catalog trong Dataset
  • Hàm reModel(self) là hàm để mô hình hóa toàn bộ dữ liệu trong Json thành mô hình hướng đối tượng. Vì khi đọc ra, tuy Deserialize rồi nhưng các đối tượng lại mới chỉ ở dạng Dictionary.

Bước 4: Viết mã lệnh cho “FileFactory.py“, class này dùng để serialize và deserialize dữ liệu với định dạng JSonArray:

import json
import os

class FileFactory:
    #path: path to serialize array of product
    #arrData: array of Product
    def writeData(self,path,dataObject):
        jsonString = json.dumps(dataObject,default=lambda o: o.__dict__, indent=4)
        jsonFile = open(path, "w")
        jsonFile.write(jsonString)
        jsonFile.close()
    #path: path to deserialize array of Product
    #ClassName: Product
    def readData(self,path,ClassName):
        if os.path.isfile(path) == False:
            return []
        file = open(path, "r")
        jsonstring=file.read()
        # Reading from file
        ds =  ClassName(**json.loads(jsonstring))
        file.close()
        return ds

Tui có chỉnh sửa lại mã lệnh của FileFactory.py để nó có thể lưu được mảng dữ liệu JSonArray lồng nhau

Bước 5: Dùng Qt Designer để thiết kế giao diện “MainWindow.ui” cho phần mềm.

Các bạn kéo thả các Widget và đặt tên giống như hình minh họa ở trên.

Bước 6: Dùng công cụ để Generate Python code cho “MainWindow.ui” ta có file “MainWindow.py”:

# 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(650, 437)
        font = QtGui.QFont()
        font.setPointSize(12)
        MainWindow.setFont(font)
        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.groupBox = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox.setGeometry(QtCore.QRect(270, 30, 351, 271))
        self.groupBox.setStyleSheet("background-color: rgb(255, 206, 241);")
        self.groupBox.setObjectName("groupBox")
        self.label_3 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_3.setGeometry(QtCore.QRect(10, 30, 111, 20))
        self.label_3.setObjectName("label_3")
        self.lineEditProductId = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditProductId.setGeometry(QtCore.QRect(80, 60, 211, 22))
        self.lineEditProductId.setObjectName("lineEditProductId")
        self.label_4 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_4.setGeometry(QtCore.QRect(10, 90, 151, 16))
        self.label_4.setObjectName("label_4")
        self.lineEditProductName = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditProductName.setGeometry(QtCore.QRect(80, 120, 211, 22))
        self.lineEditProductName.setObjectName("lineEditProductName")
        self.lineEditUnitPrice = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditUnitPrice.setGeometry(QtCore.QRect(80, 170, 211, 22))
        self.lineEditUnitPrice.setObjectName("lineEditUnitPrice")
        self.label_5 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_5.setGeometry(QtCore.QRect(10, 150, 111, 16))
        self.label_5.setObjectName("label_5")
        self.label_6 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_6.setGeometry(QtCore.QRect(10, 200, 141, 31))
        self.label_6.setObjectName("label_6")
        self.dateTimeEditTracking = QtWidgets.QDateTimeEdit(parent=self.groupBox)
        self.dateTimeEditTracking.setGeometry(QtCore.QRect(80, 230, 221, 31))
        self.dateTimeEditTracking.setDateTime(QtCore.QDateTime(QtCore.QDate(2023, 12, 29), QtCore.QTime(0, 0, 0)))
        self.dateTimeEditTracking.setCalendarPopup(True)
        self.dateTimeEditTracking.setObjectName("dateTimeEditTracking")
        self.groupBox_2 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_2.setGeometry(QtCore.QRect(9, 39, 251, 341))
        self.groupBox_2.setStyleSheet("background-color: rgb(212, 253, 255);")
        self.groupBox_2.setTitle("")
        self.groupBox_2.setObjectName("groupBox_2")
        self.listWidgetProduct = QtWidgets.QListWidget(parent=self.groupBox_2)
        self.listWidgetProduct.setGeometry(QtCore.QRect(10, 100, 231, 221))
        self.listWidgetProduct.setStyleSheet("background-color: rgb(237, 255, 202);")
        self.listWidgetProduct.setObjectName("listWidgetProduct")
        self.label = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label.setGeometry(QtCore.QRect(10, 10, 161, 21))
        self.label.setObjectName("label")
        self.cboCatalog = QtWidgets.QComboBox(parent=self.groupBox_2)
        self.cboCatalog.setGeometry(QtCore.QRect(10, 40, 221, 22))
        self.cboCatalog.setStyleSheet("background-color: rgb(237, 255, 202);")
        self.cboCatalog.setObjectName("cboCatalog")
        self.label_2 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_2.setGeometry(QtCore.QRect(10, 70, 161, 21))
        self.label_2.setObjectName("label_2")
        self.groupBox_3 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_3.setGeometry(QtCore.QRect(270, 300, 351, 81))
        self.groupBox_3.setStyleSheet("background-color: rgb(249, 255, 210);")
        self.groupBox_3.setObjectName("groupBox_3")
        self.pushButtonNew = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonNew.setGeometry(QtCore.QRect(10, 30, 93, 41))
        self.pushButtonNew.setStyleSheet("background-color: rgb(170, 255, 127);")
        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(32, 32))
        self.pushButtonNew.setObjectName("pushButtonNew")
        self.pushButtonSave = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonSave.setGeometry(QtCore.QRect(120, 30, 93, 41))
        self.pushButtonSave.setStyleSheet("background-color: rgb(170, 255, 127);")
        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(32, 32))
        self.pushButtonSave.setObjectName("pushButtonSave")
        self.pushButtonDelete = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonDelete.setGeometry(QtCore.QRect(232, 30, 111, 41))
        self.pushButtonDelete.setStyleSheet("background-color: rgb(170, 255, 127);")
        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.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonDelete.setObjectName("pushButtonDelete")
        self.label_7 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_7.setGeometry(QtCore.QRect(210, 0, 301, 31))
        palette = QtGui.QPalette()
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.ButtonText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.ButtonText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.ButtonText, brush)
        self.label_7.setPalette(palette)
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(True)
        font.setWeight(75)
        self.label_7.setFont(font)
        self.label_7.setStyleSheet("color: rgb(255, 0, 0);")
        self.label_7.setObjectName("label_7")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 650, 26))
        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 - QDateTime Demo"))
        self.groupBox.setTitle(_translate("MainWindow", "Product Details:"))
        self.label_3.setText(_translate("MainWindow", "Product ID:"))
        self.label_4.setText(_translate("MainWindow", "Product Name:"))
        self.label_5.setText(_translate("MainWindow", "Unit Price:"))
        self.label_6.setText(_translate("MainWindow", "Time Tracking:"))
        self.dateTimeEditTracking.setDisplayFormat(_translate("MainWindow", "dd/MM/yyyy HH:mm:ss"))
        self.label.setText(_translate("MainWindow", "Select a Catalog:"))
        self.label_2.setText(_translate("MainWindow", "List of Products:"))
        self.groupBox_3.setTitle(_translate("MainWindow", "Action:"))
        self.pushButtonNew.setText(_translate("MainWindow", "New"))
        self.pushButtonSave.setText(_translate("MainWindow", "Save"))
        self.pushButtonDelete.setText(_translate("MainWindow", "Delete"))
        self.label_7.setText(_translate("MainWindow", "Product Management"))

Bước 7: Ta tạo file “MainWindowEx.py” kế thừa từ “MainWindow.py” và tiến hành viết các sự kiện người dùng tương tác:

import datetime
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QListWidgetItem
from Dataset import Dataset
from FileFactory import FileFactory
from MainWindow import Ui_MainWindow
from Product import Product

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.fileFactory = FileFactory()
        self.dataset=None
        self.selectedCatalog=None
        self.selectedProduct=None

Trong MainWindowEx ta bổ sung constructor để khởi tạo 4 biến đối tượng:

  • Biến đối tượng fileFactory là biến dùng để gọi đối tượng FileFactory nhằm sử dụng Serialize và Deserialize dữ liệu với định dạng JSonArray.
  • Biến đối tượng dataset để lưu trữ danh sách hướng đối tượng các Category mà người sử dụng tương tác trên giao diện.
  • Biến đối tượng selectedCatalog để lưu trữ Category đang lựa chọn trên QComboBox.
  • Biến đối tượng selectedProduct để lưu trữ Product đang lựa chọn trên QListWidget.

Tiếp theo ta Override hàm setupUi để khởi tạo giao diện cũng như gán các signal và slot cho giao diện tương tác:

def setupUi(self, MainWindow):
    super().setupUi(MainWindow)
    self.MainWindow=MainWindow
    self.dataset = self.fileFactory.readData("database.json", Dataset)
    self.dataset.reModel()
    self.loadCatalogForComboBox()
    self.cboCatalog.activated.connect(self.processSelectedCatalog)
    self.listWidgetProduct.itemSelectionChanged.connect(self.processSelectedProduct)
    self.pushButtonNew.clicked.connect(self.processNew)
    self.pushButtonSave.clicked.connect(self.processSave)
    self.pushButtonDelete.clicked.connect(self.processDelete)

Khi khởi động phần mềm, chương trình sẽ đọc dữ liệu (deserialize) “database.json” thành hướng đối tượng Dataset và biến đối tượng dataset sẽ lưu trữ danh sách dữ liệu (các Category) này.

Hàm reModel() được triệu gọi để mô hình hóa đầy đủ cấu trúc dữ liệu trong Json thành mô hìn hướng đối tượng.

Sau đó hàm loadCatalogForComboBox() sẽ được triệu gọi để hiển thị toàn bộ Catalog trong biến đối tượng dataset lên giao diện QComboBox:

def loadCatalogForComboBox(self):
    for i in range(self.dataset.size()):
        catalog=self.dataset.item(i)
        self.cboCatalog.addItem(str(catalog),catalog)

Chương trình dùng vòng lặp để duyệt qua các Category trong biến đối tượng dataset, sau đó nó sẽ được để hiển thị lên giao diện. 

Sau đó hàm processSelectedCatalog() sẽ xử lý khi người dùng nhấn chọn Category trong QComboBox thì danh sách Product của Category này sẽ hiển thị vào QListWidget:

def processSelectedCatalog(self):
    self.selectedCatalog=self.cboCatalog.currentData(Qt.ItemDataRole.UserRole)
    self.listWidgetProduct.clear()
    for i in range(self.selectedCatalog.size()):
        product=self.selectedCatalog.item(i)
        item=QListWidgetItem()
        item.setData(Qt.ItemDataRole.UserRole, product)
        item.setText(str(product))
        self.listWidgetProduct.addItem(item)

-Hàm processNew dùng để xóa các dữ liệu đang nhập trên giao diện và focus vào ô ID để người dùng nhập dữ liệu mới được nhanh chóng hơn:

def processNew(self):
    self.lineEditProductId.setText("")
    self.lineEditProductName.setText("")
    self.lineEditUnitPrice.setText("")
    self.selectedProduct=None
    self.lineEditProductId.setFocus()

Ta có một vài lưu ý với hàm processNew:

  • biến selectedProduct sẽ được gán None để đánh dấu ta không còn chọn Product nào trong QListWidget, mà đây sẽ là Product mới hoàn toàn
  • gọi hàm setFocus() cho ID để người sử dụng nhập liệu nhanh chóng nhất.

-Hàm processSave dùng để lưu dữ liệu, nếu selectedCatalog mà bằng None thì không xử lý (người dùng cần chọn Category trước). Nếu selectedProduct mà bằng None là lưu mới, ngược lại là lưu cập nhật:

def processSave(self):
    if self.selectedCatalog==None:
        return
    id=int(self.lineEditProductId.text())
    name=self.lineEditProductName.text()
    price=float(self.lineEditUnitPrice.text())
    tracking=self.dateTimeEditTracking.dateTime()
    date_format = '%Y-%m-%d %H:%M:%S'
    trackingFormat=tracking.toPyDateTime().strftime(date_format)

    product=Product(id,name,price,trackingFormat)
    item = QListWidgetItem()
    item.setData(Qt.ItemDataRole.UserRole, product)
    item.setText(str(product))
    if self.selectedProduct==None:
        self.selectedProduct=product
        self.selectedCatalog.add(product)
        self.listWidgetProduct.addItem(item)
    else:
        index=self.selectedCatalog.index(self.selectedProduct)
        self.selectedProduct=product
        self.selectedCatalog.update(index,self.selectedProduct)
        item = self.listWidgetProduct.item(index)
        item.setData(Qt.ItemDataRole.UserRole,self.selectedProduct)
        item.setText(str(self.selectedProduct))
    self.fileFactory.writeData("database.json", self.dataset)

Sau khi thêm một Product mới thì chương trình sẽ cập nhật lại dữ liệu và lưu xuống tập tin database.json

– Hàm processDelete dùng để xóa Product đang chọn ra khỏi QListWidget:

def processDelete(self):
    if self.selectedProduct!=None:
        index = self.selectedCatalog.index(self.selectedProduct)
        self.selectedCatalog.removeByItem(self.selectedProduct)
        self.fileFactory.writeData("database.json", self.dataset)
        self.processSelectedCatalog()
        self.processNew()

-Sau khi xóa thành công thì cũng cập nhật lại danh sách cho biến đối tượng dataset, hiển thị lại dữ liệu trên giao diện, đồng thời cũng lưu lại tập tin database.json.

Dưới đây là mã lệnh đầy đủ của MainWindowEx.py:

import datetime
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QListWidgetItem
from Dataset import Dataset
from FileFactory import FileFactory
from MainWindow import Ui_MainWindow
from Product import Product

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.fileFactory = FileFactory()
        self.dataset=None
        self.selectedCatalog=None
        self.selectedProduct=None
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.dataset = self.fileFactory.readData("database.json", Dataset)
        self.dataset.reModel()
        self.loadCatalogForComboBox()
        self.cboCatalog.activated.connect(self.processSelectedCatalog)
        self.listWidgetProduct.itemSelectionChanged.connect(self.processSelectedProduct)
        self.pushButtonNew.clicked.connect(self.processNew)
        self.pushButtonSave.clicked.connect(self.processSave)
        self.pushButtonDelete.clicked.connect(self.processDelete)
    def loadCatalogForComboBox(self):
        for i in range(self.dataset.size()):
            catalog=self.dataset.item(i)
            self.cboCatalog.addItem(str(catalog),catalog)
    def processSelectedCatalog(self):
        self.selectedCatalog=self.cboCatalog.currentData(Qt.ItemDataRole.UserRole)
        self.listWidgetProduct.clear()
        for i in range(self.selectedCatalog.size()):
            product=self.selectedCatalog.item(i)
            item=QListWidgetItem()
            item.setData(Qt.ItemDataRole.UserRole, product)
            item.setText(str(product))
            self.listWidgetProduct.addItem(item)

    def processSelectedProduct(self):
        current_row = self.listWidgetProduct.currentRow()
        if current_row < 0:
            return
        item = self.listWidgetProduct.item(current_row)
        self.selectedProduct = item.data(Qt.ItemDataRole.UserRole)
        self.lineEditProductId.setText(str(self.selectedProduct.id))
        self.lineEditProductName.setText(self.selectedProduct.name)
        self.lineEditUnitPrice.setText(str(self.selectedProduct.price))
        date_format = '%Y-%m-%d %H:%M:%S'
        timetracking=datetime.datetime.strptime(self.selectedProduct.timetracking,date_format)
        self.dateTimeEditTracking.setDateTime(timetracking)
    def processNew(self):
        self.lineEditProductId.setText("")
        self.lineEditProductName.setText("")
        self.lineEditUnitPrice.setText("")
        self.selectedProduct=None
        self.lineEditProductId.setFocus()
    def processSave(self):
        if self.selectedCatalog==None:
            return
        id=int(self.lineEditProductId.text())
        name=self.lineEditProductName.text()
        price=float(self.lineEditUnitPrice.text())
        tracking=self.dateTimeEditTracking.dateTime()
        date_format = '%Y-%m-%d %H:%M:%S'
        trackingFormat=tracking.toPyDateTime().strftime(date_format)

        product=Product(id,name,price,trackingFormat)
        item = QListWidgetItem()
        item.setData(Qt.ItemDataRole.UserRole, product)
        item.setText(str(product))
        if self.selectedProduct==None:
            self.selectedProduct=product
            self.selectedCatalog.add(product)
            self.listWidgetProduct.addItem(item)
        else:
            index=self.selectedCatalog.index(self.selectedProduct)
            self.selectedProduct=product
            self.selectedCatalog.update(index,self.selectedProduct)
            item = self.listWidgetProduct.item(index)
            item.setData(Qt.ItemDataRole.UserRole,self.selectedProduct)
            item.setText(str(self.selectedProduct))
        self.fileFactory.writeData("database.json", self.dataset)

    def processDelete(self):
        if self.selectedProduct!=None:
            index = self.selectedCatalog.index(self.selectedProduct)
            self.selectedCatalog.removeByItem(self.selectedProduct)
            self.fileFactory.writeData("database.json", self.dataset)
            self.processSelectedCatalog()
            self.processNew()

    def show(self):
        self.MainWindow.show()

– Bước 8: Cuối cùng ta tạo lớp “MyApp.py” và viết mã lệnh như dưới đây để khởi chạy 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()

Chạy “MyApp.py” lên ta sẽ có kết quả như mong muốn.

Mã lệnh đầy đủ của dự án các bạn tải ở đây:

https://www.mediafire.com/file/jtvc75cgfhxtijq/LearnQDateTimeEdit.rar/file

Như vậy tới đây Tui đã trình bày xong lý thuyết cũng như kỹ thuật sử dụng QDateTimeEdit , củng cố bài cũ QComboBox, QListWidget và ứng dụng vào quản lý Product, củng cố lại các kiến thức liên quan tới lập trình hướng đối tượng, cách serialize và deserialize đối tượng ra JSonArray, củng cố lại kiến thức và kỹ thuật liên quan tới hiển thị và tương tác dữ liệu trên giao diện.

Các bạn cố gắng thực hành lại bài này nhiều lần để nắm rõ hơn về lập trình hướng đối tượng, cách tạo các lớp độc lập để tái sử dụng tốt hơn

Bài học sau Tui sẽ trình bày về QTableWidget để tạo ra widget cho người dùng lưu trữ và hiển thị dữ liệu dạng dòng và cột, nó cũng là một trong các Widget quan trọng và phổ biến, được sử dụng thường xuyên trong các phần mềm. Các bạn chú ý theo dõi

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

Bài 20: QTimeEdit – Basic Widgets – PyQt6

Trong bài QDateEdit các bạn đã được học kỹ lưỡng về cách dùng widget để lưu trữ và xử lý dữ liệu Ngày-Tháng-Năm. Bài học này Tui sẽ trình bày về cách sử dụng QTimeEdit để lưu trữ Giờ-Phút.

Trong lưu trữ dữ liệu chúng ta cũng thường có các dữ liệu [ngày tháng năm], [ngày tháng năm giờ phút giây], [giờ phút giây] tùy từng tình huống mà chúng ta sẽ lựa chọn kiểu dữ liệu phù hợp. Ví dụ như lưu năm sinh thì ta dùng Ngày Tháng Năm, lưu thời gian đặt đơn hàng thì ta dùng ngày tháng năm giờ phút giây, lưu trữ thời gian thực hiện thì ta dùng giờ phút giây hoặc kết hợp cả ngày tháng năm (ví dụ như đặt đơn hàng và yêu cầu giao hàng lúc 10h sáng ngày 20/12/2023, thì lúc này ta dùng vừa ngày tháng năm vừa giờ phút giây. hoặc tách ra làm 2 thuộc tính, thuộc tính chỉ chứa ngày tháng năm và thuộc tính chỉ chứa giờ phút giây), Ví dụ dưới đây là phần mềm Tracking Tasks có sử dụng QDateEdit và QTimeEdit, cuối bài học này Tui sẽ trình bày chi tiết cách thiết kế và lập trình phần mềm này:

Trước khi vào coding ta xem một số thuộc tính, phương thức, signal thường dùng của widget này:

Thuộc tính, phương thức, signalÝ nghĩa chức năng
QTimeEdit(self)Constructor để tạo đối tượng QTimeEdit. 
time()Hàm trả về đối tượng QTime. Do đó để chuyển về Python datetime.time ta dùng hàm toPyTime()
minimumTimeThuộc tính thiết lập giờ nhỏ nhất
maximumTimeThuộc tính thiết lập giờ lớn nhất
setDisplayFormat(format)Chuỗi format hiển thị giờ.
Ví dụ:
setDisplayFormat(“HH:mm:ss AP”)
editingFinishedsignal để lắng nghe khi người dùng kết thúc quá trình lựa chọn giờ (nhấn enter)
timeChangedsignal để lắng nghe khi người dùng đang thay đổi lựa chọn giờ

Dưới đây là các bước khai báo và sử dụng QTimeEdit:

Bước 1: Khai báo và khởi tạo đối tượng QTimeEdit

self.time_edit = QTimeEdit(self)

Bước 2: Thiết lập các giá trị cho các thuộc tính

time=QTime(10,15,35)
self.time_edit.setTime(time)
self.time_edit.setDisplayFormat("HH:mm:ss AP")

Mã lệnh trên tạo đối tượng QTime(10,15,35). 10 giờ, 15 phút, 35 giây.

hàm setTime để thiết lập giờ của đối tượng QTimeEdit.

Định dạng “HH:mm:ss AP” để thiết lập hiển thị giờ cho QTimeEdit thông qua hàm setDisplayFormat.

  • H là định dạng 24 giờ, Ta dùng HH để hiển thị giờ bổ sung thêm số 0 đằng trước khi giờ <10. Ví dụ như 8 giờ thì hiển thị 08. Ngoài ra nếu dùng h là định dạng 12 giờ.
  • m là định dạng phút, ta dùng mm để hiển thị phút bổ sung thêm số 0 đằng trước khi phút <10. Ví dụ 9 phút thì hiển thị 09
  • s là định dạng giây, ta dùng ss để hiển thị giây bổ sung thêm số 0 đằng trước khi giây <10. Ví dụ 5 giây thì hiển thị 05
  • AP là đại diện hiển thị AM hay PM

Bước 3: Thiết lập signal cho QTimeEdit khi người sử dụng lựa chọn xong Giờ

self.time_edit.editingFinished.connect(self.update_time)
def update_time(self):
    value = self.time_edit.time()
    print(str(value.toPyTime()))

Hàm time() sẽ trả về dữ liệu có kiểu QTime, nên muốn đưa về Python time thì ta gọi thêm hàm toPyTime()

Tuy nhiên editingFinished signal thì người dùng cần nhấn phím Enter để xác nhận. Do đó để tiện lại ta có thể dùng timeChanged signal, bất cứ khi nào người dùng lựa chọn Giờ trên giao diện thì ta sẽ lấy được dữ liệu này:

self.time_edit.timeChanged.connect(self.change_time)
def change_time(self):
    value = self.time_edit.time()
    print(str(value.toPyTime()))

Dưới đây là Full code minh họa:

import sys

from PyQt6.QtCore import QTime
from PyQt6.QtWidgets import QApplication, QWidget, QTimeEdit, QLabel, QFormLayout


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle('PyQt QTimeEdit')
        self.setMinimumWidth(200)

        # create a grid layout
        layout = QFormLayout()
        self.setLayout(layout)

        self.time_edit = QTimeEdit(self)
        time=QTime(10,15,35)
        self.time_edit.setTime(time)
        self.time_edit.setDisplayFormat("HH:mm:ss A")

        self.time_edit.editingFinished.connect(self.update_time)
        self.time_edit.timeChanged.connect(self.change_time)

        self.result_label = QLabel('', self)

        layout.addRow('Time:', self.time_edit)
        layout.addRow(self.result_label)
    def update_time(self):
        value = self.time_edit.time()
        print(str(value.toPyTime()))
        self.result_label.setText(str(value.toPyTime()))
        pass
    def change_time(self):
        value = self.time_edit.time()
        print(str(value.toPyTime()))
        self.result_label.setText(str(value.toPyTime()))
app = QApplication([])
window = MainWindow()
window.show()
app.exec()

Chạy mã lệnh trên ta có kết quả:

Bây giờ Tui sẽ hướng dẫn các bạn cách thức áp dụng QDateEdit và QTimeEdit vào việc phát triển phần mềm Tracking Tasks như dưới đây:

Mô tả các chức năng của phần mềm:

  • Chương trình cung cấp giao diện để nhập các Task, thông tin chi tiết của Task bao gồm: Tiêu đề, nội dung thực hiện, ngày hết hạn, giờ hết hạn, và trạng thái đã hoàn thành hay chưa
  • Khi nhập Task thành công thì các Task sẽ được đưa vào QListWidget đồng thời các Task nào chưa hoàn thành thì có biểu tượng màu đó, task nào đã hoàn thành thì có biểu tượng màu xanh
  • Chương trình sẽ lưu và phục hồi dữ liệu bằng serialize và deserialize JSON ARRAY
  • Chương trình cung cấp chức năng chỉnh sửa Task
  • Chương trình cung cấp nút xóa, sẽ xóa những Task được checked trong QListWidget
  • Chương trình cung cấp chức năng selection trong QListWidget, khi người dùng bấm chuột hoặc di chuyển item trong QListWidget thì chi tiết của Task đang lựa chọn sẽ được hiển thị lại trong các widget bên màn hình bên phải.

Ta tạo một dự án tên “LearnQTimeEdit” trong Pycharm, có cấu trúc tập tin và thư mục như dưới đây:

  • Thư mục “images” lưu trữ các icon của phần mềm như Window Icon, Add Icon, Save Icon, Remove Icon, Icon cho 2 trạng thái Finished và not Finished task
  • Task.py Là file lưu mã lệnh của lớp Task để định nghĩa một task bao gồm: title, content, deadline, deadlinetime, isfinish
  • Tasks.py Là file lưu mã lệnh để xử lý danh sách các Task, chẳng hạn các chức năng: thêm, truy suất, sửa, xóa Task
  • FileFactory.py” là mã lệnh dùng để ghi toàn bộ dữ liệu xuống ổ cứng với định dạng JSonArray, cũng như phục hồi và mô hình hóa lại dữ liệu hướng đối tượng
  • MainWindow.ui” là file thiết kế giao diện bằng Qt Designer
  • MainWindow.py” là file python code được generate tự động từ “MainWindow.ui”. Cách thiết kế và tích hợp công cụ tự động Generate đã được hướng dẫn rất chi tiết ở những bài học đầu tiên, các bạn cần xem lại
  • MainWindowEx.py” là mã lệnh ta bổ sung, kế thừ từ lớp được generate trong MainWindow.py để xử lý các sự kiện người dùng
  • MyApp.py” là mã lệnh để thực thi chương trình
  • database.json” là file JSonArray để lưu trữ các dữ liệu mà người dùng thao tác trên phần mềm.

Bước 1: Viết mã lệnh cho “Task.py”, mã lệnh được thể hiện như dưới đây:

class Task:
    def __init__(self,title,content,deadline,deadlinetime,isfinish):
        self.title=title
        self.content=content
        self.deadline=deadline
        self.deadlinetime=deadlinetime
        self.isfinish=isfinish
        pass
    def __str__(self):
        return self.title

Ta khai báo lớp Task cùng với constructor và các đối số chính xác như trên để việc serialize và deserialize JSon data được chính xác.

Bước 2: Viết mã lệnh cho “Tasks.py” để xử lý danh sách các Task, mã lệnh được thể hiện như dưới đây:

from Task import Task
class Tasks:
    def __init__(self):
        self.lists=[]
    def item(self,index)->Task:
        return self.lists[index]
    def add(self,task):
        self.lists.append(task)
    def addAll(self,tasks):
        for i in range(len(tasks)):
            task=tasks[i]
            self.add(task)
    def index(self,task):
        i=self.lists.index(task)
        return i
    def update(self,index,task)->Task:
        self.lists[index]=task
        return self.lists[index]
    def removeByIndex(self,index)->Task:
        return self.lists.pop(index)
    def removeByItem(self,item):
        self.lists.remove(item)
    def clear(self):
        self.lists.clear()
    def size(self):
        return len(self.lists)

Lớp Tasks ở trên Tui thiết kế các phương thức để ta có thể sử dụng lưu danh sách, cũng như tương tác các danh sách chẳng hạn:

  • Hàm item(self,index) là hàm trả về Task theo index
  • Hàm index(self,task) là hàm trả về vị trí của task trong danh sách
  • Hàm add(self,task) để thêm mới một task vào danh sách
  • Hàm addAll(self,tasks) để thêm mới nhiều task vào danh sách
  • Hàm update(self,index,task) để cập nhật dữ liệu cho Task
  • Hàm removeByIndex(self,index) để xóa 1 task ra khỏi danh sách tại ví trí index
  • Hàm removeByItem(self,item) để xóa 1 task ra khỏi danh sách tại ví trí item
  • Hàm clear(self) xóa toàn bộ task ra khỏi danh sách
  • Hàm size(self) trả về kích thước (số lượng) các phần tử trong danh sách

Bước 3: Viết mã lệnh cho “FileFactory.py“, class này dùng để serialize và deserialize dữ liệu với định dạng JSonArray:

import json
import os

class FileFactory:
    #path: path to serialize array of product
    #arrData: array of Product
    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 Product
    #ClassName: Product
    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ưu ý “FileFactory.py” là có mã lệnh không đổi, sử dụng chung cho mọi dự án muốn lưu dữ liệu dạng JSONARRAY, nó đã được trình bày ở bài học trước.

Bước 4: Dùng Qt Designer để thiết kế giao diện “MainWindow.ui” cho phần mềm.

Các bạn kéo thả các Widget và đặt tên giống như hình minh họa ở trên.

Bước 5: Dùng công cụ để Generate Python code cho “MainWindow.ui” ta có file “MainWindow.py”:

# 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(566, 385)
        font = QtGui.QFont()
        font.setPointSize(11)
        MainWindow.setFont(font)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("images/ic_logo.jpg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        MainWindow.setWindowIcon(icon)
        MainWindow.setStyleSheet("")
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setStyleSheet("")
        self.centralwidget.setObjectName("centralwidget")
        self.groupBox = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox.setGeometry(QtCore.QRect(10, 30, 211, 311))
        self.groupBox.setStyleSheet("background-color: rgb(230, 255, 246);")
        self.groupBox.setObjectName("groupBox")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox)
        self.verticalLayout.setObjectName("verticalLayout")
        self.listWidgetTask = QtWidgets.QListWidget(parent=self.groupBox)
        self.listWidgetTask.setObjectName("listWidgetTask")
        self.verticalLayout.addWidget(self.listWidgetTask)
        self.groupBox_2 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_2.setGeometry(QtCore.QRect(230, 30, 321, 241))
        self.groupBox_2.setStyleSheet("background-color: rgb(248, 255, 215);")
        self.groupBox_2.setObjectName("groupBox_2")
        self.label_2 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_2.setGeometry(QtCore.QRect(10, 30, 31, 16))
        self.label_2.setObjectName("label_2")
        self.label_3 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_3.setGeometry(QtCore.QRect(10, 90, 51, 16))
        self.label_3.setObjectName("label_3")
        self.lineEditTitle = QtWidgets.QLineEdit(parent=self.groupBox_2)
        self.lineEditTitle.setGeometry(QtCore.QRect(60, 30, 241, 20))
        self.lineEditTitle.setObjectName("lineEditTitle")
        self.textEditContent = QtWidgets.QTextEdit(parent=self.groupBox_2)
        self.textEditContent.setGeometry(QtCore.QRect(60, 60, 241, 81))
        self.textEditContent.setObjectName("textEditContent")
        self.radFinished = QtWidgets.QRadioButton(parent=self.groupBox_2)
        self.radFinished.setGeometry(QtCore.QRect(60, 210, 83, 18))
        self.radFinished.setObjectName("radFinished")
        self.radNotFinished = QtWidgets.QRadioButton(parent=self.groupBox_2)
        self.radNotFinished.setGeometry(QtCore.QRect(150, 210, 101, 20))
        self.radNotFinished.setObjectName("radNotFinished")
        self.label_4 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_4.setGeometry(QtCore.QRect(10, 150, 51, 20))
        self.label_4.setObjectName("label_4")
        self.dateEditDeadline = QtWidgets.QDateEdit(parent=self.groupBox_2)
        self.dateEditDeadline.setGeometry(QtCore.QRect(60, 150, 110, 22))
        self.dateEditDeadline.setCalendarPopup(True)
        self.dateEditDeadline.setDate(QtCore.QDate(2023, 12, 14))
        self.dateEditDeadline.setObjectName("dateEditDeadline")
        self.label_5 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_5.setGeometry(QtCore.QRect(10, 180, 41, 16))
        self.label_5.setObjectName("label_5")
        self.timeEditDeadline = QtWidgets.QTimeEdit(parent=self.groupBox_2)
        self.timeEditDeadline.setGeometry(QtCore.QRect(60, 180, 111, 22))
        self.timeEditDeadline.setObjectName("timeEditDeadline")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        self.label.setGeometry(QtCore.QRect(30, 0, 491, 31))
        font = QtGui.QFont()
        font.setFamily("Times New Roman")
        font.setPointSize(18)
        font.setBold(False)
        font.setItalic(False)
        font.setWeight(9)
        self.label.setFont(font)
        self.label.setStyleSheet("font: 75 18pt \"Times New Roman\";\n"
"color: rgb(0, 0, 255);")
        self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label.setObjectName("label")
        self.groupBox_3 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_3.setGeometry(QtCore.QRect(230, 280, 321, 61))
        self.groupBox_3.setStyleSheet("background-color: rgb(255, 216, 249);")
        self.groupBox_3.setObjectName("groupBox_3")
        self.pushButtonNew = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonNew.setGeometry(QtCore.QRect(20, 20, 71, 31))
        self.pushButtonNew.setStyleSheet("background-color: rgb(255, 255, 127);")
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("images/ic_add.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.groupBox_3)
        self.pushButtonSave.setGeometry(QtCore.QRect(120, 20, 71, 31))
        self.pushButtonSave.setStyleSheet("background-color: rgb(255, 255, 127);")
        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.pushButtonRemove = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonRemove.setGeometry(QtCore.QRect(220, 20, 81, 31))
        self.pushButtonRemove.setStyleSheet("background-color: rgb(255, 255, 127);")
        icon3 = QtGui.QIcon()
        icon3.addPixmap(QtGui.QPixmap("images/ic_remove.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonRemove.setIcon(icon3)
        self.pushButtonRemove.setIconSize(QtCore.QSize(24, 24))
        self.pushButtonRemove.setObjectName("pushButtonRemove")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 566, 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", "Tran Duy Thanh - Tracking Tasks"))
        self.groupBox.setTitle(_translate("MainWindow", "List of Tasks:"))
        self.groupBox_2.setTitle(_translate("MainWindow", "Details of Task:"))
        self.label_2.setText(_translate("MainWindow", "Title:"))
        self.label_3.setText(_translate("MainWindow", "Content:"))
        self.radFinished.setText(_translate("MainWindow", "Finished"))
        self.radNotFinished.setText(_translate("MainWindow", "Not Finished"))
        self.label_4.setText(_translate("MainWindow", "Deadline:"))
        self.label_5.setText(_translate("MainWindow", "Time:"))
        self.label.setText(_translate("MainWindow", "Tracking Tasks"))
        self.groupBox_3.setTitle(_translate("MainWindow", "Actions:"))
        self.pushButtonNew.setText(_translate("MainWindow", "New"))
        self.pushButtonSave.setText(_translate("MainWindow", "Save"))
        self.pushButtonRemove.setText(_translate("MainWindow", "Remove"))

Bước 6: Ta tạo file “MainWindowEx.py” kế thừa từ “MainWindow.py” và tiến hành viết các sự kiện người dùng tương tác:

import datetime

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QMessageBox, QListWidgetItem

from FileFactory import FileFactory
from MainWindow import Ui_MainWindow
from Task import Task
from Tasks import Tasks

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.tasks=Tasks()
        self.fileFactory = FileFactory()
        self.selectedTask=None

Trong MainWindowEx ta bổ sung constructor để khởi tạo 3 biến đối tượng:

  • Biến đối tượng tasks để lưu trữ danh sách hướng đối tượng các Task mà người sử dụng tương tác trên giao diện
  • Biến đối tượng fileFactory là biến dùng để gọi đối tượng FileFactory nhằm sử dụng Serialize và Deserialize dữ liệu với định dạng JSonArray.
  • Biến đối tượng selectedTask để lưu trữ Task đang lựa chọn trên QListWidget (Task hiện hành)

Tiếp theo ta Override hàm setupUi để khởi tạo giao diện cũng như gán các signal và slot cho giao diện tương tác:

def setupUi(self, MainWindow):
    super().setupUi(MainWindow)
    self.MainWindow=MainWindow
    arrData= self.fileFactory.readData("database.json", Task)
    self.tasks.addAll(arrData)
    self.showTasksIntoQListWidget()
    self.pushButtonNew.clicked.connect(self.processNew)
    self.pushButtonSave.clicked.connect(self.processSave)
    self.pushButtonRemove.clicked.connect(self.processRemove)
    self.listWidgetTask.itemSelectionChanged.connect(self.processItemSelection)

Khi khởi động phần mềm, chương trình sẽ đọc dữ liệu (deserialize) “database.json” thành danh sách hướng đối tượng Task và biến đối tượng tasks sẽ lưu trữ danh sách dữ liệu này.

Sau đó hàm showTasksIntoQListWidget() sẽ được triệu gọi để hiển thị toàn bộ Task trong biến đối tượng tasks lên giao diện QListWidget:

def showTasksIntoQListWidget(self):
    self.listWidgetTask.clear()
    for index in range(self.tasks.size()):
        task=self.tasks.item(index)
        item=QListWidgetItem()
        item.setData(Qt.ItemDataRole.UserRole, task)
        item.setText(str(task))
        item.setCheckState(Qt.CheckState.Unchecked)
        if task.isfinish==True:
            item.setIcon(QIcon("images/ic_finished.png"))
        else:
            item.setIcon(QIcon("images/ic_notfinished.png"))
        if isinstance(task.deadline,str):
            task.deadline= datetime.date.fromisoformat(task.deadline)
        if isinstance(task.deadlinetime,str):
            task.deadlinetime=datetime.time.fromisoformat(task.deadlinetime)
        self.listWidgetTask.addItem(item)

Chương trình dùng vòng lặp để duyệt qua các Task trong biến đối tượng tasks, sau đó sẽ tạo các đối tượng QListWidgetItem để hiển thị lên giao diện. Nếu Task nào là FINISHED thì dùng icon finished (có màu xanh), chưa finished thì icon màu đỏ.

Ngoài ra vì dữ liệu ngày tháng khi serialize xuống ổ cứng với định dạng JSon thì nó là chuỗi, nên ta dùng hàm isinstance để kiểm tra xem thuộc tính deadline có phải đang là chuỗi hay không, nếu là chuỗi thì ta ép kiểu qua kiểu Python Date bằng hàm: datetime.date.fromisoformat(task.deadline)

Tương tự như vậy ta cũng viết mã lệnh để kiểm tra thuộc tính deadlinetime, ta chuyển qua Python time bằng hàm: datetime.time.fromisoformat(task.deadlinetime)

-Hàm processNew dùng để xóa các dữ liệu đang nhập trên giao diện và focus vào ô title để người dùng nhập dữ liệu mới được nhanh chóng hơn:

def processNew(self):
    self.lineEditTitle.setText("")
    self.textEditContent.setText("")
    self.dateEditDeadline.setSpecialValueText(None)
    self.radFinished.setAutoExclusive(False)
    self.radNotFinished.setAutoExclusive(False)
    self.radFinished.setChecked(False)
    self.radNotFinished.setChecked(False)
    self.radFinished.setAutoExclusive(True)
    self.radNotFinished.setAutoExclusive(True)
    self.selectedTask=None
    self.lineEditTitle.setFocus()

Ta có một vài lưu ý với hàm processNew:

  • Bởi vì tính chất đặc biệt của QRadioButton nên ta gọi hàm setAutoExclusive(False) trước khi gỡ bỏ các checked của RadioButton, sau đó ta gọi setAutoExclusive(True)
  • Biến đối tượng selectedTask=None để đảm bảo lúc này chưa chọn Task nào trên giao diện, vì lúc này ta đang thêm mới 1 task.

-Hàm processSave dùng để lưu dữ liệu, nếu selectedTask mà khác None lưu cập nhật, còn selectedTask =None thì lưu mới:

def processSave(self):
    title=self.lineEditTitle.text()
    content=self.textEditContent.toPlainText()
    date=self.dateEditDeadline.date().toPyDate()
    time=self.timeEditDeadline.time().toPyTime()
    isFinished=self.radFinished.isChecked()
    task=Task(title,content,date,time,isFinished)
    if self.selectedTask==None:
        self.tasks.add(task)
    else:
        index=self.tasks.index(self.selectedTask)
        self.tasks.update(index,task)
    self.selectedTask = task
    self.showTasksIntoQListWidget()
    self.fileFactory.writeData("database.json",self.tasks.lists)

Sau khi thêm một Task mới thì chương trình sẽ cập nhật danh sách và lưu xuống tập tin database.json

– Hàm processRemove dùng để xóa các QListWidgetItem được checked trên giao diện ra khỏi QListWidget:

def processRemove(self):
    answer = QMessageBox.question(
        self.MainWindow,
        'Confirmation',
        'Do you want to remove checked Items?',
        QMessageBox.StandardButton.Yes |
        QMessageBox.StandardButton.No
    )
    if answer == QMessageBox.StandardButton.No:
        return
    size=self.listWidgetTask.count()
    for index in range(size-1,-1,-1):
        item=self.listWidgetTask.item(index)
        if item.checkState()==Qt.CheckState.Checked:
            self.tasks.removeByIndex(index)
    self.selectedTask = None
    self.showTasksIntoQListWidget()
    self.fileFactory.writeData("database.json", self.tasks.lists)

-Mã lệnh vòng lặp ở trên Tui chạy ngược lại để khi xóa trong danh sách không bị ảnh hưởng thứ tự.

-Sau khi xóa thành công thì cũng cập nhật lại danh sách cho biến đối tượng tasks, hiển thị lại dữ liệu trên giao diện, đồng thời cũng lưu lại tập tin database.json.

Chương trình sẽ hiển thị cửa sổ xác nhận có muốn xóa hay không, nếu đồng ý xóa thì chương trình sẽ xóa, sau đó sẽ lưu (serialize) lại dữ liệu xuống JSonArray cũng như hiển thị lại giao diện. Hình minh họa xóa:

-Cuối cùng là hàm/signal “processItemSelection“, hàm này sẽ lắng nghe người dùng đang chọn QListWidgetItem nào trên giao diện và hiển thị thông tin chi tiết lên các ô dữ liệu. Người dùng có thể dùng phím mũi tên di chuyển hoặc click chuột vào item bất kỳ:

def processItemSelection(self):
    row=self.listWidgetTask.currentRow()
    task=self.tasks.item(row)
    self.lineEditTitle.setText(task.title)
    self.textEditContent.setText(task.content)
    self.dateEditDeadline.setDate(task.deadline)
    self.timeEditDeadline.setTime(task.deadlinetime)
    if task.isfinish:
        self.radFinished.setChecked(True)
        self.radNotFinished.setChecked(False)
    else:
        self.radFinished.setChecked(False)
        self.radNotFinished.setChecked(True)
    self.selectedTask=task

-Tùy từng thông tin của các task đang chọn mà chương trình sẽ hiển thị dữ liệu lên các widget cho phù hợp.

Dưới đây là mã lệnh đầy đủ của MainWindowEx.py:

import datetime

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QMessageBox, QListWidgetItem

from FileFactory import FileFactory
from MainWindow import Ui_MainWindow
from Task import Task
from Tasks import Tasks

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.tasks=Tasks()
        self.fileFactory = FileFactory()
        self.selectedTask=None
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        arrData= self.fileFactory.readData("database.json", Task)
        self.tasks.addAll(arrData)
        self.showTasksIntoQListWidget()
        self.pushButtonNew.clicked.connect(self.processNew)
        self.pushButtonSave.clicked.connect(self.processSave)
        self.pushButtonRemove.clicked.connect(self.processRemove)
        self.listWidgetTask.itemSelectionChanged.connect(self.processItemSelection)
    def showTasksIntoQListWidget(self):
        self.listWidgetTask.clear()
        for index in range(self.tasks.size()):
            task=self.tasks.item(index)
            item=QListWidgetItem()
            item.setData(Qt.ItemDataRole.UserRole, task)
            item.setText(str(task))
            item.setCheckState(Qt.CheckState.Unchecked)
            if task.isfinish==True:
                item.setIcon(QIcon("images/ic_finished.png"))
            else:
                item.setIcon(QIcon("images/ic_notfinished.png"))
            if isinstance(task.deadline,str):
                task.deadline= datetime.date.fromisoformat(task.deadline)
            if isinstance(task.deadlinetime,str):
                task.deadlinetime=datetime.time.fromisoformat(task.deadlinetime)
            self.listWidgetTask.addItem(item)
    def processNew(self):
        self.lineEditTitle.setText("")
        self.textEditContent.setText("")
        self.dateEditDeadline.setSpecialValueText(None)
        self.radFinished.setAutoExclusive(False)
        self.radNotFinished.setAutoExclusive(False)
        self.radFinished.setChecked(False)
        self.radNotFinished.setChecked(False)
        self.radFinished.setAutoExclusive(True)
        self.radNotFinished.setAutoExclusive(True)
        self.selectedTask=None
        self.lineEditTitle.setFocus()
    def processSave(self):
        title=self.lineEditTitle.text()
        content=self.textEditContent.toPlainText()
        date=self.dateEditDeadline.date().toPyDate()
        time=self.timeEditDeadline.time().toPyTime()
        isFinished=self.radFinished.isChecked()
        task=Task(title,content,date,time,isFinished)
        if self.selectedTask==None:
            self.tasks.add(task)
        else:
            index=self.tasks.index(self.selectedTask)
            self.tasks.update(index,task)
        self.selectedTask = task
        self.showTasksIntoQListWidget()
        self.fileFactory.writeData("database.json",self.tasks.lists)
    def processRemove(self):
        answer = QMessageBox.question(
            self.MainWindow,
            'Confirmation',
            'Do you want to remove checked Items?',
            QMessageBox.StandardButton.Yes |
            QMessageBox.StandardButton.No
        )
        if answer == QMessageBox.StandardButton.No:
            return
        size=self.listWidgetTask.count()
        for index in range(size-1,-1,-1):
            item=self.listWidgetTask.item(index)
            if item.checkState()==Qt.CheckState.Checked:
                self.tasks.removeByIndex(index)
        self.selectedTask = None
        self.showTasksIntoQListWidget()
        self.fileFactory.writeData("database.json", self.tasks.lists)
    def processItemSelection(self):
        row=self.listWidgetTask.currentRow()
        task=self.tasks.item(row)
        self.lineEditTitle.setText(task.title)
        self.textEditContent.setText(task.content)
        self.dateEditDeadline.setDate(task.deadline)
        self.timeEditDeadline.setTime(task.deadlinetime)
        if task.isfinish:
            self.radFinished.setChecked(True)
            self.radNotFinished.setChecked(False)
        else:
            self.radFinished.setChecked(False)
            self.radNotFinished.setChecked(True)
        self.selectedTask=task
    def show(self):
        self.MainWindow.show()

– Bước 7: Cuối cùng ta tạo lớp “MyApp.py” và viết mã lệnh như dưới đây để khởi chạy chương trình:

from PyQt6.QtWidgets import QApplication, QMainWindow

from MainWindowEx import MainWindowEx

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

Chạy “MyApp.py” lên ta sẽ có kết quả như mong muốn.

Mã lệnh đầy đủ của dự án các bạn tải ở đây:

https://www.mediafire.com/file/p8f1hbqvr7w7o6s/LearnQTimeEdit.rar/file

Như vậy tới đây Tui đã trình bày xong lý thuyết cũng như kỹ thuật sử dụng QTimeEdit , củng cố bài cũ QDateEdit và ứng dụng vào quản lý Task, củng cố lại các kiến thức liên quan tới lập trình hướng đối tượng, cách serialize và deserialize đối tượng ra JSonArray, củng cố lại kiến thức và kỹ thuật liên quan tới QListWidget để hiển thị và tương tác dữ liệu trên giao diện.

Các bạn cố gắng thực hành lại bài này nhiều lần để nắm rõ hơn về lập trình hướng đối tượng, cách tạo các lớp độc lập để tái sử dụng tốt hơn

Bài học sau Tui sẽ trình bày về QDateTimeEdit để tạo ra widget cho người dùng vừa nhập Ngày tháng năm vừa nhập giờ phú giây, nó cũng là một trong các Widget quan trọng và phổ biến, được sử dụng thường xuyên trong các phần mềm. Các bạn chú ý theo dõi

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

Bài 19: QDateEdit – Basic Widgets – PyQt6

Trong các dữ liệu, hầu hết chúng đều có liên quan tới ngày tháng, ví dụ như đối với Sản phẩm thì ta thường có các thuộc tính: Ngày nhập hàng, ngày xuất hàng, hạn sử dụng. Với Customer thì ta có ngày tháng năm sinh, ngày mua hàng, ngày thanh toán đơn hàng… Python/ PyQt cũng như các nền tảng khác đều hỗ trợ các widget/control cung cấp nhập liệu liên quan tới ngày tháng. Với PyQt6 thì có các widget như QDateEdit dùng để nhập liệu và hiển thị ngày tháng năm, QTimeEdit dùng để nhập liệu và hiển thị giờ – phút, QDateTimeEdit dùng để vừa hiển thị và nhập liệu ngày tháng năm – giờ phút. Tùy vào từng mục đích lưu trữ dữ liệu mà ta sẽ sử dụng các widget này một cách phù hợp để cung cấp giao diện nhập liệu cũng như hiển thị.

Bài học này Tui sẽ trình bày về QDateEdit, nó là widget dùng để nhập liệu và hiểu thị dữ liệu liên quan tới Ngày – Tháng – Năm. Trước khi vào coding ta xem một số thuộc tính, phương thức, signal thường dùng của widget này:

Thuộc tính, phương thức, signalÝ nghĩa chức năng
QDateEdit(self)Constructor để tạo đối tượng QDateEdit. Nếu dùng constructor này thì người sử dụng sẽ dùng các phím mũi tên lên/xuống hay nhấn vào biểu tượng lên/xuống của widget để thay đổi ngày tháng năm.
QDateEdit(self,calendarPopup=True)Constructor để tạo đối tượng QDateEdit. Nếu dùng constructor này thì người sử dụng sẽ dùng Calendar popup để lựa chọn
date()Hàm trả về ngày tháng năm mà người sử dụng nhập trên giao diện, nó có kiểu QDate, do đó muốn chuyển về date trong python thuần túy thì ta gọi thêm hàm toPyDate()
setDate(date)Hàm thiết lập Ngày – Tháng – Năm cho widget
minimumDateThuộc tính thiết lập ngày nhỏ nhất
maximumDateThuộc tính thiết lập ngày lớn nhất
setDisplayFormat()Phương thức xác định cách thức hiện thị dữ liệu Ngày – Tháng – Năm lên giao diện
editingFinishedSignal để xử lý khi người dùng hoàn tất việc nhập liệu Ngày – Tháng – Năm (đã chọn và nhấn phím Enter)
dateChanged Signal để xử lý khi người dùng chọn lựa Ngày – Tháng – Năm trên Widget

Dưới đây là các bước khai báo và sử dụng QDateEdit:

Bước 1: Khai báo và khởi tạo đối tượng QDateEdit

self.date_edit = QDateEdit(self,calendarPopup=True)

Bước 2: Thiết lập các giá trị cho các thuộc tính

today=date.today()
self.date_edit.setDate(today)
self.date_edit.setDisplayFormat("dd/MM/yyyy")

Mã lệnh ở trên thiết lập ngày hiện tại của máy tính cho QDateEdit thông qua hàm setDate. Và thiết lập cách thức hiển thị Ngày – Tháng- Năm cho QDateEdit. Ở ví dụ trên tui dùng cú pháp: dd/MM/yyyy

  • dd: Ký hiệu d đại diện cho day, ta viết 2 ký tự dd thì những ngày nhỏ hơn 9 sẽ tự động được chèn thêm số 0 đằng trước. Ví dụ như 08, 07
  • MM: Ký hiệu M đại hiện cho Month, ta viết 2 ký tự MM thì những tháng nhỏ hơn 9 sẽ tự động được chèn thêm số 0 đằng trước. Ví dụ tháng 01, tháng 02
  • yyyy: Ký hiệu y đại diện cho Year, ta viết 4 ký tự yyyy để lấy đầy đủ năm với 4 ký số, ví dụ 2023, 2022…

Bước 3: Thiết lập signal cho QDateEdit khi người sử dụng lựa chọn xong Ngày Tháng Năm

self.date_edit.editingFinished.connect(self.SelectedDate)
def SelectedDate(self):
    value = self.date_edit.date()
    print(type(value))
    print(str(value.toPyDate()))

Hàm date() sẽ trả về dữ liệu có kiểu QDate, nên muốn đưa về Python date thì ta gọi thêm hàm toPyDate()

Tuy nhiên editingFinished signal thì người dùng cần nhấn phím Enter để xác nhận. Do đó để tiện lại ta có thể dùng dateChanged signal, bất cứ khi nào người dùng lựa chọn ngày tháng năm trên giao diện thì ta sẽ lấy được dữ liệu này:

self.date_edit.dateChanged.connect(self.ChangingDate)
def ChangingDate(self):
    value = self.date_edit.date()
    print(type(value))
    print(str(value.toPyDate()))

Chúng ta sẽ ứng dụng QDateEdit, QCheckBoxQListWidget để viết phần mềm Quản lý thực phẩm. Phần mềm này sẽ cung cấp giao diện nhập dữ liệu Thực phẩm (Sản phẩm) bao gồm: Mã sản phẩm, tên sản phẩm, đơn giá, hạn sử dụng; Sản phẩm thì có sản phẩm được miễn thuế hoặc không, dùng Icon để phân biệt 2 loại sản phẩm này. Đồng thời nếu hạn sử dụng mà trong khoảng 1 tuần tính tới thời điểm hiện tại thì tô màu đỏ cho (thực phẩm) sản phẩm này:

  • Chương trình cung cấp chức năng “Save” Product có 2 nhiệm vụ: Thêm mới và Chỉnh sửa dữ liệu, nếu Product thêm vào mà trùng Id thì chương trình sẽ chỉnh sửa dữ liệu. Thao tác Save sẽ đồng thì lưu toàn bộ Product xuống ổ cứng với định dạng JSonArray
  • Ngoài ra, nút “Delete” sẽ xác nhận xóa các Product được Checked trong QListWidget.

Các mã lệnh trong bài dự án này sẽ được tách ra thành các lớp riêng biệt để tái sử dụng.

Trước tiên ta tạo một dự án tên “LearnQDateEdit” với các cấu trúc và tập tin như dưới đây:

  • Product.py” là mã lệnh để tạo lớp đối tượng Product có các thuộc tính như Mã, tên, giá, ngày tháng(hạn dùng)
  • FileFactory.py” là mã lệnh dùng để ghi toàn bộ dữ liệu xuống ổ cứng với định dạng JSonArray, cũng như phục hồi và mô hình hóa lại dữ liệu hướng đối tượng
  • MainWindow.ui” là file thiết kế giao diện bằng Qt Designer
  • MainWindow.py” là file python code được generate tự động từ “MainWindow.ui”. Cách thiết kế và tích hợp công cụ tự động Generate đã được hướng dẫn rất chi tiết ở những bài học đầu tiên, các bạn cần xem lại
  • MainWindowEx.py” là mã lệnh ta bổ sung, kế thừ từ lớp được generate trong MainWindow.py để xử lý các sự kiện người dùng
  • MyApp.py” là mã lệnh để thực thi chương trình
  • database.json” là file JSonArray để lưu trữ các dữ liệu mà người dùng thao tác trên phần mềm.

Bước 1: Viết mã lệnh cho “Product.py”, mã lệnh được thể hiện như dưới đây:

class Product:
    def __init__(self,ProductId,ProductName,Price,ExpiredDate,FreeTax):
        self.ProductId=ProductId
        self.ProductName=ProductName
        self.Price=Price
        self.ExpiredDate=ExpiredDate
        self.FreeTax=FreeTax
    def __str__(self):
        return str(self.ProductId) +" - "+self.ProductName

Ta khai báo lớp Product cùng với constructor và các đối số chính xác như trên để việc serialize deserialize JSon data được chính xác.

hàm __str__ dùng để hiển thị dữ liệu của đối tượng lên giao diện, ta muốn hiển thị các thuộc tính nào thì viết mã lệnh trong hàm này.

Bước 2: Viết mã lệnh cho “FileFactory.py“, class này dùng để serialize và deserialize dữ liệu với định dạng JSonArray:

import json
import os

class FileFactory:
    #path: path to serialize array of product
    #arrData: array of Product
    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 Product
    #ClassName: Product
    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

Chúng ta lưu ý rằng, Tui viết “FileFactory.py” ở trên nó sẽ hiểu bất kỳ kiểu dữ liệu danh sách nào. Ví dụ như bạn có thể Serialize và Deserialize danh sách Product, hoặc Nhân viên, hoặc Khách hàng…. lớp này đều hiểu hết. Nên các bài sau nếu có lưu danh sách dữ liệu dạng JSonArray thì các bạn có thể tái sử dụng luôn “FileFactory.py” mà không cần phải bổ sung thêm bất kỳ mã lệnh nào.

Bước 3: Dùng Qt Designer để thiết kế giao diện “MainWindow.ui” cho phần mềm.

Các bạn kéo thả các Widget và đặt tên giống như hình minh họa ở trên.

Bước 4: Dùng công cụ để Generate Python code cho “MainWindow.ui” ta có file “MainWindow.py”:

# 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(457, 533)
        font = QtGui.QFont()
        font.setPointSize(10)
        MainWindow.setFont(font)
        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.groupBox = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox.setGeometry(QtCore.QRect(10, 20, 441, 161))
        self.groupBox.setStyleSheet("background-color: rgb(255, 213, 255);")
        self.groupBox.setFlat(False)
        self.groupBox.setCheckable(False)
        self.groupBox.setObjectName("groupBox")
        self.label = QtWidgets.QLabel(parent=self.groupBox)
        self.label.setGeometry(QtCore.QRect(30, 20, 47, 14))
        self.label.setObjectName("label")
        self.lineEditId = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditId.setGeometry(QtCore.QRect(110, 20, 251, 20))
        self.lineEditId.setObjectName("lineEditId")
        self.lineEditName = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditName.setGeometry(QtCore.QRect(110, 50, 251, 20))
        self.lineEditName.setObjectName("lineEditName")
        self.label_2 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_2.setGeometry(QtCore.QRect(30, 50, 47, 14))
        self.label_2.setObjectName("label_2")
        self.lineEditPrice = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditPrice.setGeometry(QtCore.QRect(110, 80, 251, 20))
        self.lineEditPrice.setObjectName("lineEditPrice")
        self.label_3 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_3.setGeometry(QtCore.QRect(30, 80, 47, 14))
        self.label_3.setObjectName("label_3")
        self.dateEditExpiredDate = QtWidgets.QDateEdit(parent=self.groupBox)
        self.dateEditExpiredDate.setGeometry(QtCore.QRect(110, 110, 110, 22))
        self.dateEditExpiredDate.setCalendarPopup(True)
        self.dateEditExpiredDate.setDate(QtCore.QDate(2023, 1, 1))
        self.dateEditExpiredDate.setObjectName("dateEditExpiredDate")
        self.label_4 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_4.setGeometry(QtCore.QRect(30, 110, 71, 16))
        self.label_4.setObjectName("label_4")
        self.chkFreeTax = QtWidgets.QCheckBox(parent=self.groupBox)
        self.chkFreeTax.setGeometry(QtCore.QRect(110, 140, 151, 18))
        self.chkFreeTax.setObjectName("chkFreeTax")
        self.groupBox_2 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_2.setGeometry(QtCore.QRect(10, 240, 441, 241))
        self.groupBox_2.setStyleSheet("background-color: rgb(255, 213, 255);")
        self.groupBox_2.setObjectName("groupBox_2")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox_2)
        self.verticalLayout.setObjectName("verticalLayout")
        self.listWidgetProduct = QtWidgets.QListWidget(parent=self.groupBox_2)
        self.listWidgetProduct.setStyleSheet("")
        self.listWidgetProduct.setObjectName("listWidgetProduct")
        self.verticalLayout.addWidget(self.listWidgetProduct)
        self.label_5 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_5.setGeometry(QtCore.QRect(10, 0, 441, 31))
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(True)
        font.setWeight(75)
        self.label_5.setFont(font)
        self.label_5.setStyleSheet("color: rgb(0, 0, 127);")
        self.label_5.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label_5.setObjectName("label_5")
        self.groupBox_3 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_3.setGeometry(QtCore.QRect(10, 180, 441, 61))
        self.groupBox_3.setStyleSheet("background-color: rgb(255, 254, 225);")
        self.groupBox_3.setTitle("")
        self.groupBox_3.setObjectName("groupBox_3")
        self.pushButtonNew = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonNew.setGeometry(QtCore.QRect(50, 10, 75, 41))
        self.pushButtonNew.setStyleSheet("background-color: rgb(170, 255, 255);")
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("images/ic_add.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonNew.setIcon(icon1)
        self.pushButtonNew.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonNew.setObjectName("pushButtonNew")
        self.pushButtonSave = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonSave.setGeometry(QtCore.QRect(180, 10, 75, 41))
        self.pushButtonSave.setStyleSheet("background-color: rgb(170, 255, 255);")
        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(32, 32))
        self.pushButtonSave.setObjectName("pushButtonSave")
        self.pushButtonDelete = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonDelete.setGeometry(QtCore.QRect(310, 10, 91, 41))
        self.pushButtonDelete.setStyleSheet("background-color: rgb(170, 255, 255);")
        icon3 = QtGui.QIcon()
        icon3.addPixmap(QtGui.QPixmap("images/ic_remove.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonDelete.setIcon(icon3)
        self.pushButtonDelete.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonDelete.setObjectName("pushButtonDelete")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 457, 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 - Food Management"))
        self.groupBox.setTitle(_translate("MainWindow", "Product Detail:"))
        self.label.setText(_translate("MainWindow", "ID:"))
        self.label_2.setText(_translate("MainWindow", "Name:"))
        self.label_3.setText(_translate("MainWindow", "Price:"))
        self.label_4.setText(_translate("MainWindow", "Expired Date:"))
        self.chkFreeTax.setText(_translate("MainWindow", "Free Tax"))
        self.groupBox_2.setTitle(_translate("MainWindow", "List of Products"))
        self.label_5.setText(_translate("MainWindow", "Food Management"))
        self.pushButtonNew.setText(_translate("MainWindow", "New"))
        self.pushButtonSave.setText(_translate("MainWindow", "Save"))
        self.pushButtonDelete.setText(_translate("MainWindow", "Delete"))

Bước 5: Ta tạo file “MainWindowEx.py” kế thừa từ “MainWindow.py” và tiến hành viết các sự kiện người dùng tương tác:

import datetime

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QListWidgetItem, QMessageBox

from FileFactory import FileFactory
from MainWindow import Ui_MainWindow
from Product import Product

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        self.arrData=[]
        self.fileFactory=FileFactory()

Trong MainWindowEx ta bổ sung constructor để khởi tạo 2 biến:

  • Biến arrData để lưu trữ danh sách hướng đối tượng các Product mà người sử dụng tương tác trên giao diện
  • Biến fileFactory là biến dùng để gọi đối tượng FileFactory nhằm sử dụng Serialize và Deserialize dữ liệu với định dạng JSonArray.

Tiếp theo ta Override hàm setupUi để khởi tạo giao diện cũng như gán các signal và slot cho giao diện tương tác:

def setupUi(self, MainWindow):
    super().setupUi(MainWindow)
    self.MainWindow=MainWindow
    self.arrData = self.fileFactory.readData("database.json", Product)
    self.showProductIntoQListWidget()
    self.pushButtonNew.clicked.connect(self.processNew)
    self.pushButtonSave.clicked.connect(self.processSave)        
    self.pushButtonDelete.clicked.connect(self.processDelete)
    self.listWidgetProduct.itemSelectionChanged.connect(self.processItemSelection)

Khi khởi động phần mềm, chương trình sẽ đọc dữ liệu (deserialize) “database.json” thành danh sách hướng đối tượng Product và biến arrayData sẽ lưu trữ danh sách dữ liệu này.

Sau đó hàm showProductIntoQListWidget() sẽ được triệu gọi để hiển thị toàn bộ Product trong arrData lên giao diện QListWidget:

def showProductIntoQListWidget(self):
    self.listWidgetProduct.clear()
    for product in self.arrData:
        item=QListWidgetItem()
        item.setData(Qt.ItemDataRole.UserRole, product)
        item.setText(str(product))
        item.setCheckState(Qt.CheckState.Unchecked)
        if product.FreeTax==True:
            item.setIcon(QIcon("images/ic_tax.png"))
        else:
            item.setIcon(QIcon("images/ic_tax_including.png"))
        if isinstance(product.ExpiredDate,str):
            product.ExpiredDate=datetime.date.fromisoformat(product.ExpiredDate)
        day=(product.ExpiredDate-datetime.date.today()).days
        if day<=7:
            item.setForeground(Qt.GlobalColor.red)
        self.listWidgetProduct.addItem(item)

Chương trình dùng vòng lặp để duyệt qua các Product trong arrData, sau đó sẽ tạo các đối tượng QListWidgetItem để hiển thị lên giao diện. Nếu Food nào là FreeTax thì dùng icon freetax.

Ngoài ra vì dữ liệu ngày tháng khi serialize xuống ổ cứng với định dạng JSon thì nó là chuỗi, nên ta dùng hàm isinstance để kiểm tra xem thuộc tính ExpiredDate có phải đang là chuỗi hay không, nếu là chuỗi thì ta ép kiểu qua kiểu Python Date bằng hàm: datetime.date.fromisoformat(product.ExpiredDate)

Sau đó ta dùng phép trừ của ngày hiện tại với ngày hết hạn để tìm ra số ngày, nếu day<=7 thì ta dùng chữ màu đỏ cho QListWidgetItem này bằng hàm item.setForeground(Qt.GlobalColor.red).

-Hàm processNew dùng để xóa các dữ liệu đang nhập trên giao diện và focus vào ô ID để người dùng nhập dữ liệu mới được nhanh chóng hơn:

def processNew(self):
    self.lineEditId.setText("")
    self.lineEditName.setText("")
    self.lineEditPrice.setText("")
    self.chkFreeTax.setCheckState(Qt.CheckState.Unchecked)
    self.lineEditId.setFocus()

-Hàm processSave dùng để lưu dữ liệu, nêu Id không trùng thì lưu mới, nếu Id trùng thì lưu cập nhật:

def processSave(self):
    id=self.lineEditId.text()
    name=self.lineEditName.text()
    price=float(self.lineEditPrice.text())
    date=self.dateEditExpiredDate.date().toPyDate()
    freeTax=self.chkFreeTax.isChecked()
    newItem = Product(id,name, price, date,freeTax)
    oldItem=self.checkDuplicate(id)
    if oldItem !=None:
        index = self.arrData.index(oldItem)
        self.arrData[index]=newItem
    else:
        self.arrData.append(newItem)
    self.showProductIntoQListWidget()
    self.fileFactory.writeData("database.json",self.arrData)

Để biết được id nào trùng hay không, Tui cung cấp hàm checkDuplicate dưới đây:

def checkDuplicate(self,id):
    items=[x for x in self.arrData if x.ProductId == id]
    if len(items)==0:
        return None
    return items[0]

Nếu không tìm thấy item nào có id trong danh sách thì chương trình trả về None. Còn nều tìm thấy thì trả về Item tìm thấy đó.

– Hàm processDelete dùng để xóa các QListWidgetItem được checked trên giao diện ra khỏi QListWidget:

def processDelete(self):
    answer = QMessageBox.question(
        self.MainWindow,
        'Confirmation',
        'Do you want to remove checked Items?',
        QMessageBox.StandardButton.Yes |
        QMessageBox.StandardButton.No
    )
    if answer == QMessageBox.StandardButton.No:
        return
    for index in range(self.listWidgetProduct.count()):
        item=self.listWidgetProduct.item(index)
        if item.checkState()==Qt.CheckState.Checked:
            product=item.data(Qt.ItemDataRole.UserRole)
            self.arrData.remove(product)
    self.showProductIntoQListWidget()
    self.fileFactory.writeData("database.json", self.arrData)

Chương trình sẽ hiển thị cửa sổ xác nhận có muốn xóa hay không, nếu đồng ý xóa thì chương trình sẽ xóa, sau đó sẽ lưu (serialize) lại dữ liệu xuống JSonArray cũng như hiển thị lại giao diện. Hình minh họa xóa:

-Cuối cùng là hàm/signal “processItemSelection“, hàm này sẽ lắng nghe người dùng đang chọn QListWidgetItem nào trên giao diện và hiển thị thông tin chi tiết lên các ô dữ liệu. Người dùng có thể dùng phím mũi tên di chuyển hoặc click chuột vào item bất kỳ:

def processItemSelection(self):
    row=self.listWidgetProduct.currentRow()
    item=self.listWidgetProduct.item(row)
    product=item.data(Qt.ItemDataRole.UserRole)
    self.lineEditId.setText(str(product.ProductId))
    self.lineEditName.setText(product.ProductName)
    self.lineEditPrice.setText(str(product.Price))
    if isinstance(product.ExpiredDate, str):
        product.ExpiredDate = datetime.date.fromisoformat(product.ExpiredDate)
    self.dateEditExpiredDate.setDate(product.ExpiredDate)
    if product.FreeTax:
        self.chkFreeTax.setCheckState(Qt.CheckState.Checked)
    else:
        self.chkFreeTax.setCheckState(Qt.CheckState.Unchecked)

Dưới đây là mã lệnh đầy đủ của MainWindowEx.py:

import datetime

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QListWidgetItem, QMessageBox

from FileFactory import FileFactory
from MainWindow import Ui_MainWindow
from Product import Product

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        self.arrData=[]
        self.fileFactory=FileFactory()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.arrData = self.fileFactory.readData("database.json", Product)
        self.showProductIntoQListWidget()
        self.pushButtonNew.clicked.connect(self.processNew)
        self.pushButtonSave.clicked.connect(self.processSave)
        self.pushButtonDelete.clicked.connect(self.processDelete)
        self.listWidgetProduct.itemSelectionChanged.connect(self.processItemSelection)
    def showProductIntoQListWidget(self):
        self.listWidgetProduct.clear()
        for product in self.arrData:
            item=QListWidgetItem()
            item.setData(Qt.ItemDataRole.UserRole, product)
            item.setText(str(product))
            item.setCheckState(Qt.CheckState.Unchecked)
            if product.FreeTax==True:
                item.setIcon(QIcon("images/ic_tax.png"))
            else:
                item.setIcon(QIcon("images/ic_tax_including.png"))
            if isinstance(product.ExpiredDate,str):
                product.ExpiredDate=datetime.date.fromisoformat(product.ExpiredDate)
            day=(product.ExpiredDate-datetime.date.today()).days
            if day<=7:
                item.setForeground(Qt.GlobalColor.red)
            self.listWidgetProduct.addItem(item)
    def processNew(self):
        self.lineEditId.setText("")
        self.lineEditName.setText("")
        self.lineEditPrice.setText("")
        self.chkFreeTax.setCheckState(Qt.CheckState.Unchecked)
        self.lineEditId.setFocus()
    def checkDuplicate(self,id):
        items=[x for x in self.arrData if x.ProductId == id]
        if len(items)==0:
            return None
        return items[0]
    def processSave(self):
        id=self.lineEditId.text()
        name=self.lineEditName.text()
        price=float(self.lineEditPrice.text())
        date=self.dateEditExpiredDate.date().toPyDate()
        freeTax=self.chkFreeTax.isChecked()
        newItem = Product(id,name, price, date,freeTax)
        oldItem=self.checkDuplicate(id)
        if oldItem !=None:
            index = self.arrData.index(oldItem)
            self.arrData[index]=newItem
        else:
            self.arrData.append(newItem)
        self.showProductIntoQListWidget()
        self.fileFactory.writeData("database.json",self.arrData)
    def processDelete(self):
        answer = QMessageBox.question(
            self.MainWindow,
            'Confirmation',
            'Do you want to remove checked Items?',
            QMessageBox.StandardButton.Yes |
            QMessageBox.StandardButton.No
        )
        if answer == QMessageBox.StandardButton.No:
            return
        for index in range(self.listWidgetProduct.count()):
            item=self.listWidgetProduct.item(index)
            if item.checkState()==Qt.CheckState.Checked:
                product=item.data(Qt.ItemDataRole.UserRole)
                self.arrData.remove(product)
        self.showProductIntoQListWidget()
        self.fileFactory.writeData("database.json", self.arrData)
    def processItemSelection(self):
        row=self.listWidgetProduct.currentRow()
        item=self.listWidgetProduct.item(row)
        product=item.data(Qt.ItemDataRole.UserRole)
        self.lineEditId.setText(str(product.ProductId))
        self.lineEditName.setText(product.ProductName)
        self.lineEditPrice.setText(str(product.Price))
        if isinstance(product.ExpiredDate, str):
            product.ExpiredDate = datetime.date.fromisoformat(product.ExpiredDate)
        self.dateEditExpiredDate.setDate(product.ExpiredDate)
        if product.FreeTax:
            self.chkFreeTax.setCheckState(Qt.CheckState.Checked)
        else:
            self.chkFreeTax.setCheckState(Qt.CheckState.Unchecked)
    def show(self):
        self.MainWindow.show()

Bước 6: Cuối cùng ta tạo lớp “MyApp.py” và viết mã lệnh như dưới đây để khởi chạy 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()

Mã lệnh đầy đủ của dự án các bạn tải ở đây:

https://www.mediafire.com/file/y7urznpsswmhgcl/LearnQDateEdit.rar/file

Như vậy tới đây Tui đã trình bày xong lý thuyết cũng như kỹ thuật sử dụng QDateEdit và ứng dụng vào quản lý Food, củng cố lại các kiến thức liên quan tới lập trình hướng đối tượng, cách serialize và deserialize đối tượng ra JSonArray, củng cố lại kiến thức và kỹ thuật liên quan tới QListWidget để hiển thị và tương tác dữ liệu trên giao diện.

Các bạn cố gắng thực hành lại bài này nhiều lần vì độ khó nó đã được tăng lên do Tui đã tách ra các lớp độc lập để tái sử dụng.

Bài học sau Tui sẽ trình bày về QTimeEdit để tạo ra widget cho người dùng nhập Thời gian, nó cũng là một trong các Widget quan trọng và phổ biến, được sử dụng thường xuyên trong các phần mềm. Các bạn chú ý theo dõi

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

Bài 18: QListWidget – Part 2– Basic Widgets – PyQt6

Trong bài QListWidget Part 1 các bạn đã nắm rõ và biết cách sử dụng các tính năng của Widget này. Trong Part 2 chúng ta sẽ tiếp tục củng cố các kiến thức và kỹ năng xử lý với QListWidget thông qua bài minh họa quản lý Employee, các bạn sẽ dùng Qt Designer để thiết kế giao diện, và thực hiện các chức năng nghiệp vụ trên QListWidget và các control khác bằng ngôn ngữ Python trong công cụ Pycharm được mô tả như dưới đây:

  • (1) Chức năng mô hình hóa hướng đối tượng: Lớp Employee được thiết kế và được mô hình hóa dữ liệu từ giao diện vào danh sách hướng đối tượng Employee cũng như mapping thành định dạng JSon Array để lưu vào ổ cứng (gọi là Json Encode). Chức năng này sẽ hỗ trợ các thao tác Thêm mới Employee, Chỉnh sửa Employee và Xóa Employee. Khi có sự thay đổi dữ liệu thì chương trình sẽ lưu dữ liệu xuống ổ cứng với định dạn JSon
  • (2) Chức năng phục hồi dữ liệu và mô hình hóa lại hướng đối tượng từ JSon Array được lưu trong ổ cứng lên bộ nhớ và được mapping và danh sách đối tượng Employee (gọi là Json Decode). Chức năng này sẽ hỗ trợ thao tác hiển thị lại dữ liệu danh sách Employee lên giao diện
  • (3) Chức năng “New“: Khi nhấn vào button này thì các QLineEdit sẽ bỏ trống và con trỏ văn bản được focus tới ô “Name” để người sử dụng dễ dàng nhập liệu
  • (4) Chức năng “Save“: Chức năng này có 2 nhiệm vụ là lưu mới Employee hoặc cập nhật Employee. Nếu Email tồn tại thì cập nhật, còn Email không tồn tại thì thêm mới. Các bạn có thể bổ sung thêm các thuộc tính khác như ID cho Employee. Khi “Save” thành công thì chương trình vừa hiển thị dữ liệu lên giao diện QListWidget vừa lưu danh sách dữ liệu từ giao diện xuống ổ cứng với định dạng JSon (JSon Array vì ta lưu danh sách, tuy nhiên để nói ngắn gọn ta có thể nói Json thì mọi người đều hiểu). Lưu ý rằng tương ứng với giới tính mà Icon hiển thị của mỗi QListWidgetItem sẽ có Icon khác nhau.
  • (5) Chức năng “Delete“: Chương trình sẽ kiểm tra tất cả các dòng Item nào được checked thì sẽ xác nhận xóa, nếu đồng ý xóa thì chương trình sẽ xóa những dòng Item nào được checked.
  • (6) Chức năng “Close“: Chức năng này dùng để đóng cửa sổ phần mềm, khi nhấn vào nút lệnh này chương trình sẽ hiển thị cửa sổ xác nhận Đóng hay không, nếu đồng ý đóng thì chương trình sẽ đóng cửa sổ.
  • (7) Chức năng “Selection“: Khi người sử dụng chọn dòng Item nào trong QListWidget thì thông tin chi tiết trong dòng đang chọn sẽ được hiển thị ngược lại lên giao diện.

Ta tiến hành làm dự án này.

Trong Pycharm, đặt tên dự án “LearnQListWidgetEmployee“, có cấu hình như dưới đây:

Thư mục images, các bạn có thể lấy bất kỳ hình ảnh nào có kích thước 32×32 để dùng.

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

class Employee:
    def __init__(self,name,email,gender):
        self.name=name
        self.email=email
        self.gender=gender
    def __str__(self):
        if self.gender==True:
            return self.name+" - "+self.email +"(Woman)"
        else:
            return self.name + " - " + self.email + "(Man)"

Mã lệnh ở trên tạo một lớp đối tượng Employee, nó sẽ lưu trữ các dữ liệu trên giao diện mà người dùng nhập liệu, Lớp này có 1 constructor có 3 đối số là name, email, và gender. Ngoài ra Tui còn bổ sung thêm phương thức __str__ để hiển thị thông tin dữ liệu của đối tượng lên giao diện, cách hiển thị như thế nào là do chúng ta quyết định ở hàm này.

Tiếp theo nữa là các bạn dùng công cụ Qt Designer đã được học rất kỹ lưỡng ở những bài học trước và nó đã được tích hợp vào Pycharm để thiết kế giao diện có tên “MainWindow.ui”, giao diện như hình dưới đây:

Các bạn tiến hành kéo thả các Widget ra giao diện bao gồm QLineEdit, QPushButton, QLabel, QListWidget và đặt tên các Widget như hình trên.

Sau đó dùng chức năng Generate Python Code cho “MainWindow.ui” thành “MainWindow.py” có mã lệnh như dưới đây (tự động tạo):

# 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(490, 528)
        font = QtGui.QFont()
        font.setPointSize(12)
        MainWindow.setFont(font)
        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.groupBox = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox.setGeometry(QtCore.QRect(20, 40, 451, 121))
        self.groupBox.setObjectName("groupBox")
        self.label = QtWidgets.QLabel(parent=self.groupBox)
        self.label.setGeometry(QtCore.QRect(20, 30, 71, 16))
        self.label.setObjectName("label")
        self.lineEditName = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditName.setGeometry(QtCore.QRect(120, 30, 311, 22))
        self.lineEditName.setObjectName("lineEditName")
        self.lineEditEmail = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditEmail.setGeometry(QtCore.QRect(120, 60, 311, 22))
        self.lineEditEmail.setObjectName("lineEditEmail")
        self.label_2 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_2.setGeometry(QtCore.QRect(20, 60, 71, 16))
        self.label_2.setObjectName("label_2")
        self.radWoman = QtWidgets.QRadioButton(parent=self.groupBox)
        self.radWoman.setGeometry(QtCore.QRect(120, 90, 95, 20))
        self.radWoman.setChecked(True)
        self.radWoman.setObjectName("radWoman")
        self.radMan = QtWidgets.QRadioButton(parent=self.groupBox)
        self.radMan.setGeometry(QtCore.QRect(260, 90, 95, 20))
        self.radMan.setObjectName("radMan")
        self.groupBox_2 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_2.setGeometry(QtCore.QRect(20, 230, 451, 241))
        self.groupBox_2.setObjectName("groupBox_2")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox_2)
        self.verticalLayout.setObjectName("verticalLayout")
        self.listWidgetEmployee = QtWidgets.QListWidget(parent=self.groupBox_2)
        self.listWidgetEmployee.setObjectName("listWidgetEmployee")
        self.verticalLayout.addWidget(self.listWidgetEmployee)
        self.groupBox_3 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_3.setGeometry(QtCore.QRect(20, 160, 451, 71))
        self.groupBox_3.setObjectName("groupBox_3")
        self.pushButtonNew = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonNew.setGeometry(QtCore.QRect(10, 20, 81, 41))
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("images/ic_add.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonNew.setIcon(icon1)
        self.pushButtonNew.setObjectName("pushButtonNew")
        self.pushButtonSave = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonSave.setGeometry(QtCore.QRect(110, 20, 91, 41))
        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.setObjectName("pushButtonSave")
        self.pushButtonDelete = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonDelete.setGeometry(QtCore.QRect(230, 20, 91, 41))
        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")
        self.pushButtonClose = QtWidgets.QPushButton(parent=self.groupBox_3)
        self.pushButtonClose.setGeometry(QtCore.QRect(340, 20, 91, 41))
        icon4 = QtGui.QIcon()
        icon4.addPixmap(QtGui.QPixmap("images/ic_close.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonClose.setIcon(icon4)
        self.pushButtonClose.setObjectName("pushButtonClose")
        self.label_3 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_3.setGeometry(QtCore.QRect(50, 0, 411, 31))
        font = QtGui.QFont()
        font.setPointSize(13)
        font.setBold(True)
        font.setWeight(75)
        self.label_3.setFont(font)
        self.label_3.setStyleSheet("color: rgb(0, 0, 127);")
        self.label_3.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label_3.setObjectName("label_3")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 490, 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)
        MainWindow.setTabOrder(self.lineEditName, self.lineEditEmail)
        MainWindow.setTabOrder(self.lineEditEmail, self.radWoman)
        MainWindow.setTabOrder(self.radWoman, self.radMan)
        MainWindow.setTabOrder(self.radMan, self.pushButtonNew)
        MainWindow.setTabOrder(self.pushButtonNew, self.pushButtonSave)
        MainWindow.setTabOrder(self.pushButtonSave, self.pushButtonDelete)
        MainWindow.setTabOrder(self.pushButtonDelete, self.pushButtonClose)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh - Employee Demo"))
        self.groupBox.setTitle(_translate("MainWindow", "Employee Information:"))
        self.label.setText(_translate("MainWindow", "Name:"))
        self.label_2.setText(_translate("MainWindow", "Email:"))
        self.radWoman.setText(_translate("MainWindow", "Woman"))
        self.radMan.setText(_translate("MainWindow", "Man"))
        self.groupBox_2.setTitle(_translate("MainWindow", "List of Employee:"))
        self.groupBox_3.setTitle(_translate("MainWindow", "Action:"))
        self.pushButtonNew.setText(_translate("MainWindow", "New"))
        self.pushButtonSave.setText(_translate("MainWindow", "Save"))
        self.pushButtonDelete.setText(_translate("MainWindow", "Delete"))
        self.pushButtonClose.setText(_translate("MainWindow", "Close"))
        self.label_3.setText(_translate("MainWindow", "Employee Management"))

Sau đó ta cần tạo thêm một lớp “MainWindowEx.py” như đã học ở những bài trước, lớp này kế thừa từ UI_Main trong Generate Python code ở trên, mục đích của nó là viết mã lệnh xử lý các sự kiện và có thể dễ dàng bổ sung, mở rộng các mã lệnh mới mà không sợ bị ảnh hưởng khi giao diện thay đổi.
Bây giờ Tui giải thích từng đoạn mã lệnh tương ứng với từng chức năng, sau đó Tui sẽ cùng cấp mã lệnh đầy đủ của chương trình ở bên dưới:

Chức năng “New” ta bổ sung signal và slot như bên dưới:

self.pushButtonNew.clicked.connect(self.processNew)
def processNew(self):
    self.lineEditName.setText("")
    self.lineEditEmail.setText("")
    self.lineEditName.setFocus()

Mã lệnh “processNew” sẽ xóa trắng các dữ liệu trong các ô nhập liệu, và focus tới ô nhập Tên.

-Chức năng mapping và lưu dữ liệu từ bộ nhớ xuống Ổ cứng với định dạng JSon:

def writeEmployeeToJson(self):
    dataset=[]
    for i in range(0,self.listWidgetEmployee.count()):
        item=self.listWidgetEmployee.item(i)
        emp=item.data(Qt.ItemDataRole.UserRole)
        dataset.append(emp)
    jsonString=json.dumps([emp.__dict__ for emp in dataset])
    jsonFile=open("database.json", "w")
    jsonFile.write(jsonString)
    jsonFile.close()

Mã lệnh “wireEmployeeToJson” sẽ quét toàn bộ Employee trong QListWidget và đưa vào biến mảng dataset. Sau đó dataset sẽ được json.dumps để lưu toàn bộ dữ liệu xuống ổ cứng với định dạng Json. Ví dụ minh họa cấu trúc Json được lưu xuống ổ cứng “database.json”:

[{"name": "Albert Einstein", "email": "Einstein@gmail.com", "gender": false}, {"name": "Isaac Newton", "email": "newton@hotmail.com", "gender": false}, {"name": "Marie Curie", "email": "Curie@gmail.com", "gender": true}, {"name": "Jane Goodall", "email": "Goodall@gmail.com", "gender": true}, {"name": "Stephen Hawking", "email": "hawking@uel.edu.vn", "gender": false}, {"name": "Aristotle", "email": "Aristotle@uel.edu.vn", "gender": false}, {"name": "Thomas Edison", "email": "Edison@uel.edu.vn", "gender": false}, {"name": "Barbara McClintock", "email": "Barbara@uel.edu.vn", "gender": true}]
  • Tiếp theo là chức năng đọc dữ liệu Json từ ổ cứng lên bộ nhớ thông qua cách Mapping ngược lại lên danh sách hướng đối tượng List Employee:
def readEmployeeFromJson(self):
    if os.path.isfile("database.json") ==False:
        return
    file = open('database.json', "r")
    # Reading from file
    self.dataset = json.loads(file.read(), object_hook=lambda d: Employee(**d))
    file.close()
    for emp in self.dataset:
        item = QListWidgetItem()
        item.setData(Qt.ItemDataRole.UserRole, emp)
        item.setText(str(emp))
        item.setCheckState(Qt.CheckState.Unchecked)
        if emp.gender==True:
            item.setIcon(QIcon("images/ic_woman.png"))
        else:
            item.setIcon(QIcon("images/ic_man.png"))
        self.listWidgetEmployee.addItem(item)

Mã lệnh “readEmployeeFromJson” ở trên sẽ kiểm tra xem file “database.json” có tồn tại hay không, nếu có tồn tại thì chương trình sẽ tiến hành đọc và mapping dữ liệu JSON Array qua list object (danh sách hướng đối tượng Employee) và lưu vào biến dataset. Sau đó ta dùng vòng lặp để hiển thị lên giao diện QListWidget. Lưu ý trong bài này các bạn có thể tách riêng lớp đọc và ghi dữ liệu độc lập với hiển thị lên giao diện. Tuy nhiên để các bạn dễ hiểu hơn nên Tui gộp lại cho các bạn.

-Chức năng “Save” dùng để lưu mới hoặc cập nhật Employee:

self.pushButtonSave.clicked.connect(self.processSave)
def processSave(self):
    insertEmployee=Employee(self.lineEditName.text(),self.lineEditEmail.text(),self.radWoman.isChecked())
    isDuplicated=False
    for i in range(0,self.listWidgetEmployee.count()):
        item=self.listWidgetEmployee.item(i)
        data=item.data(Qt.ItemDataRole.UserRole)
        if insertEmployee.email.lower()==data.email.lower():
            isDuplicated=True
            break
    if not isDuplicated:
        item=QListWidgetItem()
    item.setData(Qt.ItemDataRole.UserRole,insertEmployee)
    item.setText(str(insertEmployee))
    item.setCheckState(Qt.CheckState.Unchecked)
    if self.radWoman.isChecked():
        item.setIcon(QIcon("images/ic_woman.png"))
    else:
        item.setIcon(QIcon("images/ic_man.png"))
    if not isDuplicated:
        self.listWidgetEmployee.addItem(item)
    self.writeEmployeeToJson()

Chức năng “processSave” ở trên, Tui xử lý cho 2 trường hợp: Nếu Employee có email không trùng nhau là thêm mới, nếu email trùng nhau là cập nhật lại tên Employee. Nếu dữ liệu được lưu hoặc cập nhật thành công thì chúng ta gọi chức năng “writeEmployeeToJson” để lưu toàn bộ dữ liệu xuống ổ cứng với định dạng Json. Khi lưu thành công rồi thì ta có thể chạy lại phần mềm, hoặc bất cứ khi nào phần mềm được khởi chạy thì ta gọi hàm “readEmployeeFromJson” lúc này toàn bộ danh sách Employee sẽ được hiển thị ngược lại lên QListWidget.

-Chức năng “Delete” dùng để xóa các Employee được checked trên QListWidget:

def processDelete(self):
    answer = QMessageBox.question(
        self.MainWindow,
        'Confirmation',
        'Do you want to remove checked Items?',
        QMessageBox.StandardButton.Yes |
        QMessageBox.StandardButton.No
    )
    if answer == QMessageBox.StandardButton.No:
        return
    for index in range(self.listWidgetEmployee.count()-1,-1,-1):
        item=self.listWidgetEmployee.item(index)
        if item.checkState()==Qt.CheckState.Checked:
            current_item = self.listWidgetEmployee.takeItem(index)
            del current_item
    self.processNew()
    self.writeEmployeeToJson()

Mã lệnh “processDelete” sẽ hiển thị màn hình xác nhận muốn xóa hay không, nếu xác nhận có thì chương trình sẽ tiến hành dùng vòng lặp chạy qua từng phần tử và kiểm tra xem phần tử đó có checkState() là Qt.CheckState.Checked hay không, nếu có thì ta tiến hành gọi lệnh takeItem để xóa Item này ra khỏi QListWidget. Chú ý rằng vì chúng ta xóa nhiều Item nên cần chạy ngược vòng lặp từ cuối dành sách trở về đầu tiên để mỗi khi item bị xóa khỏi danh sách thì các Item trước đó vẫn không bị thay đổi index (nếu bạn chạy vòng lặp xuôi chắc chắn sẽ sai):

Sau khi xóa thành công thì ta gọi lệnh “writeEmployeeToJson” để lưu lại các Employee còn lại trên giao diện, coi như đây là danh sách mới nhất.

-Chức năng “Close” để đóng cửa sổ chương trình:

self.pushButtonClose.clicked.connect(self.processClose)
def processClose(self):
    msg = QMessageBox()
    msg.setText(f"Are you sure you want to exit ?")
    msg.setWindowTitle("Exit Confirmation")
    msg.setIcon(QMessageBox.Icon.Question)
    buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
    msg.setStandardButtons(buttons)
    result = msg.exec()
    if result == QMessageBox.StandardButton.Yes:
       self.MainWindow.close()

Mã lệnh “processClose” sẽ mở cửa sổ xác nhận thoát phần mềm hay không, nếu xác nhận có thì thoát phần mềm.

-Cuối cùng là chức năng khi người dùng lựa chọn trên QListWidgetItem (nhấn chuột hoặc di chuyển phím mũi tên) thì dữ liệu chi tiết của Item sẽ hiển thị ngược lại lên các ô nhập liệu và QRadioButton:

self.listWidgetEmployee.itemSelectionChanged.connect(self.processItemSelectionChanged)
def processItemSelectionChanged(self):
    current_row=self.listWidgetEmployee.currentRow()
    if current_row<0:
        return
    item=self.listWidgetEmployee.item(current_row)
    emp=item.data(Qt.ItemDataRole.UserRole)
    self.lineEditName.setText(emp.name)
    self.lineEditEmail.setText(emp.email)
    if emp.gender==True:
        self.radWoman.setChecked(True)
    else:
        self.radMan.setChecked(True)

Ví dụ như khi người sử dụng bấm chuột hoặc dùng phím mũi tên lên xuống để chọn Item “Stephen Hawking“, dữ liệu sẽ hiển chi tiết thị lên các ô nhập liệu và QRadioButton:

Dưới đây là mã lệnh đầy đủ của “MainWindowEx.py” với đầy đủ chức năng mô tả ở trên:

import json
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QListWidgetItem, QMessageBox

from Employee import Employee
from MainWindow import Ui_MainWindow
import os.path

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        self.dataset=[]
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.pushButtonNew.clicked.connect(self.processNew)
        self.pushButtonSave.clicked.connect(self.processSave)
        self.listWidgetEmployee.itemSelectionChanged.connect(self.processItemSelectionChanged)
        self.pushButtonDelete.clicked.connect(self.processDelete)
        self.pushButtonClose.clicked.connect(self.processClose)
        self.readEmployeeFromJson()
    def show(self):
        self.MainWindow.show()
    def processNew(self):
        self.lineEditName.setText("")
        self.lineEditEmail.setText("")
        self.lineEditName.setFocus()
    def processSave(self):
        insertEmployee=Employee(self.lineEditName.text(),self.lineEditEmail.text(),self.radWoman.isChecked())
        isDuplicated=False
        for i in range(0,self.listWidgetEmployee.count()):
            item=self.listWidgetEmployee.item(i)
            data=item.data(Qt.ItemDataRole.UserRole)
            if insertEmployee.email.lower()==data.email.lower():
                isDuplicated=True
                break
        if not isDuplicated:
            item=QListWidgetItem()
        item.setData(Qt.ItemDataRole.UserRole,insertEmployee)
        item.setText(str(insertEmployee))
        item.setCheckState(Qt.CheckState.Unchecked)
        if self.radWoman.isChecked():
            item.setIcon(QIcon("images/ic_woman.png"))
        else:
            item.setIcon(QIcon("images/ic_man.png"))
        if not isDuplicated:
            self.listWidgetEmployee.addItem(item)
        self.writeEmployeeToJson()
    def processItemSelectionChanged(self):
        current_row=self.listWidgetEmployee.currentRow()
        if current_row<0:
            return
        item=self.listWidgetEmployee.item(current_row)
        emp=item.data(Qt.ItemDataRole.UserRole)
        self.lineEditName.setText(emp.name)
        self.lineEditEmail.setText(emp.email)
        if emp.gender==True:
            self.radWoman.setChecked(True)
        else:
            self.radMan.setChecked(True)
    def processDelete(self):
        answer = QMessageBox.question(
            self.MainWindow,
            'Confirmation',
            'Do you want to remove checked Items?',
            QMessageBox.StandardButton.Yes |
            QMessageBox.StandardButton.No
        )
        if answer == QMessageBox.StandardButton.No:
            return
        for index in range(self.listWidgetEmployee.count()-1,-1,-1):
            item=self.listWidgetEmployee.item(index)
            if item.checkState()==Qt.CheckState.Checked:
                current_item = self.listWidgetEmployee.takeItem(index)
                del current_item
        self.processNew()
        self.writeEmployeeToJson()
    def processClose(self):
        msg = QMessageBox()
        msg.setText(f"Are you sure you want to exit ?")
        msg.setWindowTitle("Exit Confirmation")
        msg.setIcon(QMessageBox.Icon.Question)
        buttons = QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
        msg.setStandardButtons(buttons)
        result = msg.exec()
        if result == QMessageBox.StandardButton.Yes:
           self.MainWindow.close()
    def writeEmployeeToJson(self):
        dataset=[]
        for i in range(0,self.listWidgetEmployee.count()):
            item=self.listWidgetEmployee.item(i)
            emp=item.data(Qt.ItemDataRole.UserRole)
            dataset.append(emp)
        jsonString=json.dumps([emp.__dict__ for emp in dataset])
        jsonFile=open("database.json", "w")
        jsonFile.write(jsonString)
        jsonFile.close()
    def readEmployeeFromJson(self):
        if os.path.isfile("database.json") ==False:
            return
        file = open('database.json', "r")
        # Reading from file
        self.dataset = json.loads(file.read(), object_hook=lambda d: Employee(**d))
        file.close()
        for emp in self.dataset:
            item = QListWidgetItem()
            item.setData(Qt.ItemDataRole.UserRole, emp)
            item.setText(str(emp))
            item.setCheckState(Qt.CheckState.Unchecked)
            if emp.gender==True:
                item.setIcon(QIcon("images/ic_woman.png"))
            else:
                item.setIcon(QIcon("images/ic_man.png"))
            self.listWidgetEmployee.addItem(item)

Để thực thi chương trình. Ta tạo thêm “MyApp.py” có mã lệnh như dưới đây:

from PyQt6.QtWidgets import QApplication, QMainWindow

from MainWindowEx import MainWindowEx

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

Chạy chương trình lên ta có kết quả như mong muốn.

Mã lệnh chi tiết của chương trình này các bạn tải ở đây:

https://www.mediafire.com/file/n0y4punkxmr00cb/LearnQListWidgetEmployee.rar/file

Như vậy tới đây Tui đã củng cố xong kiến thức và các kỹ thuật xử lý liên quan tới QListWidget. Các bạn đã biết cách ứng dụng QListWidget vào triển khai dự án, sử dụng Widget để hiển thị danh sách dữ liệu, cũng như cách thức xử lý mô hình hóa hướng đối tượng, lưu và phục hồi dữ liệu với định dạng JSon.

Bài học sau Tui trình bày về các Widget liên quan tới Date, DateTime để hỗ trợ xử lý dữ liệu với thời gian, các bạn chú ý theo dõi

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

Bài 17: QListWidget – Part 1– Basic Widgets – PyQt6

Bài học QComboBox các bạn đã biết cách hiển thị dữ liệu dạng danh sách rồi, tuy nhiên mặc định widget này chỉ cho lựa chọn một item, và không hiển thị toàn bộ danh sách ra nên đôi khi nó cũng bất tiện. QListWidget sẽ giúp giải quyết vấn đề này, nó là một widget hiển thị toàn bộ danh sách dữ liệu để quan sát và lựa chọn, trong trường hợp dữ liệu nhiều có thể xuất hiện thêm thanh Scroll để xem thêm dữ liệu.

Một số thuộc tính, phương thức và signal thường dùng của QListWidget:

Thuộc tính/ phương thức/ signalChức năng
QListWidgetClass để tạo đối tượng QListWidget
QListWidgetItemClass dùng để tạo các item cho QListWidget
addItem(QListWidgetItem)Hàm thêm một item vào QListWidget
addItems(iterable)Hàm thêm nhiều items vào QListWidget
insertItem(row, QListWidgetItem)Hàm chèn một item vào QListWidget ở vị trí thứ row
item(row)Hàm trả về QListWidgetItem ở vị trí thứ row
takeItem(row)Hàm xóa một item ở dòng thứ row ra khỏi QListWidget
currentRow()Hàm trả về vị trí đang chọn trong QListWidget
clear()Hàm xóa toàn bộ item trong QListWidget
itemClickedsignal lắng nghe người dùng nhấn chuột vào Item trên QListWidget
itemDoubleClickedsignal lắng nghe người dùng nhấn double chuột vào Item trên QListWidget
itemSelectionChangedsignal lắng nghe người dùng thay đổi lựa chọn Item trên QListWidget

Các bạn theo dõi Tui minh họa cách sử dụng QListWidget cũng như xử lý các sự kiện của Widget này như hình dưới đây:

Chương trình gồm 8 chức năng chính:

  1. Chức năng hiển thị danh sách mặc định ban đầu lên QListWidget, bao gồm các minh hoạt về Icon, màu nền, màu chữ…
  2. Chức năng thêm mới một QListWidgetItem
  3. Chức năng Cập nhật một Item
  4. Chức năng Chèn một Item vào một vị trí bất kỳ trong QListWidget
  5. Chức năng Xóa một Item ra khỏi QListWidget
  6. Chức năng xóa toàn bộ Item trong QListWidget
  7. Chức năng lựa chọn một Item bất kỳ trên QListWidget thì dữ liệu sẽ hiển thị lên Title của cửa sổ
  8. Chức năng khi Double click vào một Item bất kỳ trong QListWidget thì sẽ hiển thị màn hình cập nhật dữ liệu cho Item này.

Các bạn có thể viết code ra giao diện hoặc sử dụng Qt Designer để thiết kế. Nói chung cách nào đi nữa thì nó cũng là sử dụng thư viện PyQt6. Trước khi làm chi tiết các chức năng trong bài minh họa ở trên, thì các bạn cần phải nắm rõ và thực hiện được các bước cơ bản được giải thích dưới đây:

Bước 1: Tạo đối tượng QListWidget

self.list_widget = QListWidget(self)

Bước 2: Gọi các hàm addItem, addItems để thêm item vào QListWidget

self.list_widget.addItems(["Learn Python","Machine Learning","Deep Learning"])
self.list_widget.addItem("Smart Contract")

Ở trên là các mã lệnh để đưa danh sách dữ liệu vào QListWidget thông qua hàm addItems, và đưa một item vào QListWidget thông qua hàm addItem. Các dữ liệu trong trường hợp này là chuỗi.

Ta cũng có thể tạo đối tượng QListWidgetItem để đưa từng Item vào, tuy nhiên trường hợp này ta có thể cấu hình Icon, màu nền, màu chữ… cho từng Item:

item=QListWidgetItem()
item.setText("Metaverse")
item.setIcon(QIcon("images/ic_metaverse.png"))
item.setForeground(Qt.GlobalColor.red)
item.setBackground(Qt.GlobalColor.yellow)
self.list_widget.addItem(item)

Bước 3: Để chèn item vào QListWidget ta dùng hàm insertItem. Hàm này có 2 đối số, đối số 1 là vị trí muốn chèn, đối số 2 là giá trị muốn chèn. Ví dụ dưới đây sẽ chèn item vào vị trí đằng sau dòng mà người dùng đang chọn:

item="Value or QListWidgetItem"
current_row = self.list_widget.currentRow()
self.list_widget.insertItem(current_row+1, item)

Bước 4: Để chỉnh sửa Item trong QListWidget ta làm như sau:

updatedItem=self.list_widget.item(0)
updatedItem.setText("New value for item at row 0")

Bước 5: Để xóa item đang chọn ra khỏi QListWidget ta gọi lệnh takeItem

current_row = self.list_widget.currentRow()
if current_row >= 0:
    current_item = self.list_widget.takeItem(current_row)
    del current_item

Bước 6: Để xóa toàn bộ item ra khỏi QListWidget ta gọi lệnh clear():

self.list_widget.clear()

Dưới đây là mã lệnh minh họa đầy đủ các chức năng của QListWidget:

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QInputDialog, QApplication, QWidget, QGridLayout, QListWidget, QPushButton, QLabel, \
    QListWidgetItem
from PyQt6.QtGui import QIcon

class MainWindow(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setWindowTitle('QListWidget Demo')
        self.setWindowIcon(QIcon('images/ic_logo.jpg'))
        self.setGeometry(100, 100, 400, 100)

        layout = QGridLayout(self)
        self.setLayout(layout)

        self.list_widget = QListWidget(self)
        #Create a QListWidgetItem and set the attributes
        newItem = QListWidgetItem()
        newItem.setText("Metaverse")
        newItem.setIcon(QIcon('images/ic_metaverse.png'))
        newItem.setForeground(Qt.GlobalColor.red)
        newItem.setBackground(Qt.GlobalColor.yellow)
        #add new QListWidgetItem
        self.list_widget.addItem(newItem)

        #add a text for a new QlistWidgetItem
        self.list_widget.addItem("Smart Contract")
        #get item at row 1 and set icon for this Item
        self.list_widget.item(1).setIcon(QIcon('images/ic_smartcontract.png'))
        #add an array for QListWidget
        self.list_widget.addItems(["Learn Python", "Machine Learning", "Deep Learning"])

        layout.addWidget(self.list_widget, 0, 0, 5, 1)
        # create buttons and do signals and slots for QPushButton action:
        add_button = QPushButton('Add New Item')
        add_button.clicked.connect(self.addItem)

        update_button = QPushButton('Update Item')
        update_button.clicked.connect(self.updateItem)

        insert_button = QPushButton('Insert New Item')
        insert_button.clicked.connect(self.insertItem)

        remove_button = QPushButton('Remove Selected Item')
        remove_button.clicked.connect(self.removeItem)

        clear_button = QPushButton('Clear All')
        clear_button.clicked.connect(self.clearAll)

        layout.addWidget(add_button, 0, 1)
        layout.addWidget(update_button, 1, 1)
        layout.addWidget(insert_button, 2, 1)
        layout.addWidget(remove_button, 3, 1)
        layout.addWidget(clear_button, 4, 1)
        #mouse and key signal for QListWidget
        self.list_widget.itemClicked.connect(self.processItemClicked)
        self.list_widget.itemDoubleClicked.connect(self.processItemDoubleClicked)
        #Signal listener user selected the item
        self.list_widget.itemSelectionChanged.connect(self.processItemSelectionChanged)
        # show the window
        self.show()
    #slot to show text of selected item into the title of Window
    def processItemSelectionChanged(self):
        current_row = self.list_widget.currentRow()
        item = self.list_widget.item(current_row)
        self.setWindowTitle(item.text())
    #slot show the update ui:
    def processItemDoubleClicked(self):
        self.updateItem()
    #slot for clicking the item
    def processItemClicked(self):
        current_row = self.list_widget.currentRow()
        data = self.list_widget.item(current_row)
        print("itemClicked=",data.text())
    #slot to show Adding new Item for QListWidget, using QInputDialog
    def addItem(self):
        text, ok = QInputDialog.getText(self, 'Add a New Data', 'New Data:')
        if ok and text:
            self.list_widget.addItem(text)

    # slot to show Updating selected Item for QListWidget, using QInputDialog
    def updateItem(self):
        current_row = self.list_widget.currentRow()
        if current_row >= 0:
            item = self.list_widget.item(current_row)
            text, ok = QInputDialog.getText(self, 'Update Data', 'New Data:',text=item.text())
            if ok and text:
                item.setText(text)

    # slot to show Inserting a new Item for QListWidget, using QInputDialog
    def insertItem(self):
        text, ok = QInputDialog.getText(self, 'Insert a New Data', 'New Data:')
        if ok and text:
            current_row = self.list_widget.currentRow()
            self.list_widget.insertItem(current_row+1, text)
    #slot to remove selected item
    def removeItem(self):
        current_row = self.list_widget.currentRow()
        if current_row >= 0:
            current_item = self.list_widget.takeItem(current_row)
            del current_item
    #slot to remove all item from QListWidget
    def clearAll(self):
        self.list_widget.clear()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())

Đoạn lệnh từ 20 tới 27 là tạo một đối tượng QListWidgetItem và thiết lập các thuộc tính như text, icon, màu nền, màu chữ:

#Create a QListWidgetItem and set the attributes
newItem = QListWidgetItem()
newItem.setText("Metaverse")
newItem.setIcon(QIcon('images/ic_metaverse.png'))
newItem.setForeground(Qt.GlobalColor.red)
newItem.setBackground(Qt.GlobalColor.yellow)
#add new QListWidgetItem
self.list_widget.addItem(newItem)

Đoạn lệnh từ 29 tới 32 là thêm một Item vào QListWidget, đồng thời sau khi thêm chương trình sẽ truy suất item có rowindex là 1 để chỉnh sửa bằng cách thay đổi icon:

#add a text for a new QlistWidgetItem
self.list_widget.addItem("Smart Contract")
#get item at row 1 and set icon for this Item
self.list_widget.item(1).setIcon(QIcon('images/ic_smartcontract.png'))

Đoạn lệnh 33 tới 34 để thêm danh sách các item dạng text vào QListWidget:

#add an array for QListWidget
self.list_widget.addItems(["Learn Python", "Machine Learning", "Deep Learning"])

Những mã lệnh từ dòng 20 tới 34 sẽ giúp ta hiển thị danh sách dữ liệu vào QListWidget như hình dưới đây:

Chức năng “Add New Item”:

add_button.clicked.connect(self.addItem)
#slot to show Adding new Item for QListWidget, using QInputDialog
def addItem(self):
    text, ok = QInputDialog.getText(self, 'Add a New Data', 'New Data:')
    if ok and text:
        self.list_widget.addItem(text)

Khi nhấn vào nút “Add New Item”, chương trình sẽ mở cửa sổ nhập liệu bằng QInputDialog như hình dưới đây:

Trong ô nhập liệu “New Data” nhập liệu rồi nhấn nút OK, kết quả sẽ thấy “PyQt6 & Qt Designer” được đưa vào cuối danh sách:

Chức năng “Update Item”:

update_button.clicked.connect(self.updateItem)
# slot to show Updating selected Item for QListWidget, using QInputDialog
def updateItem(self):
    current_row = self.list_widget.currentRow()
    if current_row >= 0:
        item = self.list_widget.item(current_row)
        text, ok = QInputDialog.getText(self, 'Update Data', 'New Data:',text=item.text())
        if ok and text:
            item.setText(text)

Khi người sử dụng nhấn vào “Update Item”: Đầu tiên chương trình sẽ kiểm tra xem người sử dụng đang chọn Item nào trong QListWidget, sau khi kiểm tra có thấy item được lựa chọn (current_row>=0) thì lúc này chương trình sẽ hiển thị giá trị đang chọn này vào QInputDialog và yêu cầu người dùng nhập lại. Ví dụ như người dùng chọn “Machine Learning” rồi nhấn nút “Update Item”:

Nếu người dùng thay đổi giá trị khác và nhấn OK thì lúc này dữ liệu trên giao diện sẽ thay đổi. Ví dụ như người dùng thay “Machine Learning” thành “Basic Machine Learning”:

Tương tự chức năng “Insert New Item“, chương trình sẽ chèn dữ liệu ngay đằng sau dòng đang chọn:

insert_button.clicked.connect(self.insertItem)
# slot to show Inserting a new Item for QListWidget, using QInputDialog
def insertItem(self):
    text, ok = QInputDialog.getText(self, 'Insert a New Data', 'New Data:')
    if ok and text:
        current_row = self.list_widget.currentRow()
        self.list_widget.insertItem(current_row+1, text)

Trước tiên bạn chọn một dòng dữ liệu bất kỳ trong QListWidget, ví dụ như nhấn vào dòng “Deep Learning”, rồi chọn nút “Insert New Item”:

Nhập dữ liệu, ví dụ “Flask API” sau đó nhấn nút OK, chương trình sẽ thêm dòng dữ liệu “Flask API” ở ngay bên dưới “Deep Learning”:

Tiếp theo tới chức năng “Remove Selected Item” để xóa một dòng dữ liệu đang chọn ra khỏi QListWidget.

remove_button.clicked.connect(self.removeItem)
#slot to remove selected item
def removeItem(self):
    current_row = self.list_widget.currentRow()
    if current_row >= 0:
        current_item = self.list_widget.takeItem(current_row)
        del current_item

Mã lệnh ở trên sẽ xóa dòng dữ liệu đang chọn ra khỏi QListWidget. Tuy nhiên thông thường thao tác xóa ta nên hiển thị một cửa sổ xác nhận xem người dùng có thực sự muốn xóa hay không, để làm được điều này ta sử dụng QMessageBox như mã lệnh dưới đây:

#slot to remove selected item
def removeItem(self):
    current_row = self.list_widget.currentRow()
    if current_row >= 0:
        item=self.list_widget.item(current_row)
        msg=QMessageBox()
        msg.setText(f"Are you sure you want to remove {item.text()}?")
        msg.setWindowTitle("Removing Confirmation")
        msg.setIcon(QMessageBox.Icon.Question)
        buttons=QMessageBox.StandardButton.Yes|QMessageBox.StandardButton.No
        msg.setStandardButtons(buttons)
        result= msg.exec()
        if result==QMessageBox.StandardButton.Yes:
            current_item = self.list_widget.takeItem(current_row)
            del current_item

Sau khi bổ sung mã lệnh xác nhận xóa hay không thì chương trình sẽ xuất hiện cửa sổ như dưới đây:

Tiếp theo là chức năng “Clear All” để xóa toàn bộ item nằm bên trong QListWidget:

clear_button.clicked.connect(self.clearAll)
#slot to remove all item from QListWidget
def clearAll(self):
    self.list_widget.clear()

Chạy phần mềm lên, nếu bạn nhấnt nút “Clear All” thì toàn bộ dữ liệu trong QListWidget sẽ bị xóa toàn bộ. Tuy nhiên cũng tương tự như chức năng “Remove Selected Item” bạn nên bổ sung thêm cửa sổ xác nhận có muốn xóa toàn bộ hay không. Coding minh họa dưới này Tui làm theo cách mới để xác nhận:

#slot to remove all item from QListWidget
def clearAll(self):
    answer = QMessageBox.question(
        self,
        'Confirmation',
        'Do you want to clear all Data?',
        QMessageBox.StandardButton.Yes |
        QMessageBox.StandardButton.No
    )
    if answer == QMessageBox.StandardButton.Yes:
        self.list_widget.clear()

Khi chạy phần mềm lên, nhấn nút “Clear All” ta có màn hình xác nhận:

Chức năng tiếp theo trong bài này đó là lắng nghe người sử dụng đang chọn dòng nào, dòng nào được chọn thì dữ liệu sẽ được hiển thị lên thanh tiêu đề của cửa sổ. Ở đây các bạn có thể dùng các signal itemClicked hoặc itemSelectionChanged cho QListWidget. Điểm khác nhau của itemClicked với với ItemSelectionChanged là itemClicked chỉ lắng nghe lúc người dùng nhấn chuột vào từng item. Còn itemSelectionChanged có thể lắng nghe kể cả người sử dụng dùng chuột hay dùng phím mũi tên lên xuống:

self.list_widget.itemClicked.connect(self.processItemClicked)
self.list_widget.itemSelectionChanged.connect(self.processItemSelectionChanged)
#slot to show text of selected item into the title of Window
def processItemSelectionChanged(self):
    current_row = self.list_widget.currentRow()
    item = self.list_widget.item(current_row)
    self.setWindowTitle(item.text())
#slot for clicking the item
def processItemClicked(self):
    current_row = self.list_widget.currentRow()
    data = self.list_widget.item(current_row)
    print("itemClicked=",data.text())

Cuối cùng đó là chức năng số 8, chức năng khi người dùng Double click (nhấn chuột 2 lần liên tục vào QListWidget) thì sẽ thực hiện chức năng cập nhật dữ liệu (nó chính là gọi “Update Item”):

self.list_widget.itemDoubleClicked.connect(self.processItemDoubleClicked)
#slot show the update ui:
def processItemDoubleClicked(self):
    self.updateItem()

Dưới đây là mã lệnh đầy đủ của “QListWidgetDemo.py” bao gồm bổ sung các cửa sổ xác nhận:

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QInputDialog, QApplication, QWidget, QGridLayout, QListWidget, QPushButton, QLabel, \
    QListWidgetItem, QMessageBox
from PyQt6.QtGui import QIcon

class MainWindow(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setWindowTitle('QListWidget Demo')
        self.setWindowIcon(QIcon('images/ic_logo.jpg'))
        self.setGeometry(100, 100, 400, 100)

        layout = QGridLayout(self)
        self.setLayout(layout)

        self.list_widget = QListWidget(self)
        #Create a QListWidgetItem and set the attributes
        newItem = QListWidgetItem()
        newItem.setText("Metaverse")
        newItem.setIcon(QIcon('images/ic_metaverse.png'))
        newItem.setForeground(Qt.GlobalColor.red)
        newItem.setBackground(Qt.GlobalColor.yellow)
        newItem.setCheckState(Qt.CheckState.Checked)
        #add new QListWidgetItem
        self.list_widget.addItem(newItem)

        #add a text for a new QlistWidgetItem
        self.list_widget.addItem("Smart Contract")
        #get item at row 1 and set icon for this Item
        self.list_widget.item(1).setIcon(QIcon('images/ic_smartcontract.png'))
        #add an array for QListWidget
        self.list_widget.addItems(["Learn Python", "Machine Learning", "Deep Learning"])

        layout.addWidget(self.list_widget, 0, 0, 5, 1)
        # create buttons and do signals and slots for QPushButton action:
        add_button = QPushButton('Add New Item')
        add_button.clicked.connect(self.addItem)

        update_button = QPushButton('Update Item')
        update_button.clicked.connect(self.updateItem)

        insert_button = QPushButton('Insert New Item')
        insert_button.clicked.connect(self.insertItem)

        remove_button = QPushButton('Remove Selected Item')
        remove_button.clicked.connect(self.removeItem)

        clear_button = QPushButton('Clear All')
        clear_button.clicked.connect(self.clearAll)

        layout.addWidget(add_button, 0, 1)
        layout.addWidget(update_button, 1, 1)
        layout.addWidget(insert_button, 2, 1)
        layout.addWidget(remove_button, 3, 1)
        layout.addWidget(clear_button, 4, 1)
        #mouse and key signal for QListWidget
        self.list_widget.itemClicked.connect(self.processItemClicked)
        self.list_widget.itemDoubleClicked.connect(self.processItemDoubleClicked)
        #Signal listener user selected the item
        self.list_widget.itemSelectionChanged.connect(self.processItemSelectionChanged)
        # show the window
        self.show()
    #slot to show text of selected item into the title of Window
    def processItemSelectionChanged(self):
        current_row = self.list_widget.currentRow()
        item = self.list_widget.item(current_row)
        self.setWindowTitle(item.text())
    #slot show the update ui:
    def processItemDoubleClicked(self):
        self.updateItem()
    #slot for clicking the item
    def processItemClicked(self):
        current_row = self.list_widget.currentRow()
        data = self.list_widget.item(current_row)
        print("itemClicked=",data.text())
    #slot to show Adding new Item for QListWidget, using QInputDialog
    def addItem(self):
        text, ok = QInputDialog.getText(self, 'Add a New Data', 'New Data:')
        if ok and text:
            self.list_widget.addItem(text)

    # slot to show Updating selected Item for QListWidget, using QInputDialog
    def updateItem(self):
        current_row = self.list_widget.currentRow()
        if current_row >= 0:
            item = self.list_widget.item(current_row)
            text, ok = QInputDialog.getText(self, 'Update Data', 'New Data:',text=item.text())
            if ok and text:
                item.setText(text)

    # slot to show Inserting a new Item for QListWidget, using QInputDialog
    def insertItem(self):
        text, ok = QInputDialog.getText(self, 'Insert a New Data', 'New Data:')
        if ok and text:
            current_row = self.list_widget.currentRow()
            self.list_widget.insertItem(current_row+1, text)
    #slot to remove selected item
    def removeItem(self):
        current_row = self.list_widget.currentRow()
        if current_row >= 0:
            item=self.list_widget.item(current_row)
            msg=QMessageBox()
            msg.setText(f"Are you sure you want to remove {item.text()}?")
            msg.setWindowTitle("Removing Confirmation")
            msg.setIcon(QMessageBox.Icon.Question)
            buttons=QMessageBox.StandardButton.Yes|QMessageBox.StandardButton.No
            msg.setStandardButtons(buttons)
            result= msg.exec()
            if result==QMessageBox.StandardButton.Yes:
                current_item = self.list_widget.takeItem(current_row)
                del current_item
    #slot to remove all item from QListWidget
    def clearAll(self):
        answer = QMessageBox.question(
            self,
            'Confirmation',
            'Do you want to clear all Data?',
            QMessageBox.StandardButton.Yes |
            QMessageBox.StandardButton.No
        )
        if answer == QMessageBox.StandardButton.Yes:
            self.list_widget.clear()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())

Như vậy là tới đây Tui đã hướng dẫn xong QListWidget, các bạn đã nắm rất rõ các kiến thức liên quan tới widget này, biết được ý nghĩa chức năng của từng thuộc tính, phương thức và signal của QListWidget. Các bạn đã ứng dụng được các đặc tính này trong ví dụ minh họa với 8 chức năng chính. Từ đây các bạn có thể dễ dàng áp dụng QListWidget ở các dự án cụ thể đáp ứng nhu cầu lưu trữ và hiển thị dữ liệu khác nhau của khách hàng.

Mà lệnh đầy đủ và các hình ảnh sử dụng trong dự án này các bạn có thể tải tại đây:

https://www.mediafire.com/file/nuxagzk5uer9sva/LearnQListWidget.rar/file

Bài học tiếp theo Tui sẽ tiếp tục trình bày ứng dụng của QListWidget trong việc quản lý dữ liệu Employee. Các bạn chú ý theo dõi

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

Bài 16: QCombobox – Basic Widgets – PyQt6

Nếu bạn muốn hiển thị dữ liệu dạng danh sách và cho người dùng lựa chọn một phần tử trong danh sách này thì ta dùng QComboBox. Ví dụ như bạn muốn hiển thị danh sách Tỉnh Thành, hiển thị danh sách Danh mục Sản phẩm, hiển thị Danh sách Phòng ban…. thì QComboBox là một trong các lựa chọn khá phù hợp.

Một số thuộc tính, signal, phương thức thường dùng của QComboBox:

Thuộc tính/Phương thức/signalÝ nghĩa/ Chức năng
QCombobBox(self)Constructor để tạo đối tượng QComboBox
addItem(text, userData)Hàm thêm một phần tử vào cuối QComboBox. userData là đối tượng tương ứng lúc thêm phần tử, nó có thể None
insertItem(index, text, userData)Hàm chèn một phần tử vào vị trí index trong QComboBox. userData là đối tượng tương ứng lúc chèn phần tử, nó có thể None
addItems(texts)Hàm thêm nhiều phần tử vào QComboBox
insertItems(index,texts)Hàm chèn nhiều phần tử vào QComboBox bắt đầu từ vị trí index
currentData()Hàm trả về đối tượng tại phần tử đang chọn trên QComboBox
currentIndex()Hàm trả về vị trí hiện tại phần tử đang chọn trên QComboBox
currentText()Hàm trả về text hiện tại phần tử đang chọn trên QComboBox
setEditable(True/False)Hàm thiết lập cho phép nhập liệu trong QComboBox hay không
activatedsignal để xử lý lấy dữ liệu mà người dùng đang chọn phần tử trong QComboBox.

Dưới đây là các bước cơ bản để tạo và sử dụng QCombobox với dữ liệu cơ bản (các ví dụ sau ta sẽ sử dụng dữ liệu dạng đối tượng), tạo dự án “LearnQComboBox” trong Pycharm, sau đó tạo tập tin “QComboBoxBasicDemo.py” viết mã lệnh trong tập tin này:

Bước 1: Gọi thư viện để sử dụng QComboBox

from PyQt6.QtWidgets import QComboBox

Bước 2: Tạo đối tượng QComboBox

self.cboCategory = QComboBox(self)

Bước 3: Thêm các dữ liệu vào QComboBox bằng addItem hoặc insertItem

self.cboCategory.addItem('Laptop')
self.cboCategory.addItem('Phone & Tablet')
self.cboCategory.addItem('Smart Watch')
self.cboCategory.insertItem(1,"Head Phone")

Chúng ta cũng có thể chèn hoặc thêm nhiều dữ liệu cùng một lúc vào QComboBox:

self.cboCategory.insertItems(2,["Mouse","Mouse Pad"])
self.cboCategory.addItems(["Game & Stream","Monitor"])

Bước 4: Xử lý signal khi người dùng nhấn chọn phần tử trên QComboBox

self.cboCategory.activated.connect(self.processSelectedComboBox)

Slot processSelectedComboBox để lấy index và text của phần tử đang chọn trên QComboBox:

def processSelectedComboBox(self):
    index=self.cboCategory.currentIndex()
    text=self.cboCategory.currentText()
    self.result_label.setText(
        f'You selected index= {index}, text={text}')

Dưới đây là mã lệnh đầy đủ minh họa sử dụng và xử lý sự kiện cho QComboBox mức cơ bản:

import sys
from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout, QComboBox

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle('Tran Duy Thanh')
        self.setMinimumWidth(300)

        # create a grid layout
        layout = QVBoxLayout()
        self.setLayout(layout)

        cb_label = QLabel('Please select a Category:', self)

        # create a combobox
        self.cboCategory = QComboBox(self)
        self.cboCategory.addItem('Laptop')
        self.cboCategory.addItem('Phone & Tablet')
        self.cboCategory.addItem('Smart Watch')
        self.cboCategory.insertItem(1,"Head Phone")

        self.cboCategory.insertItems(2,["Mouse","Mouse Pad"])
        self.cboCategory.addItems(["Game & Stream","Monitor"])

        self.cboCategory.activated.connect(self.processSelectedComboBox)

        self.result_label = QLabel('', self)

        layout.addWidget(cb_label)
        layout.addWidget(self.cboCategory)
        layout.addWidget(self.result_label)

        self.show()

    def processSelectedComboBox(self):
        index=self.cboCategory.currentIndex()
        text=self.cboCategory.currentText()
        self.result_label.setText(
            f'You selected index= {index}, text={text}')
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())

Chạy phần mềm lên ta có giao diện, chọn một item bất kỳ:

Chọn Item bất kỳ ta có kết quả:

Bây giờ Tui sẽ nâng cấp bài trên bằng cách bổ sung các lớp mô hình lớp hướng đối tượng để lưu trữ, hiển thị và xử lý thao tác người dùng tốt hơn trong trường hợp dữ liệu có nhiều thông tin khác nhau.

Bổ sung vào dự án “LearnQComboBox” có cấu trúc như dưới đây:

  • Thư mục “images” để lưu trữ một số hình ảnh cho các danh mục sản phẩm
  • Category.py là lớp đối tượng Danh Mục có id và name
  • QComboBoxAdvancedDemo.py để tạo giao diện sử dụng QComboBox của dữ liệu là hướng đối tượng Category và các icon tương ứng.

Lớp Category.py có mã lệnh như dưới đây:

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

Lớp Category có 2 thuộc tính là id và name. Ta cung cấp 2 hàm, hàm thứ nhất là contructor nhận vào 2 đối số id, name. Hàm thứ 2 là __str__() hàm này tương đương với các hàm toString() của ngôn ngữ Java, C#… nó sẽ tự động được triệu gọi khi ta hiển thị đối tượng lên giao diện. Do đó, muốn dữ liệu hiển thị như thế nào thì ta có thể hiệu trong trong hàm __str__()

Lớp “QComboBoxAdvancedDemo.py” có sự khác biệt so với BasicDemo là ta thêm Icon và các Đối tượng cho QComboBox:

self.cboCategory = QComboBox(self)

laptop_icon=QIcon("images/laptop.png")
laptop_model=Category(100,"Laptop")
self.cboCategory.addItem(laptop_icon,laptop_model.name,laptop_model)

phone_icon = QIcon("images/phone.png")
phone_model = Category(200, "Phone")
self.cboCategory.addItem(phone_icon, phone_model.name, phone_model)

smart_icon = QIcon("images/smartwatch.png")
smart_model = Category(300, "Smart Watch")
self.cboCategory.addItem(smart_icon, smart_model.name, smart_model)

Mã lệnh ở trên khi đưa dữ liệu lên QComboBox sẽ gồm 3 dữ liệu: Icon, text hiển thị, và đối tượng tương ứng. Đối tượng tương ứng này sẽ rất hiểu quả trong quá trình xử lý dữ liệu được lựa chọn trên giao diện.

Để xử lý sự kiện, ta cũng gán signal như bình thường, ở đây ta có thể lấy currentData() để truy suất tới đối tượng lúc đưa vào QComboBox, lấy currentText(), lấy currentIndex(). Trong bài này Tui minh họa cách lấy currentData() còn các hàm khác làm tương tự như ví dụ trước đó:

self.cboCategory.activated.connect(self.processSelectedComboBox)
def processSelectedComboBox(self):
    data=self.cboCategory.currentData()
    self.result_label.setText(
        f'You selected index= {data}')

Khi data được hiển thị lên màn hình thì hàm __str__() sẽ tự động được triệu gọi.

Dưới đây là mã lệnh đầy đủ của “QComboBoxAdvancedDemo.py”:

import sys

from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout, QComboBox

from Category import Category


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle('Tran Duy Thanh')
        self.setMinimumWidth(300)

        # create a grid layout
        layout = QVBoxLayout()
        self.setLayout(layout)

        cb_label = QLabel('Please select a Category:', self)

        # create a combobox
        self.cboCategory = QComboBox(self)

        laptop_icon=QIcon("images/laptop.png")
        laptop_model=Category(100,"Laptop")
        self.cboCategory.addItem(laptop_icon,laptop_model.name,laptop_model)

        phone_icon = QIcon("images/phone.png")
        phone_model = Category(200, "Phone")
        self.cboCategory.addItem(phone_icon, phone_model.name, phone_model)

        smart_icon = QIcon("images/smartwatch.png")
        smart_model = Category(300, "Smart Watch")
        self.cboCategory.addItem(smart_icon, smart_model.name, smart_model)


        self.cboCategory.activated.connect(self.processSelectedComboBox)

        self.result_label = QLabel('', self)

        layout.addWidget(cb_label)
        layout.addWidget(self.cboCategory)
        layout.addWidget(self.result_label)

        self.show()

def processSelectedComboBox(self):
    data=self.cboCategory.currentData()
    self.result_label.setText(
        f'You selected index= {data}')
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec())

Chạy phần mềm lên ta có kết quả:

Mã lệnh đầy đủ của 2 ví dụ về QComboBox này các bạn tải ở đây:

https://www.mediafire.com/file/8hjvyv8lncuyhae/LearnQComboBox.rar/file

Bây giờ Tui hướng dẫn các bạn ứng dụng QComboBox vào việc xây dựng phần mềm như dưới đây:

Tạo một dự án tên “LearnQComboBoxAdvanced” trong Pycharm:

Trong dự án ở trên, tạo thư mục “images”, bổ sung thêm các biểu tượng liên quan tới các thành phố.

Sau đó dùng Qt Designer để thiết kế giao diện như dưới đây (MainWindow.ui):

Các widget kéo ra như trên, định dạng và đặt tên cho các Widget này như hình:

ComboBox City ta kéo vào, và nhập dữ liệu cho widget này như sau (double click vào widget sau khi kéo vào giao diện):

  • Nhấn biểu tượng dấu “+” để nhập thêm item cho QComboBox
  • Nhấn biểu tượng dấu “-” để xóa item đang chọn ra khỏi QComboBox
  • Các biểu tượng mũi tên lên, xuống để thay đổi vị trí của các item trong QComboBox
  • Nút lệnh “Properties” để hiển thị chi tiết các thuộc tính của từng item trong QComboBox. Bạn muốn thêm Icon cho item thì bấm chọn Choose File… trong nhóm icon như hình trên.

Sau khi chọn đầy đủ các hình cho Item ta có kết quả như hình bên dưới:

Sau khi thiết kế xong giao diện, ta lưu lại với tên “MainWindow.ui” rồi qua lại Pycharm để Generate code Python thành “MainWindow.py”. Sau đó tiến hành bổ sung thêm 1 class tên là “Employee” có các thông số sau:

class Employee:
    def __init__(self,fullName,gender,city):
        self.fullName=fullName
        self.gender=gender
        self.city=city

Tiếp theo tạo “MainWindowEx.py” kế thừa từ lớp được Generate trong “MainWindow.py”:

from MainWindow import Ui_MainWindow

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
    def show(self):
        self.MainWindow.show()

Các mã lệnh chi tiết xử lý sự kiện ta sẽ bổ sung sau cho MainWindowEx.

Tiếp theo ta tạo “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()

Ta xem cấu trúc dữ liệu cuối cùng trong Pycharm:

Chạy “MyApp.py” ta có kết quả:

Như vậy ta có được giao diện như mong muốn.

Bây giờ ta bổ sung thêm hàm __str__ để trả về chuỗi json:

import json

class Employee:
    def __init__(self,fullName,gender,city):
        self.fullName=fullName
        self.gender=gender
        self.city=city
    def __str__(self):
        return json.dumps(self.__dict__)

Hàm __str__ sẽ được sử dụng khi ta hiển thị đối tượng lên giao diện

Sau đó ta tiếp tục bổ sung mã lệnh cho MainWindow.py để xử lý sự kiện:

from Employee import Employee
from MainWindow import Ui_MainWindow

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.pushButtonClose.clicked.connect(self.processClose)
        self.pushButtonConfirm.clicked.connect(self.processConfirmation)
    def processConfirmation(self):
        name=self.lineEditfullName.text()
        gender="Man"
        if self.chkGender.isChecked():
            gender="Woman"
        city=self.cboCity.currentText()
        emp=Employee(name,gender,city)
        self.plainTextEditOutput.setPlainText(str(emp))
    def processClose(self):
        self.MainWindow.close()
    def show(self):
        self.MainWindow.show()

Mã lệnh slot “processConfirmation” ở trên sẽ lắng nghe và xử lý lấy các dữ liệu từ giao diện mà người dùng cung cấp.

Dòng lệnh 18 sẽ khởi tạo 1 đối tượng Employee

Dòng lệnh 19 dùng str(emp) để chương trình tự động triệu gọi __str__ để lấy chuỗi JSON của đối tượng và sau đó hiển thị lên giao diện.

Khi nhấn nút “Confirm” sẽ hiển thị dữ liệu JSON vào màn hình OutPut, nhấn “Close” sẽ đóng cửa sổ:

Như vậy tới đây Tui đã trình bày xong các kiến thức liên quan tới QComboBox cũng như các kỹ thuật lập trình, ứng dụng QComboBox và các dự án mẫu. Thông qua 3 ví dụ ở trên các bạn đã có đủ kiến thức và kỹ năng để sử dụng và phát triển mở rộng QComboBox ở các trường hợp khác nhau. Các bạn cố gắng thực hành lại nhiều lần để hiểu rõ hơn về Widget này.

Mã lệnh của bài này các bạn tải ở đây:

https://www.mediafire.com/file/evpr0v8xdx82el2/LearnQComboBoxAdvanced.rar/file

Bài học sau, Tui sẽ trình bày về wiget QListWidget, nó cũng là một dạng widget dùng để hiển thị dữ liệu dạng danh sách nhưng nó có một chút ít khác biết so với QComboBox.

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

Bài 15: QRadioButton – Basic Widgets – PyQt6

Trong bài trước chúng ta học về QCheckBox – widget dùng để cung cấp chức năng cho người sử dùng chọn nhiều lựa chọn trên giao diện. Trong bài học này Tui sẽ trình bày về QRadioButton, nó khác biệt với QCheckBox, đó là nó dùng để thiết kế giao diện cho phép người dùng tại một thời điểm chỉ chọn một lựa chọn.

QRadioButton cung cấp 2 trạng thái:

  • on(checked)
  • off(unchecked)

Và các QRadioButton cần được nhóm lại thông qua QButtonGroup nếu trên giao diện bạn muốn thiết kế nhiều nhóm, hoặc chúng ta kéo các Widget vào bên trong các QGroupBox khác nhau. Ví dụ như bạn muốn thiết kế 2 nhóm chọn lựa: Giao diện vừa có nhóm Chọn giới tính Nam hay Nữ, vừa có nhóm chọn trình độ học vấn như Đại học hay cao đẳng….

Tương tự như QCheckBox, thì QRadioButton cũng có một số thuộc tính, phương thức, signal thường dùng sau:

Thuộc tính/Phương thức/signalÝ nghĩa/ Chức năng
objectNameTên QRadioButton dùng để truy suất
setChecked(True/False)Hàm thiết lập Checked hoặc Unchecked cho QRadioButton
isChecked()Hàm trả về trạng thái của QRadioButton có được checked hay không
text()Hàm trả về chuỗi hiển thị trên QRadioButton
toggledSignal để kiểm tra trạng thái của QRadioButton có được checked hay không
clickedSignal để lấy trạng thái true khi người dùng click chọn QRadioButton

Để sử dụng QRadioButton ta có thể thiết kế bằng Qt Designer hoặc làm tuần tự các bước sau, dưới này Tui minh họa bài thay đổi màu nền của Cửa sổ khi người sử dụng nhấn vào các QRadioButton tương ứng:

Bước 1: Import QRadioButton từ module PyQt6.QtWidgets và các thư viện liên quan khác:

from PyQt6.QtWidgets import QApplication
from PyQt6.QtWidgets import QWidget
from PyQt6.QtWidgets import QRadioButton
from PyQt6.QtWidgets import QButtonGroup

Bước 2: Khai báo các đối tượng Widget, QRadioButton, ButtonGroup để tạo các nhóm:

from PyQt6.QtWidgets import QApplication
from PyQt6.QtWidgets import QWidget
from PyQt6.QtWidgets import QRadioButton
from PyQt6.QtWidgets import QButtonGroup

app = QApplication([])

w = QWidget()
w.setWindowTitle("Trần Duy Thanh - QRadioButton")
w.resize(300, 150)

radRed = QRadioButton("Red",w)
radRed.move(20, 20)

radGreen = QRadioButton("Green",w)
radGreen.move(20, 40)

radBlue = QRadioButton("Blue",w)
radBlue.move(20, 60)

color_group = QButtonGroup(w)
color_group.addButton(radRed)
color_group.addButton(radGreen)
color_group.addButton(radBlue)

w.show()

app.exec()

Chạy mã lệnh ở trên ta được giao diện như mong muốn.

Bước 3: Gán Signal cho 3 QRadioButton để đổi màu nền cửa sổ:

def changeBackgroundToRed(value):
    if value==True:
        w.setStyleSheet("background-color:red;");
def changeBackgroundToGreen(value):
    if value == True:
        w.setStyleSheet("background-color:green;")
def changeBackgroundToBlue(value):
    if value == True:
        w.setStyleSheet("background-color:blue;")
radRed.clicked.connect(changeBackgroundToRed)
radGreen.clicked.connect(changeBackgroundToGreen)
radBlue.clicked.connect(changeBackgroundToBlue)

Dưới đây là mã lệnh đầy đủ cho bài đổi màu nền:

from PyQt6.QtWidgets import QApplication
from PyQt6.QtWidgets import QWidget
from PyQt6.QtWidgets import QRadioButton
from PyQt6.QtWidgets import QButtonGroup

app = QApplication([])

w = QWidget()
w.setWindowTitle("Trần Duy Thanh - QRadioButton")
w.resize(300, 150)

radRed = QRadioButton("Red",w)
radRed.move(20, 20)

radGreen = QRadioButton("Green",w)
radGreen.move(20, 40)

radBlue = QRadioButton("Blue",w)
radBlue.move(20, 60)

color_group = QButtonGroup(w)
color_group.addButton(radRed)
color_group.addButton(radGreen)
color_group.addButton(radBlue)

def changeBackgroundToRed(value):
    if value==True:
        w.setStyleSheet("background-color:red;");
def changeBackgroundToGreen(value):
    if value == True:
        w.setStyleSheet("background-color:green;")
def changeBackgroundToBlue(value):
    if value == True:
        w.setStyleSheet("background-color:blue;")
radRed.clicked.connect(changeBackgroundToRed)
radGreen.clicked.connect(changeBackgroundToGreen)
radBlue.clicked.connect(changeBackgroundToBlue)

w.show()

app.exec()

Chạy phần mềm lên và lựa chọn các Màu ta có:

Tiếp theo ta ứng dụng QRadioButton để thiết kế màn hình nhập Personal Information với nhiều thông tin hơn, và đặc biệt ta dùng Qt Designer, GroupBox để group các QRadioButton trên các nhóm khác nhau:

Trong Pycharm ta tạo một dự án tên “LearnQRadioButton“:


Ta dùng Qt Designer để thiết kế giao diện, đặt tên “MainWindow.ui”:

Trước tiên ta kéo các “Group Box” ra giao diện như hình trên, đặt tiêu tiêu, căn lề tiêu đề cũng như chỉnh styleSheet đổi màu nền cho các Group Box.

Ở trên các bạn thấy Tui có tạo 2 Group Box có nền màu vàng (Personal Information) và Group có nền xanh (Other Information). Sau đó trong mỗi Group Box được kéo vào một số Widget như QLabel, QLineEdit, QRadioButton.

Các Group Box, bạn có thể định dạng như hình dưới đây:

  • Thuộc tính title: Để nhập tiêu đề cho Group Box
  • Thuộc tính alignment: Dùng để căn lề cho tiêu đề Group Box
  • Thuộc tính styleSheet: Dùng để định dạng CSS cho GroupBox ví dụ như đường viên, màu viền, màu nền….

Các QRadioButton nào được kéo vào GroupBox nào thì nó sẽ thuộc nhóm đó, tức là khi ta nhấn chọn Radio Button nào thì trong nhóm đó sẽ ảnh hưởng thôi, không liên quan tới GroupBox khác.

Các QRadioButton cũng có nhiều thuộc tính, trong đó các thuộc tính thường dùng như text, icon, checked:

QRadioButton nào muốn mặc định checked thì bạn tick vào thuộc tính checked như hình.

Cuối cùng bạn đặt tên các Widget như hình dưới đây để hỗ trợ cho việc viết mã lệnh tương tác được tốt nhất:

Sau khi đặt xong tên các Widget, các bạn lưu giao diện lại với file “MainWindow.ui” vào đúng dự án trong Pycharm “LearnQRadioButton

Sau đó dùng chức năng Generate Python code đã học để tạo mã lệnh Python cho “MainWindow.ui”, file mã lệnh tự động tạo ra sẽ là “MainWindow.py“:

Mã lệnh của file này như sau:

# 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(449, 378)
        font = QtGui.QFont()
        font.setPointSize(12)
        MainWindow.setFont(font)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.groupBox = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox.setGeometry(QtCore.QRect(20, 10, 411, 121))
        self.groupBox.setStyleSheet("border-color: rgb(0, 0, 255);\n"
"background-color: rgb(251, 255, 171);\n"
"")
        self.groupBox.setObjectName("groupBox")
        self.radWoman = QtWidgets.QRadioButton(parent=self.groupBox)
        self.radWoman.setGeometry(QtCore.QRect(110, 90, 91, 18))
        self.radWoman.setChecked(True)
        self.radWoman.setObjectName("radWoman")
        self.radMan = QtWidgets.QRadioButton(parent=self.groupBox)
        self.radMan.setGeometry(QtCore.QRect(220, 90, 83, 18))
        self.radMan.setObjectName("radMan")
        self.lineEditFullName = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditFullName.setGeometry(QtCore.QRect(110, 30, 281, 20))
        self.lineEditFullName.setObjectName("lineEditFullName")
        self.label = QtWidgets.QLabel(parent=self.groupBox)
        self.label.setGeometry(QtCore.QRect(20, 30, 81, 21))
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_2.setGeometry(QtCore.QRect(20, 90, 71, 21))
        self.label_2.setObjectName("label_2")
        self.lineEditEmail = QtWidgets.QLineEdit(parent=self.groupBox)
        self.lineEditEmail.setGeometry(QtCore.QRect(110, 60, 281, 20))
        self.lineEditEmail.setObjectName("lineEditEmail")
        self.label_3 = QtWidgets.QLabel(parent=self.groupBox)
        self.label_3.setGeometry(QtCore.QRect(20, 60, 81, 21))
        self.label_3.setObjectName("label_3")
        self.groupBox_2 = QtWidgets.QGroupBox(parent=self.centralwidget)
        self.groupBox_2.setGeometry(QtCore.QRect(20, 150, 411, 131))
        self.groupBox_2.setStyleSheet("background-color: rgb(232, 255, 225);")
        self.groupBox_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter)
        self.groupBox_2.setObjectName("groupBox_2")
        self.radBachelor = QtWidgets.QRadioButton(parent=self.groupBox_2)
        self.radBachelor.setGeometry(QtCore.QRect(110, 60, 91, 18))
        self.radBachelor.setObjectName("radBachelor")
        self.radMaster = QtWidgets.QRadioButton(parent=self.groupBox_2)
        self.radMaster.setGeometry(QtCore.QRect(110, 80, 83, 18))
        self.radMaster.setObjectName("radMaster")
        self.lineEditAddress = QtWidgets.QLineEdit(parent=self.groupBox_2)
        self.lineEditAddress.setGeometry(QtCore.QRect(110, 30, 281, 20))
        self.lineEditAddress.setObjectName("lineEditAddress")
        self.label_4 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_4.setGeometry(QtCore.QRect(20, 30, 81, 21))
        self.label_4.setObjectName("label_4")
        self.label_5 = QtWidgets.QLabel(parent=self.groupBox_2)
        self.label_5.setGeometry(QtCore.QRect(20, 60, 71, 21))
        self.label_5.setObjectName("label_5")
        self.radDoctoral = QtWidgets.QRadioButton(parent=self.groupBox_2)
        self.radDoctoral.setGeometry(QtCore.QRect(110, 100, 83, 18))
        self.radDoctoral.setChecked(True)
        self.radDoctoral.setObjectName("radDoctoral")
        self.pushButtonSendData = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonSendData.setGeometry(QtCore.QRect(150, 290, 121, 31))
        self.pushButtonSendData.setObjectName("pushButtonSendData")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 449, 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"))
        self.groupBox.setTitle(_translate("MainWindow", "Personal Information:"))
        self.radWoman.setText(_translate("MainWindow", "Woman"))
        self.radMan.setText(_translate("MainWindow", "Man"))
        self.lineEditFullName.setText(_translate("MainWindow", "Trần Phạm Thanh Trà"))
        self.label.setText(_translate("MainWindow", "Full Name:"))
        self.label_2.setText(_translate("MainWindow", "Gender:"))
        self.lineEditEmail.setText(_translate("MainWindow", "trachanh@gmail.com"))
        self.label_3.setText(_translate("MainWindow", "Email:"))
        self.groupBox_2.setTitle(_translate("MainWindow", "Other Information:"))
        self.radBachelor.setText(_translate("MainWindow", "Bachelor"))
        self.radMaster.setText(_translate("MainWindow", "Master"))
        self.lineEditAddress.setText(_translate("MainWindow", "The Earth"))
        self.label_4.setText(_translate("MainWindow", "Address:"))
        self.label_5.setText(_translate("MainWindow", "Education:"))
        self.radDoctoral.setText(_translate("MainWindow", "Doctoral"))
        self.pushButtonSendData.setText(_translate("MainWindow", "Send Data"))

Tiếp tục tạo một file “MainWindowEX.py” (lớp) kế thừa từ lớp được Generate Python Code trong “MyWindow.py”, mã lệnh tạm thời của “MainWindowEX.py” bạn gõ như dưới đây:

from MainWindow import Ui_MainWindow

class MainWindowEX(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
    def show(self):
        self.MainWindow.show()

Các mã lệnh liên quan tới signal cho các Widget ta sẽ xử lý sau.

Tiếp theo, ta tạo thêm “MyApp.py” có mã lệnh dưới đây để khởi tạo chạy 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()

Chạy chương trình “MyApp.py” lên ta có kết quả:

Khi bạn chạy được giao diện như hình trên là đã thành công.

Bây giờ ta tiếp tục bổ sung một số mã lệnh cho “MainWindowEx.py” để xử lý các Signal:

  • Khi nhấn nút “Send Data” chương trình sẽ lấy các dữ liệu trên giao diện mà người dùng lựa chọn rồi dùng QMessageBox hiển thị các dữ liệu này lên.
import json

from PyQt6.QtWidgets import QMessageBox

from MainWindow import Ui_MainWindow

class MainWindowEX(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.pushButtonSendData.clicked.connect(self.getInformation)
    def getInformation(self):
        fullName=self.lineEditFullName.text()
        email=self.lineEditEmail.text()
        gender="Woman"
        if not self.radWoman.isChecked():
            gender=self.radMan.text()
        address=self.lineEditAddress.text()
        degree="Bachelor"
        if self.radMaster.isChecked():
            degree=self.radMaster.text()
        elif self.radDoctoral.isChecked():
            degree=self.radDoctoral.text()
        information={"FullName":fullName,
                     "Email":email,
                     "Gender":gender,
                     "Address":address,
                     "Degree":degree
                     }
        self.msgBox=QMessageBox()
        self.msgBox.setWindowTitle("Information")
        self.msgBox.setText(json.dumps(information, ensure_ascii=False))
        self.msgBox.show()
    def show(self):
        self.MainWindow.show()

Mã lệnh ở trên, Tui chủ ý đưa dữ liệu về dạng JSON để các bạn làm quen ở các bài học trong tương lai, vì JSON là một trong các cấu trúc lưu trữ dữ liệu phổ biến nhất hiện nay.

Sau khi bổ sung xong mã lệnh cho “MainWindowEx.py”, bây giờ các bạn chạy lại “MyApp.py”, khi bấm “Send Data” ta sẽ có kết quả:

Như vậy tới đây Tui đã trình bày xong về lý thuyết cũng như cách kỹ thuật lập trình với QRadioButton. Các bạn đã biết được ý nghĩa của các thuộc tính, cách gán signal, và đã sử dụng được 2 cách xử lý sự kiện trên QRadioButton:

  • Xử lý sự kiện QRadioButton ngay lúc người dùng thay đổi lựa chọn trên các widget này
  • Xử lý sự kiện QRadioButton sau khi người dùng nhấn vào nút Send Data

Việc biết được các kiến thức và kỹ thuật xử lý này sẽ giúp ích cho các bạn trong cách vận dụng QRadioButton vào từng trường hợp cụ thể. Các bạn chú ý đọc kỹ và thực hành lại nhiều lần để hiểu rõ hơn về Widget này.

Mã lệnh của bài học QRadioButton các bạn có thể tải ở đây:

https://www.mediafire.com/file/fntsllw6njwapak/LearnQRadioButton.rar/file

Bài học sau Tui sẽ trình bày về cách sử dụng QComboBox QListWidget để hiển thị dữ liệu dạng danh sách, đây là một các Widget rất quan trọng để hỗ trợ việc hiển thị danh sách dữ liệu, cho phép người dùng chọn 1 phần tử hay chọn nhiều phần từ… Các bạn chú ý theo dõi.

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

Bài 14: QCheckBox – Basic Widgets – PyQt6

QCheckBox là một trong các Widget thuộc nhóm cơ bản và nó được dùng thường xuyên trong hầu hết các phần mềm, QCheckBox cung cấp một số trạng thái cho người sử dụng lựa chọn như:

  • Checked
  • Unchecked
  • hoặc Partially checked

Ta thường dùng QCheckBox trong trường hợp cung cấp giao diện cho Khách hàng chọn nhiều lựa chọn. Ví dụ như cung cấp giao diện hỏi về Sở thích (có nhiều sở thích), Cung cấp giao diện cho người dùng chọn nhiều cấu hình, hay chức năng lưu mật khẩu đăng nhập, nói chung là cho nhiều lựa chọn…. Ví dụ:

Một số thuộc tính và phương thức chính thường dùng của QCheckBox:

Thuộc tính/Phương thức/signalÝ nghĩa/ Chức năng
objectNameTên QCheckBox dùng để truy suất
Qt.CheckStateThuộc tính kiểm tra trạng thái của QCheckBox là có checked, unchecked hay partially checked
setChecked(True/False)Hàm thiết lập Checked hoặc Unchecked cho QCheckBox
isChecked()Hàm trả về trạng thái của QCheckBox có được checked hay không
text()Hàm trả về chuỗi hiển thị trên QCheckBox
setTristate(True/False)Thiết lập trạng thái thứ 3 của QCheckBox (Partially checked)
stateChangedSignal để gửi tín hiệu xác nhận người dùng checked hay unchecked QCheckBox

Để sử dụng QCheckBox ta có thể thiết kế bằng Qt Designer hoặc làm tuần tự các bước sau:

Bước 1: Import QCheckBox từ module PyQt6.QtWidgets

from PyQt6.QtWidgets import QCheckBox

Bước 2: Khai báo biến và tạo đối tượng QCheckBox

checkbox = QCheckBox(text)

Bước 3: Lắng nghe thay đổi trạng thái của QCheckBox bằng stateChanged signal

Với checkbox ta cần biết khi nào người sử dụng checked hay unchecked.

checkbox.stateChanged.connect(self.on_checkbox_changed)

stateChanged signal sẽ gửi tín hiệu tới slot on_checkbox_changed (dĩ nhiên ta thích đặt tên slot nào cũng được) để ta biết được trạng thái của checkbox thông qua đối tượng Qt.CheckState:

def on_checkbox_changed(self, value):
    state = Qt.CheckState(value)
    if state == Qt.CheckState.Checked:
        print('You Checked')
    elif state == Qt.CheckState.Unchecked:
        print('You Unchecked')

Qt.CheckState có 3 trạng thái chính như sau:

StateÝ nghĩa
Qt.CheckState.CheckedCheckbox được checked
Qt.CheckState.UncheckedCheckbox được unchecked
Qt.CheckState.PartiallyCheckedHiểu nôm na là Không xác định dạng thái checked hay là unchecked.

Trong bài học này, Tui sẽ minh họa 2 ví dụ về cách sử dụng QCheckBox. Trường hợp thứ nhất là trên giao diện co các QCheckBox, người sử dụng checked/unchecked tùy thích sau đó có QPushButton nhấn để xử lý xem các QCheckBox nào đang được checked. Trường hợp thứ 2 là trên giao diện người dùng lúc checked/unchecked thì ngay lập tức ta xử lý trạng thái của QCheckBox mà không cần thêm một thao tác nhấn QPushButton. Cả 2 trường hợp này ta đều gặp thường xuyên trong quá trình xử lý các trường hợp sử dụng của khách hàng.

Bây giờ ta vào trường hợp 1. Ta sẽ thiết kế và xử lý sự kiện cho bài sau:

Khi người dùng nhập Full Name, Email. Và checked chọn các Khóa học rồi nhấn Submit thì ra cửa sổ thông báo sau:

Ta tạo dự án “LearnCheckbox”, cấu trúc:

Ta dùng Qt Designer để thiết kế giao diện đặt tên file là “MainWindow.ui”:

Thiết kế giao diện và đặt tên các Widget như hình sau đó lưu lại tên “MainWindow.ui” vào dự án Pycharm, sau đó dùng tool để Generate python code thành “MainWindow.py”:

# 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(441, 334)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        self.label.setGeometry(QtCore.QRect(120, 10, 301, 41))
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(True)
        font.setItalic(True)
        font.setWeight(75)
        self.label.setFont(font)
        self.label.setStyleSheet("color: rgb(0, 0, 255);")
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(20, 60, 101, 21))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.label_2.setFont(font)
        self.label_2.setObjectName("label_2")
        self.lineEditFullName = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditFullName.setGeometry(QtCore.QRect(130, 60, 251, 20))
        self.lineEditFullName.setObjectName("lineEditFullName")
        self.lineEditEmail = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditEmail.setGeometry(QtCore.QRect(130, 90, 251, 20))
        self.lineEditEmail.setObjectName("lineEditEmail")
        self.label_3 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_3.setGeometry(QtCore.QRect(20, 90, 81, 21))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.label_3.setFont(font)
        self.label_3.setObjectName("label_3")
        self.label_4 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_4.setGeometry(QtCore.QRect(130, 120, 251, 21))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.label_4.setFont(font)
        self.label_4.setStyleSheet("background-color: rgb(255, 255, 0);")
        self.label_4.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label_4.setObjectName("label_4")
        self.chkMachineLearning = QtWidgets.QCheckBox(parent=self.centralwidget)
        self.chkMachineLearning.setGeometry(QtCore.QRect(130, 150, 241, 18))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.chkMachineLearning.setFont(font)
        self.chkMachineLearning.setObjectName("chkMachineLearning")
        self.chkDeepLearning = QtWidgets.QCheckBox(parent=self.centralwidget)
        self.chkDeepLearning.setGeometry(QtCore.QRect(130, 180, 241, 18))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.chkDeepLearning.setFont(font)
        self.chkDeepLearning.setObjectName("chkDeepLearning")
        self.chkSmartContract = QtWidgets.QCheckBox(parent=self.centralwidget)
        self.chkSmartContract.setGeometry(QtCore.QRect(130, 210, 241, 18))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.chkSmartContract.setFont(font)
        self.chkSmartContract.setObjectName("chkSmartContract")
        self.pushButtonSubmit = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonSubmit.setGeometry(QtCore.QRect(170, 240, 121, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.pushButtonSubmit.setFont(font)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("images/ic_send.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonSubmit.setIcon(icon)
        self.pushButtonSubmit.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonSubmit.setObjectName("pushButtonSubmit")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 441, 26))
        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"))
        self.label.setText(_translate("MainWindow", "Courses Registration"))
        self.label_2.setText(_translate("MainWindow", "Full Name:"))
        self.label_3.setText(_translate("MainWindow", "Email:"))
        self.label_4.setText(_translate("MainWindow", "Courses Selection:"))
        self.chkMachineLearning.setText(_translate("MainWindow", "Machine Learning"))
        self.chkDeepLearning.setText(_translate("MainWindow", "Deep Learning"))
        self.chkSmartContract.setText(_translate("MainWindow", "Smart Contract"))
        self.pushButtonSubmit.setText(_translate("MainWindow", "Submit"))

Tiếp tục tạo “MainWindowEx.py” kế thừa từ lớp Generate Python code để xử lý các sự kiện:

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QMessageBox

from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.pushButtonSubmit.clicked.connect(self.processSubmit)
    def show(self):
        self.MainWindow.show()
    def processSubmit(self):
        str=[]
        if self.chkMachineLearning.isChecked()==True:
            str.append(self.chkMachineLearning.text())
        if self.chkDeepLearning.isChecked()==True:
            str.append(self.chkDeepLearning.text())
        if self.chkSmartContract.isChecked()==True:
            str.append(self.chkSmartContract.text())
        separator=','
        infor="Full Name = "+self.lineEditFullName.text()+"\n"
        infor+="Email = "+self.lineEditEmail.text()+"\n"
        infor+="Selected courses:"+"\n"
        infor+=separator.join(str)
        self.msg=QMessageBox()
        self.msg.setWindowTitle("Selected Courses")
        self.msg.setText(infor)

        self.msg.show()

Hàm processsSubmit ở trên xử lý cho nút lệnh “Submit”, chương trình sẽ kiểm tra các QCheckBox nào được checked, sau đó tổng hợp các dữ liệu như Full Name, Email và các lựa chọn rồi hiển thị lên QMessageBox.

Cuối cùng ta tạo “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()

Chạy chương trình lên ta có kết quả như mong muốn.

Mã lệnh của trường hợp này các bạn tải ở đây:

https://www.mediafire.com/file/l0z5tf7oatmg0no/LearnCheckBox.rar/file

Trong ví dụ thứ 2 dưới đây, Tui sẽ minh họa khi người dùng Checked trên QCheckBox thì ngay lập tức xử lý theo các trạng thái của Checkbox, tạo dự án trong Pycharm “LearnCheckBoxDirectly”:

Thiết kế giao diện như hình dưới đặt với tên “MainWindow.ui”, đặt tên widget như hình:

Dùng công cụ để Generate Python code MainWindow.py:

# 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(442, 521)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.chkShowHide = QtWidgets.QCheckBox(parent=self.centralwidget)
        self.chkShowHide.setGeometry(QtCore.QRect(60, 30, 291, 21))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.chkShowHide.setFont(font)
        self.chkShowHide.setObjectName("chkShowHide")
        self.lblImage = QtWidgets.QLabel(parent=self.centralwidget)
        self.lblImage.setGeometry(QtCore.QRect(60, 60, 301, 371))
        self.lblImage.setText("")
        self.lblImage.setPixmap(QtGui.QPixmap("images/daughter.jpg"))
        self.lblImage.setScaledContents(True)
        self.lblImage.setObjectName("lblImage")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 442, 26))
        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"))
        self.chkShowHide.setText(_translate("MainWindow", "Show/Hide Image"))

Tạo thêm “MainWindowEx.py” kế thừa từ class generate ở trên để xử lý sự kiện:

from PyQt6.QtCore import Qt

from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.chkShowHide.setChecked(True)
        self.chkShowHide.stateChanged.connect(self.processChecked)
    def show(self):
        self.MainWindow.show()
    def processChecked(self,value):
        state=Qt.CheckState(value)
        if state==Qt.CheckState.Checked:
            self.lblImage.show()
        elif state==Qt.CheckState.Unchecked:
            self.lblImage.hide()

Hàm self.chkShowHide.setChecked(True) ở trên mặc định sẽ checked QCheckBox.

Hàm processChecked để lắng nghe xem khi nào QCheckBox được checked hay unchecked.

Lệnh self.lblImage.show() để hiển thị hình khi QCheckBox được checked

Lệnh self.lblImage.hide() để hiển thị hình khi QCheckBox được unchecked

Cuối cùng ta tạo “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()

Chạy chương trình lên ta có kết quả.

Khi checked vào QCheckBox:

Khi Unchecked vào QCheckBox:

Mã lệnh của bài trong case 2 này các bạn tải ở đây:

https://www.mediafire.com/file/7so7gruuzxlzpxj/LearnCheckBoxDirectly.rar/file

Như vậy tới đây Tui đã trình bày xong widget QCheckBox một cách khá kỹ lưỡng, với phần trình bày lý thuyết các thành phần và chức năng của QCheckBox. Cũng như có 2 ví dụ minh họa cụ thể để các bạn có thể review lại, các bạn chú ý thực hành lại nhiều lần để hiểu rõ hơn về nó.

Bài học tiếp theo Tui sẽ trình bày về QRadioButton, các bạn chú ý theo dõi

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

Bài 13: QPushButton – Basic Widgets – PyQt6

QPushButton là widget cơ bản nhưng vô cùng quan trọng trong mọi phần mềm tương tác người dùng. QPushButton sẽ nhận lệnh từ người dùng và tiến hành xử lý các yêu cầu tương ứng. Ví dụ như Nút lệnh Thoát phần mềm, nhấn vào sẽ thoát phần mềm. Nút lệnh Đăng nhập, bấm vào sẽ xử lý đăng nhập vào hệ thống. Thông thường chúng ta sẽ dùng signal clicked của QPush Button để nhận và xử lý thao tác của người dùng khi họ nhấn vào Button. Button cũng có thể định dạng màu nền, màu chữ, icon…

Để sử dụng QPushButton ta thực hiện các bước như dưới đây:

Bước 1:

Ta import lớp QPushButton từ module PyQt6.QtWidgets:

from PyQt6.QtWidgets import QPushButton

Bước 2:

Tạo đối tượng QPushButton và các thuộc tính liên quan:

button = QPushButton('Click Me')

Nếu muốn thêm icon cho QPushButton ta sẽ import thêm thư viện:

from PyQt6.QtGui import QIcon

Chép hình vào dự án, sau đó gọi đối tượng setIcon như dưới đây cho QPushButton:

button.setIcon(QIcon('myicon.png'))

Để thiết lập kích thước của QPushButton cho đẹp hơn thì ta gọi hàm setFixedSize() như dưới đây:

button.setFixedSize(QSize(100, 30))

QSize thuộc module PyQt6.QtCore nên ta cần import thư viện này, QSize sẽ nhận chiều rộng (100) và chiều cao (30) và gán cho QPushButton.

Bước 3:

Gán signal clicked cho QPushButton với slit tên là on_clicked

button.clicked.connect(self.on_clicked)
def on_clicked(self):
    print("You clicked me!")

Bây giờ ta thiết màn hình dưới đây để minh họa cho QPushButton (mặc dù chúng ta đã dùng widget này rất nhiều lần ở các bài học trước):

Phần mềm trên là phần mềm BMI (Body Mass Index) phần mềm tính chỉ số cân đối của cơ thể. Chương trình nhận vào chiều cao và cân nặng. Các QPushButton có các Icon bên cạnh.

BMI = Cân nặng (kg)/ Chiều cao (mét)*Chiều cao (mét)

Do đó khi giao diện nhân được chiều cao là CM thì cần đổi qua mét và Comment được tính theo bảng Classification như dưới đây:

ClassificationBMI range – kg/m2
Severe Thinness< 16
Moderate Thinness16 – 17
Mild Thinness17 – 18.5
Normal18.5 – 25
Overweight25 – 30
Obese Class I30 – 35
Obese Class II35 – 40
Obese Class III> 40

Trong Pycharm ta tạo dự án “LearnQPushButton” với cấu trúc như hình dưới đây:

Thư mục “images” sẽ lưu 2 biểu tượng icon, các bạn có thể lựa chọn hình bất kỳ.

Tương tư như các bài học trước thì ta dùng công cụ Qt Designer được tích hợp trong Pycharm để thiết kế màn hình “MainWindow.UI”, các bài học trước chúng ta đã rất quen thuộc với thiết kế Qt Designer rồi, ở bài này ta bổ sung Icon cho QPushButton, chọn Button và trong mục Icon bấm chọn Choose From File rồi tìm tới hình ảnh và chọn lúc này ta có Icon như hình bên dưới :

Ta có thể gán signal cho Button Close.

Sau khi thiết kế và đặt tên cho các Widget như hình ở trên thì ta tiến hành Generate giao diện thành Python code “MainWindow.py”:

# 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(507, 405)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        self.label.setGeometry(QtCore.QRect(20, 10, 471, 51))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.label.setFont(font)
        self.label.setStyleSheet("background-color: rgb(255, 255, 0);\n"
"color: rgb(0, 0, 127);")
        self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(30, 70, 81, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.label_2.setFont(font)
        self.label_2.setObjectName("label_2")
        self.lineEditWeight = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditWeight.setGeometry(QtCore.QRect(140, 80, 191, 31))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.lineEditWeight.setFont(font)
        self.lineEditWeight.setObjectName("lineEditWeight")
        self.label_3 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_3.setGeometry(QtCore.QRect(350, 70, 81, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.label_3.setFont(font)
        self.label_3.setObjectName("label_3")
        self.label_4 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_4.setGeometry(QtCore.QRect(350, 130, 81, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.label_4.setFont(font)
        self.label_4.setObjectName("label_4")
        self.lineEditHeight = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditHeight.setGeometry(QtCore.QRect(140, 140, 191, 31))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.lineEditHeight.setFont(font)
        self.lineEditHeight.setObjectName("lineEditHeight")
        self.label_5 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_5.setGeometry(QtCore.QRect(30, 130, 81, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.label_5.setFont(font)
        self.label_5.setObjectName("label_5")
        self.pushButtonCalculate = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonCalculate.setGeometry(QtCore.QRect(110, 290, 141, 51))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.pushButtonCalculate.setFont(font)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("images/ic_calculate.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonCalculate.setIcon(icon)
        self.pushButtonCalculate.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonCalculate.setObjectName("pushButtonCalculate")
        self.pushButtonClose = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonClose.setGeometry(QtCore.QRect(290, 290, 141, 51))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.pushButtonClose.setFont(font)
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("images/ic_close.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
        self.pushButtonClose.setIcon(icon1)
        self.pushButtonClose.setIconSize(QtCore.QSize(32, 32))
        self.pushButtonClose.setObjectName("pushButtonClose")
        self.label_6 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_6.setGeometry(QtCore.QRect(30, 190, 81, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.label_6.setFont(font)
        self.label_6.setObjectName("label_6")
        self.label_7 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_7.setGeometry(QtCore.QRect(30, 239, 91, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.label_7.setFont(font)
        self.label_7.setObjectName("label_7")
        self.labelComment = QtWidgets.QLabel(parent=self.centralwidget)
        self.labelComment.setGeometry(QtCore.QRect(140, 240, 281, 41))
        palette = QtGui.QPalette()
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Button, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 127, 127))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Light, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 63, 63))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Midlight, brush)
        brush = QtGui.QBrush(QtGui.QColor(127, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Dark, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Mid, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.BrightText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.ButtonText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Base, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Window, brush)
        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Shadow, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 127, 127))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.AlternateBase, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 220))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.ToolTipBase, brush)
        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.ToolTipText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Button, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 127, 127))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Light, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 63, 63))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Midlight, brush)
        brush = QtGui.QBrush(QtGui.QColor(127, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Dark, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Mid, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.BrightText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.ButtonText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Base, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Window, brush)
        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Shadow, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 127, 127))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.AlternateBase, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 220))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.ToolTipBase, brush)
        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.ToolTipText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Button, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 127, 127))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Light, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 63, 63))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Midlight, brush)
        brush = QtGui.QBrush(QtGui.QColor(127, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Dark, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Mid, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.BrightText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.ButtonText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Base, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Window, brush)
        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Shadow, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.AlternateBase, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 220))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.ToolTipBase, brush)
        brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.ToolTipText, brush)
        self.labelComment.setPalette(palette)
        font = QtGui.QFont()
        font.setPointSize(12)
        self.labelComment.setFont(font)
        self.labelComment.setStyleSheet("color: rgb(255, 0, 0);")
        self.labelComment.setObjectName("labelComment")
        self.labelBMI = QtWidgets.QLabel(parent=self.centralwidget)
        self.labelBMI.setGeometry(QtCore.QRect(140, 190, 191, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.labelBMI.setFont(font)
        self.labelBMI.setStyleSheet("color: rgb(0, 0, 255);")
        self.labelBMI.setObjectName("labelBMI")
        self.label_10 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_10.setGeometry(QtCore.QRect(350, 190, 81, 41))
        font = QtGui.QFont()
        font.setPointSize(12)
        self.label_10.setFont(font)
        self.label_10.setObjectName("label_10")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 507, 26))
        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)
        self.pushButtonClose.clicked.connect(MainWindow.close) # type: ignore
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh"))
        self.label.setText(_translate("MainWindow", "BMI Application"))
        self.label_2.setText(_translate("MainWindow", "Weight:"))
        self.lineEditWeight.setText(_translate("MainWindow", "80"))
        self.label_3.setText(_translate("MainWindow", "(Kg)"))
        self.label_4.setText(_translate("MainWindow", "(CM)"))
        self.lineEditHeight.setText(_translate("MainWindow", "170"))
        self.label_5.setText(_translate("MainWindow", "Height:"))
        self.pushButtonCalculate.setText(_translate("MainWindow", "Calculate"))
        self.pushButtonClose.setText(_translate("MainWindow", "Close"))
        self.label_6.setText(_translate("MainWindow", "BMI:"))
        self.label_7.setText(_translate("MainWindow", "Comment:"))
        self.labelComment.setText(_translate("MainWindow", "Overweight"))
        self.labelBMI.setText(_translate("MainWindow", "27.7"))
        self.label_10.setText(_translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'MS Shell Dlg 2\'; font-size:12pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">kg/m2</p></body></html>"))

Tiếp tục tạo lớp “MainWindowEx.py” kế thừa từ lớp giao diện được Generate ở trên để xử lý các sự kiện:

from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.pushButtonCalculate.clicked.connect(self.calculateBMI)
    def calculateBMI(self):
        weight=float(self.lineEditWeight.text())
        height=float(self.lineEditHeight.text())
        height=height/100
        BMI=weight/(height*height)
        BMI=round(BMI,2)
        comment=""
        if BMI<16:
            comment="Severe Thinness"
        elif BMI<17:
            comment="Moderate Thinness"
        elif BMI<18.5:
            comment="Mild Thinness"
        elif BMI<25:
            comment="Normal"
        elif BMI<30:
            comment="Overweight"
        elif BMI<35:
            comment="Obese Class I"
        elif BMI<40:
            comment="Obese Class II"
        else:
            comment = "Obese Class III"
        self.labelBMI.setText(str(BMI))
        self.labelComment.setText(comment)
    def show(self):
        self.MainWindow.show()

Lớp “MainWindowEx.py” ở trên được cung cấp slot calculateBMI cho Button “Calculate BMI” để tính chỉ số BMI cũng như đưa ra phân loại chỉ số cân đối cơ thể. Hàm này còn sử dụng round() để làm tròn 2 chữ số thập phân.

Cuối cùng ta 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()

Chạy phần mềm lên ta sẽ có kết quả như mong muốn.

Mã lệnh đầy đủ của bài BMI các bạn tải ở đây:

https://www.mediafire.com/file/a1z1zl19ov7og68/LearnQPushButton.rar/file

Ngoài ra QPushButton còn có thể định dạng loại toggle:

button = QPushButton('Toggle Me')
button.setCheckable(True)
button.clicked.connect(self.on_toggle)

Hàm on_toggle để kiểm tra trạng thái:

def on_toggle(self, checked):
    print(checked)

Như vậy tới đây Tui đã trình bày xong QPushButton về cấu trúc, cách sử dụng cũng như kỹ thuật lập trình liên quan tới QPushButton, các bạn cũng biết cách tạo Icon cho nó, biết cách gán sự kiện và xử lý các tác vụ liên quan.

Bài học sau Tui sẽ trình bày về các Widget liên quan tới CheckBox và RadioButton. Các bạn chú ý theo dõi

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

Bài 12: QLineEdit – Basic Widgets – PyQt6

QLineEdit là Widget dùng để cung cấp ô nhập dữ liệu, ví dụ như nhập tên sản phẩm, nhập giá sản phẩm, nhập user name, nhập password….

Trong bài này chúng ta sẽ học:

  • Cách khai báo và sử dụng QLineEdit trong Qt Designer và trong code python,
  • Tìm hiểu một số thuộc tính thường sử dụng của QLineEdit,
  • Cách tạo ô nhập mật khẩu bằng QLineEdit,
  • Cách tạo chức năng auto-complete cho QLineEdit

Để tạo QLineEdit ta làm các bước sau:

Bước 1:

Ta import QLineEdit trong module PyQt6.QtWidgets

from PyQt6.QtWidgets import QLineEdit

Bước 2:

Tạo đối tượng QLineEdit, có thể không có đối số nào, hoặc có parent widget hoặc có chuỗi mặc định.

nameLineEdit = QLineEdit('Tran Duy Thanh', self)

QLineEdit cung cấp nhiều thuộc tính, trong đó có một số thuộc tính thường sử dụng như dưới đây:

Thuộc tínhKiểu dữ liệuMô tả
textstringThuộc tính lưu trữ nội dung nhập liệu của QLineEdit
readOnlyBooleanNếu thiết lập True thì QLineEdit chỉ đọc không thể sửa dữ liệu
placeholderTextstringThuộc tính hiển thị chuỗi gợi ý nếu như nội dung của QLineEdit đang rỗng
echoModeQLineEdit.EchoModeThuộc tính thay đổi cách thức hiển thị chuỗi, ví dụ như hiển thị dạng nhập mật khẩu với ký hiệu *
maxLengthintegerThuộc tính thiết lập số ký tự tối đa có thể nhập cho QLineEdit
clearButtonEnabledBooleanNếu True thì sẽ thêm button clear cho QLineEdit

Ta tiến hành tạo một dự án tên “LearnQLineEdit” trong Pycharm và thiết kế giao diện như dưới đây bằng Qt Designer:

Ta tiến hành lựa chọn layout management và đặt tên và gán nhãn cho các Widget như hình dưới đây (Tui không chụp hình lại các bước kéo thả Widget nữa vì các bài trước các bạn đã làm rồi, bài này tương tự):

Ô Full name ta bổ sung Hint và clearButton:

Ta nhấn chuột chọn QLineEdit cho Full Name trước, sau đó nhập dữ liệu cho thuộc tính “placeholderText” là “Enter your full name here” khi chạy giao diện lên nếu dữ liệu ô QLineEdit này chưa được nhập thì hint này sẽ được hiển thị để hướng dẫn hay gợi ý người sử dụng cách nhập liệu. Tiếp tục checked vào thuộc tính “clearButtonEnabled” chương trình sẽ thêm nút clear Button để giúp người sử dụng dễ dàng và nhanh chóng xóa dữ liệu trong ô nhập để nhập dữ liệu mới nhanh hơn.

Ta tiến hành chạy thử giao diện trong Qt Designer bằng cách nhấn tổ hợp phím “Ctrl + r“, chương trình sẽ chạy giao diện lên như hình:

Ta quan sát thấy ô nhập Full Name có placetextHolder hiển thị (gọi là hint) khi ô dữ liệu trống. Bây giờ ta nhập liệu cho Full Name và quan sát:

Ta thấy khi nhập liệu thì dòng chữ dạng hint bị biến mất, đồng thời xuất hiện button clear ở góc phải của QLineEdit, ta có thể nhấn vào button này để xóa dữ liệu cũ để nhập dữ liệu mới được nhanh hơn.

Tiếp theo cấu hình ký tự Password (*) cho các ô Password và Confirm password bằng cách chọn echoMode thành Password:

Nhấn tổ hợp phím “ctrl+r” để tiếp tục thử nghiệm nhập liệu password:

Cuối cùng ta gán signal và slot cho Close button:

Trong thẻ “Signal/Slot” ta chọn:

Sender: Chọn closepushButton

Signal: Chọn clicked()

Receiver: Chọn MainWindow

Slot: Chọn close()

Chạy phần mềm thử nghiệm ta nhấn nút “Close” chương trình sẽ tắt cửa sổ làm việc này.

Bây giờ ta lưu file giao diện này lại với tên “MainWindow.ui”

Tiến hành tạo Python code bằng chức năng Generate code như các bài trước đã hướng dẫn, lúc này “MainWindow.py” tạo ra là mã lệnh giao diện của “MainWindow.ui”:

Dưới đây là mã lệnh đầy đủ của “MainWindow.py“:

# 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(415, 357)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        self.label.setMaximumSize(QtCore.QSize(16777215, 50))
        self.label.setObjectName("label")
        self.verticalLayout.addWidget(self.label)
        self.gridLayout = QtWidgets.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.label_2 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_2.setObjectName("label_2")
        self.gridLayout.addWidget(self.label_2, 0, 0, 1, 1)
        self.fullNamelineEdit = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.fullNamelineEdit.setClearButtonEnabled(True)
        self.fullNamelineEdit.setObjectName("fullNamelineEdit")
        self.gridLayout.addWidget(self.fullNamelineEdit, 0, 1, 1, 1)
        self.label_3 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_3.setObjectName("label_3")
        self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1)
        self.userNamelineEdit = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.userNamelineEdit.setObjectName("userNamelineEdit")
        self.gridLayout.addWidget(self.userNamelineEdit, 1, 1, 1, 1)
        self.label_4 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_4.setObjectName("label_4")
        self.gridLayout.addWidget(self.label_4, 2, 0, 1, 1)
        self.emaillineEdit = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.emaillineEdit.setObjectName("emaillineEdit")
        self.gridLayout.addWidget(self.emaillineEdit, 2, 1, 1, 1)
        self.label_5 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_5.setObjectName("label_5")
        self.gridLayout.addWidget(self.label_5, 3, 0, 1, 1)
        self.citylineEdit = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.citylineEdit.setObjectName("citylineEdit")
        self.gridLayout.addWidget(self.citylineEdit, 3, 1, 1, 1)
        self.label_6 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_6.setObjectName("label_6")
        self.gridLayout.addWidget(self.label_6, 4, 0, 1, 1)
        self.passwordlineEdit = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.passwordlineEdit.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
        self.passwordlineEdit.setObjectName("passwordlineEdit")
        self.gridLayout.addWidget(self.passwordlineEdit, 4, 1, 1, 1)
        self.label_7 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_7.setObjectName("label_7")
        self.gridLayout.addWidget(self.label_7, 5, 0, 1, 1)
        self.confirmPasswordlineEdit = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.confirmPasswordlineEdit.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
        self.confirmPasswordlineEdit.setObjectName("confirmPasswordlineEdit")
        self.gridLayout.addWidget(self.confirmPasswordlineEdit, 5, 1, 1, 1)
        self.verticalLayout.addLayout(self.gridLayout)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.registerpushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.registerpushButton.setObjectName("registerpushButton")
        self.horizontalLayout.addWidget(self.registerpushButton, 0, QtCore.Qt.AlignmentFlag.AlignHCenter)
        self.closepushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.closepushButton.setObjectName("closepushButton")
        self.horizontalLayout.addWidget(self.closepushButton, 0, QtCore.Qt.AlignmentFlag.AlignHCenter)
        self.verticalLayout.addLayout(self.horizontalLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 415, 26))
        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)
        self.closepushButton.clicked.connect(MainWindow.close) # type: ignore
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        MainWindow.setTabOrder(self.fullNamelineEdit, self.userNamelineEdit)
        MainWindow.setTabOrder(self.userNamelineEdit, self.emaillineEdit)
        MainWindow.setTabOrder(self.emaillineEdit, self.citylineEdit)
        MainWindow.setTabOrder(self.citylineEdit, self.passwordlineEdit)
        MainWindow.setTabOrder(self.passwordlineEdit, self.confirmPasswordlineEdit)
        MainWindow.setTabOrder(self.confirmPasswordlineEdit, self.registerpushButton)
        MainWindow.setTabOrder(self.registerpushButton, self.closepushButton)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh"))
        self.label.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:14pt; font-weight:600; color:#0000ff;\">Register Information</span></p></body></html>"))
        self.label_2.setText(_translate("MainWindow", "Full Name:"))
        self.fullNamelineEdit.setPlaceholderText(_translate("MainWindow", "Enter your full name here"))
        self.label_3.setText(_translate("MainWindow", "User Name:"))
        self.label_4.setText(_translate("MainWindow", "Email:"))
        self.label_5.setText(_translate("MainWindow", "City/Province:"))
        self.label_6.setText(_translate("MainWindow", "Password:"))
        self.label_7.setText(_translate("MainWindow", "Confirm Password:"))
        self.registerpushButton.setText(_translate("MainWindow", "Register"))
        self.closepushButton.setText(_translate("MainWindow", "Close"))

Tiếp theo tạo lớp kế thừa “MainWindowEx.py”:

from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
    def show(self):
        self.MainWindow.show()

Cuối cùng tạo lớp “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()

Chạy “MyApp.py” ta có kết quả:

Bây giờ ta tiến hành tạo chức năng auto-complete cho QLineEdit City/Province. Về lý thuyết ta làm theo bước sau:

Bước 1: Import QCompleter từ module PyQt6.QtWidgets

from PyQt6.QtWidgets import QCompleter

Bước 2: Tạo một danh sách dữ liệu để phục vụ cho auto-complete, thường nó là dữ liệu mảng một chiều. Ví dụ ta có:

cities=["Hà Nội","Huế", "Đà Nẵng", "Đà Lạt", "Hồ Chí Minh", "Cần Thơ"]

Bước 3: Tạo đối tượng QCompleter và gán dữ liệu danh sách ở bước 2 cho đối tượng này:

completer = QCompleter(cities)

Bước 4: Tạo đối tượng QLineEdit và gán setCompleter cho nó.

line_edit = QLineEdit(self)
line_edit.setCompleter(completer)

Bây giờ ta tiến hành bổ sung tính năng auto-complete cho City/Province, bằng cách hiệu chỉnh mã lệnh trong “MainWindowEx.py”:

from PyQt6.QtWidgets import QCompleter

from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        cities = ["Hà Nội", "Huế", "Đà Nẵng", "Đà Lạt", "Hồ Chí Minh", "Cần Thơ"]
        completer = QCompleter(cities)
        self.citylineEdit.setCompleter(completer)
    def show(self):
        self.MainWindow.show()

Tiến hành chạy “MyApp.py” ta có kết quả:

Hình trên ta thấy khi nhập City/Provice chương trình sẽ gợi ý danh sách auto-complete cho người sử dụng lựa chọn một cách nhanh chóng, đây cũng là một trong các tiêu chí về UI/UX tăng trải nghiệm sử dụng của khách hàng.

Như vậy tới đây Tui đã trình bày chi tiết và kỹ lượng xong widget QLineEdit, các bạn có thể ứng dụng QLineEdit vào các trường hợp cụ thể, biết cách sử dụng các thuộc tính quan trọng như text, clearButton, echoMode…, cũng như lập trình được auto-complete.

Mã nguồn chi tiết của bài này tải ở đây:

https://www.mediafire.com/file/55oxfrsqev3xbex/LearnQLineEdit.rar/file

Bài học sau Tui sẽ trình bày về QPushButton, là một Widget vô cùng quan trọng, hầu hết các phần mềm đều sử dụng để ra lệnh cho phần mềm tương tác. Các bạn chú ý theo dõi.

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