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.

Bài 11: QLabel – Basic Widgets – PyQt6

Các bài học trước chúng ta đã được học chi tiết về cách tạo dự án trong Pycharm và tích hợp giao diện PyQt6-Qt Designer, các bạn đã biết cách thiết kế giao diện, sử dụng các Layout management để bố cục giao diện theo từng yêu cầu riêng, cũng như cách sử dụng Signals/Slots để xử lý sự kiện, đặc biệt là biết cách viết các lớp kế thừa để xử lý sự kiện mà không bị ảnh hưởng khi trong tương lai giao diện bị thay đổi. Các chuỗi bài học tiếp theo đây Tui sẽ hướng dẫn chi tiết từng Widget trong giao diện, từ cơ bản tới nâng cao, cũng như cách xử lý đa tiến trình, Styles, Chart…. để các bạn có thể dễ dàng thiết kế mọi giao diện đáp ứng được hầu hết các yêu cầu của khách hàng. Chuỗi các bài học Tui có tổng hợp trong link này để các bạn dễ dàng truy suất: https://tranduythanh.com/pyqt6-qt-designer/. Sau khi học xong các phần xử lý giao diện, Tui sẽ hướng dẫn các bạn cách build mô hình máy học và tích hợp mô hình máy học vào các dự án phần mềm tương tác người dùng, chẳng hạn như các mô hình dự báo kinh doanh, các mô hình gợi ý sản phẩm, phân tích cảm xúc khách hàng, thị giác máy tính…. Do đó để có thể vững lý thuyết chắc tay nghề thì các Bạn cần kiên trì, chăm chỉ, đọc kỹ lý thuyết và thực hành từng bước các bài hướng dẫn nhiều lần. Các bạn không nên nhảy cóc mà hãy học lần lượt từ bài số 1 cho tới bài cuối cùng, vì hầu hết các bài học này Tui hướng dẫn từ cơ bản tới nâng cao và có tính kế thừa, vận dụng lại các bài học trước đó.

Trong bài này Tui sẽ hướng dẫn các Bạn cách thức sử dụng QLabel, QLabel là Widget dùng để hiển thị dữ liệu dạng text, hình ảnh (nhiều định dạng như bmp, gif, jpg, jpeg, png, svg, …), Một số ví dụ minh họa Chúng ta sẽ thực hiện bao gồm:

  • Hiển thị dữ liệu dạng text thông thường
  • Hiện thị dữ liệu dạng text kết hợp căn lề, font chữ
  • Hiển thị dữ liệu dạng text có định dạng HTML
  • Hiển thị dữ liệu dạng hình ảnh dạng .bmp, .jpg, .jpeg, .png, .svg (QPixmap)
  • Hiển thị dữ liệu dạng hình ảnh dạng .gif (QMovie)

Chúng ta có thể viết code để sử dụng QLable lúc runtime hoặc sử dụng Qt Designer để sử dụng QLable lúc design time, tùy vào tình huống cụ thể mà ta lựa chọn tạo QLabel theo runtime hay design time.

Để sử dụng QLabel và hiển thị dữ liệu dạng text thông thườngta làm theo các bước sau:

Bước 1: Ta import widget QLabel trong gói module PyQt6.QtWidgets

from PyQt6.QtWidgets import QLabel

Bước 2: Tạo đối tượng từ lớp QLabel

label = QLabel('Hello, I am QLabel widget')

Mã lệnh ở trên tạo một đối tượng QLabel đồng thời khởi tạo chuỗi hiển thị mặc định. Ngoài ra ta có thể tạo đối tượng QLabel sau đó mới gán chuỗi hiển thị cho Label bằng cách sau:

label = QLabel()
label.setText('Hello, I am QLabel widget')

Bước 3: Truy suất và xóa dữ liệu hiển thị trong QLabel

Để truy suất tới nhãn hiển thị của QLabel ta gọi phương thức text():

label.text()

Để xóa dữ liệu hiển thị của QLabel ta gọi phương thức clear():

label.clear()

Để thay đổi font chữ cho QLabel ta dùng phương thức setFont như sau:

font = label.font()
font.setPointSize(30)
label.setFont(font)

Để căn lề cho chuỗi hiển thị trên QLabel ta gọi phương thức setAlignment: Ví dụ thiết lập căn lề nằm giữa theo chiều ngang:

label.setAlignment(Qt.AlignmentFlag.AlignHCenter)

Một số thuộc tính căn lề cần quan tâm:

Loại căn lềMô tả
Qt.AlignmentFlag.AlignLeftCăn chuỗi hiển thị bên trái QLabel
Qt.AlignmentFlag.AlignRightCăn chuỗi hiển thị bên phải QLabel
Qt.AlignmentFlag.AlignHCenterCăn chuỗi hiển thị nằm ở giữa QLabel theo phương ngang
Qt.AlignmentFlag.AlignJustifyCăn chuỗi hiển thị điều 2 bên của QLabel
Qt.AlignmentFlag.AlignTopCăn chuỗi hiển thị nằm ở phía bên trên Top QLabel
Qt.AlignmentFlag.AlignBottomCăn chuỗi hiển thị nằm ở phía bên dưới Bottom QLabel
Qt.AlignmentFlag.AlignVCenterCăn chuỗi hiển thị nằm ở giữa QLabel theo phương đứng

Chúng ta có thể dùng ký tự | để kết hợp nhiều loại căn lề, ví dụ căn lề phía bên trên góc trái:

align_top_left = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
label.setAlignment(align_top_left)

Để hiển thị chuỗi có định dạng HTML cho QLabel ta viết như sau:

label.setText("<html><head/><body><p><span style=\" font-weight:600; color:#ff0000;\">Hello, I am QLable Widget</span></p></body></html>")

Để hiển thị dữ liệu hình ảnh định dạng .bmp, .jpg, .jpeg, .png, .svg cho QLabel ta dùng đối tượng QPixmap như dưới đây:

label = QLabel()
pixmap = QPixmap('myavatar.png')
label.setPixmap(pixmap)

Để hiển thị dữ liệu hình ảnh định dạng .gif cho QLabel ta dùng đối tượng QMovie như dưới đây:

from PyQt6.QtGui import QMovie
movie = QMovie('mygif.gif')
label = QLabel()
label.setMovie(movie)
movie.start()

Như vậy với các trình bày lý thuyết ở trên về cơ bản các bạn đã biết cách sử dụng QLabel, cách thức thao tác trên QLabel như tạo dữ liệu hiển thị, căn lề, hiển thị hình ảnh với nhiều định dạng khác nhau. Các bạn cần hiểu rõ các kiến thức này để áp dụng vào bài thiết kế thực tế.

Tiếp theo đây, chúng ta sẽ tiến hành tạo dự án tên “LearnQLabel” trong Pycharm, sử dụng Qt Designer để thiết kế cũng như thao tác với QLabel.

Sau khi tạo xong dự án “LearnQLabel“, ta tạo thêm một thư mục để lưu trữ hình ảnh .png cũng như .gif: Bấm chuột phải vào dự án chọn New/ Chọn Directory

Sau đó tạo thư mục tên là “images“:

Bạn chép 2 hình ảnh có định dạng png gif vào thư mục “images”, vì các hình ảnh bmp, jpg, jpeg, svg xử lý giống như png nên Tui chỉ minh họa png.

Tiếp theo ta sử dụng Qt Designer để thiết kế giao diện như dưới đây: Bấm chuột phải vào dự án/ chọn External Tools/ Chọn Create New Qt Designer:

Sau đó chọn QMainWindow để tạo giao diện: Trong công cụ Widget Box, ta kéo Label ra màn hình MainWindow:

Ta có thể hiệu chỉnh các thuộc tính của QLabel bằng cách bấm chuột phải vào QLabel rồi chọn “Change plain text…” để đổi nhãn hiển thị mà không có định dạng, hoặc chọn “Change rich text…” để đổi nhãn hiển thị cùng với định dạng HTML, hay đổi tên của QLabel bằng cách chọn “Change objectName…”, ngoài ra nếu muốn hiệu chỉnh nhiều thuộc tính khác của QLabel thì sau khi nhấn chuột vào QLabel, ta nhìn vào công cụ “Property Editor“, các thuộc tính của QLabel sẽ hiển thị chi tiết như text, texFormat, pixmap, alignment, margin…. tùy vào nhu cầu sử dụng mà ta thay đổi giá trị của các thuộc tính này cho nó hợp lý.

Ví dụ giả sử Tui chọn “Change rich text…” để hiệu chỉnh nhãn hiển thị có định dạng HTML:

Trong công cụ Edit Text ở trên, để thay đổi định dạng của chuỗi hiện thị, trước tiên ta bôi đen chuỗi hiển thị, sau đó chọn thay đổi size font chữ (to nhỏ), kiểu chữ (In đậm, in nghiêng, gạch chân), căn lề (trái, giữa, phải, đều 2 bên), hay hiệu chỉnh hình nền, màu nền màu chữ bằng các biểu tượng ở góc phải bên trên của cửa sổ Edit Text. Nhập dữ liệu và định dạng xong thì nhấn nút OK, ta có giao diện:

Bây giờ ta bổ sung thêm các Widgets như hình dưới đây:

Bạn cần kéo thả các Widgets và đặt tên giống như hình trên.

Ngoài ra bạn cũng có thể thêm hình vào lúc đang thiết kế, ví dụ Tui để mặc định hình hiển thị là mydaugther.PNG: Chọn QLabel, sau đó chọn pixmap-> Rồi chọn Choose File...

Sau đó chọn hình mà mình muốn hiển thị lên QLabel:

Chọn hình xong bấm Open, kết quả:

Ta checked cho thuộc tính “scaledContents” để thấy hình được stretch trong QLabel.

Lưu file giao diện với tên MainWindow.ui, nó tự động cập nhật trong dự án Pycharm:

Tiếp theo ta dùng chức năng Generate Python code cho giao diện “MainWindow.ui” lúc này file mã lệnh “MainWindow.py” được generate:

# 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(542, 450)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.Titlelabel = QtWidgets.QLabel(parent=self.centralwidget)
        self.Titlelabel.setGeometry(QtCore.QRect(40, 10, 381, 41))
        self.Titlelabel.setTextFormat(QtCore.Qt.TextFormat.AutoText)
        self.Titlelabel.setObjectName("Titlelabel")
        self.ShowPNGpushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.ShowPNGpushButton.setGeometry(QtCore.QRect(140, 100, 121, 31))
        self.ShowPNGpushButton.setObjectName("ShowPNGpushButton")
        self.showGIFpushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.showGIFpushButton.setGeometry(QtCore.QRect(270, 100, 121, 31))
        self.showGIFpushButton.setObjectName("showGIFpushButton")
        self.imageLabel = QtWidgets.QLabel(parent=self.centralwidget)
        self.imageLabel.setGeometry(QtCore.QRect(120, 140, 271, 231))
        self.imageLabel.setText("")
        self.imageLabel.setPixmap(QtGui.QPixmap("images/mydaughter.PNG"))
        self.imageLabel.setScaledContents(True)
        self.imageLabel.setObjectName("imageLabel")
        self.changeTextpushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.changeTextpushButton.setGeometry(QtCore.QRect(30, 60, 75, 23))
        self.changeTextpushButton.setObjectName("changeTextpushButton")
        self.alignLeftpushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.alignLeftpushButton.setGeometry(QtCore.QRect(240, 60, 75, 23))
        self.alignLeftpushButton.setObjectName("alignLeftpushButton")
        self.alignCenterpushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.alignCenterpushButton.setGeometry(QtCore.QRect(330, 60, 75, 23))
        self.alignCenterpushButton.setObjectName("alignCenterpushButton")
        self.alignRightpushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.alignRightpushButton.setGeometry(QtCore.QRect(410, 60, 75, 23))
        self.alignRightpushButton.setObjectName("alignRightpushButton")
        self.fontSizepushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.fontSizepushButton.setGeometry(QtCore.QRect(120, 60, 75, 23))
        self.fontSizepushButton.setObjectName("fontSizepushButton")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 542, 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", "MainWindow"))
        self.Titlelabel.setText(_translate("MainWindow", "<html><head/><body><p><span style=\" font-size:12pt; font-weight:600; color:#ff0000;\">Trần Duy Thanh</span></p></body></html>"))
        self.ShowPNGpushButton.setText(_translate("MainWindow", "Show .PNG Format"))
        self.showGIFpushButton.setText(_translate("MainWindow", "Show .GIF Format"))
        self.changeTextpushButton.setText(_translate("MainWindow", "Change Text"))
        self.alignLeftpushButton.setText(_translate("MainWindow", "Align Left"))
        self.alignCenterpushButton.setText(_translate("MainWindow", "Align Center"))
        self.alignRightpushButton.setText(_translate("MainWindow", "Align Right"))
        self.fontSizepushButton.setText(_translate("MainWindow", "Font Size ++"))

Tiếp theo ta tạo một lớp “MainWindowEx” kế thừa từ lớp Generate Python code ở trên, mục đích để xử lý sự kiện người dùng mà không bị ảnh hưởng giao diện khi trong tương lai nó thay đổi.

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()

Tiếp theo ta tạo lớp/file “MyApp.py” để 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” ta có kết quả:

Phần mềm trên có 7 chức năng mà ta cần lập trình: Đổi text, thay đổi font chữ, căn lề trái, căn giữa, căn lề phải, đổi 2 hình. Ta tiếp tục hiệu chỉnh mã lệnh cho lớp “MainWindowEx.py” để xử lý đủ 7 chức năng này:

  • Đổi Text: Gọi Signal và gán Slots cho Button đổi text, nhấn vào thì thay nhãn bằng “https://tranduythanh.com”
from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        #change text
        self.changeTextpushButton.clicked.connect(self.processChangeText)
    def show(self):
        self.MainWindow.show()
    def processChangeText(self):
        self.Titlelabel.setText("https://tranduythanh.com")

Chạy phần lên, nhấn nút “Change Text” ta có kết quả:

  • Đổi font size: Tiếp tục gán sự kiện cho nút đổi font size
from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        #change text
        self.changeTextpushButton.clicked.connect(self.processChangeText)
        #change font
        self.fontSizepushButton.clicked.connect(self.processChangeFontSize)
    def show(self):
        self.MainWindow.show()
    def processChangeText(self):
        self.Titlelabel.setText("https://tranduythanh.com")
    def processChangeFontSize(self):
        #get font object
        font=self.Titlelabel.font()
        #change font size
        font.setPointSize(20)
        #set italic
        font.setItalic(True)
        #set bold
        font.setBold(True)
        #set font name
        font.setFamily("cambria")
        #re-assign font
        self.Titlelabel.setFont(font)

Hàm/Slot processChangeFontSize ở trên dùng để thay đổi font.

  • Tiếp tục ta bổ sung mã lệnh để căn trái, phải, giữa cho text nằm trong QLable:
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
        #change text
        self.changeTextpushButton.clicked.connect(self.processChangeText)
        #change font
        self.fontSizepushButton.clicked.connect(self.processChangeFontSize)
        #set align text
        self.alignLeftpushButton.clicked.connect(self.processAlignLeft)
        self.alignCenterpushButton.clicked.connect(self.processAlignCenter)
        self.alignRightpushButton.clicked.connect(self.processAlignRight)
    def show(self):
        self.MainWindow.show()
    def processChangeText(self):
        self.Titlelabel.setText("https://tranduythanh.com")
    def processChangeFontSize(self):
        #get font object
        font=self.Titlelabel.font()
        #change font size
        font.setPointSize(20)
        #set italic
        font.setItalic(True)
        #set bold
        font.setBold(True)
        #set font name
        font.setFamily("cambria")
        #re-assign font
        self.Titlelabel.setFont(font)
    def processAlignLeft(self):
        self.Titlelabel.setAlignment(Qt.AlignmentFlag.AlignLeft)
    def processAlignCenter(self):
        self.Titlelabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
    def processAlignRight(self):
        self.Titlelabel.setAlignment(Qt.AlignmentFlag.AlignRight)
  • Cuối cùng ta viết sự kiện để thay đổi 2 hình ảnh cho các Button:
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPixmap, QMovie

from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        #change text
        self.changeTextpushButton.clicked.connect(self.processChangeText)
        #change font
        self.fontSizepushButton.clicked.connect(self.processChangeFontSize)
        #set align text
        self.alignLeftpushButton.clicked.connect(self.processAlignLeft)
        self.alignCenterpushButton.clicked.connect(self.processAlignCenter)
        self.alignRightpushButton.clicked.connect(self.processAlignRight)
        #change image with PNG format
        self.ShowPNGpushButton.clicked.connect(self.processChangePNG)
        #change image with gif format
        self.showGIFpushButton.clicked.connect(self.processChangeGIF)
    def show(self):
        self.MainWindow.show()
    def processChangeText(self):
        self.Titlelabel.setText("https://tranduythanh.com")
    def processChangeFontSize(self):
        #get font object
        font=self.Titlelabel.font()
        #change font size
        font.setPointSize(20)
        #set italic
        font.setItalic(True)
        #set bold
        font.setBold(True)
        #set font name
        font.setFamily("cambria")
        #re-assign font
        self.Titlelabel.setFont(font)
    def processAlignLeft(self):
        self.Titlelabel.setAlignment(Qt.AlignmentFlag.AlignLeft)
    def processAlignCenter(self):
        self.Titlelabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
    def processAlignRight(self):
        self.Titlelabel.setAlignment(Qt.AlignmentFlag.AlignRight)
    def processChangePNG(self):
        #create QPixmap instance
        pixmap=QPixmap("images/mydaughter.PNG")
        #set pixmap for label
        self.imageLabel.setPixmap(pixmap)
    def processChangeGIF(self):
        #create QMovie instance
        movie=QMovie("images/PyQt-Grid-AlignmentFlag.gif")
        #set movie object for label
        self.imageLabel.setMovie(movie)
        #call start method
        movie.start()

Như vậy ta đã có đầy đủ mã lệnh, chạy phần mềm lên ta được:

Như vậy Tui đã trình bày khá chi tiết cách sử dụng QLabel, và các bạn đã biết cách ứng dụng QLabel với các trường hợp khác nhau trong triển khai phần mềm. Bạn có thể kết hợp nhiều cách sử dụng QLabel vào từng trường hợp cụ thể.

Dưới đây là mã lệnh đầy đủ của bài học này:

https://www.mediafire.com/file/4a6e3xelgpmemns/LearnQLabel.rar/file

Các bạn cố gắng học phần lý thuyết và thực hành lại nhiều lần các bài hướng dẫn ở trên.

Các bài học tiếp theo Tui sẽ trình bày về QLineEdit và QPushButton. Các bạn chú ý theo dõi.

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

Bài 10: QFormLayout-Layout Management

Như vậy chúng ta đã thao tác được với các Layout: QVBoxLayout để sắp xếp widgets theo phương thẳng đứng, QHBoxLayout để sắp xếp Widgets theo phương ngang, QGridLayout để sắp xếp Widgets theo dạng dòng và cột. Trong bài này ta sẽ vào layout cuối cùng đó là QFormLayout để sắp xếp các Widget theo dạng Data- Entry, nghĩa là khi thiết kế giao diện ta muốn vừa có label vừa có nhập liệu ví dụ như thiết kế màn hình đăng nhập, thiết kế màn hình đăng ký….Thì mỗi một dòng tạo ra nó tự động có 2 cột, cột 1 là label, cột 2 là ô nhập liệu.

Bước 1: Trong Pycharm, tạo một dự án tên “LearnQFormLayout“, sử dụng Qt Designer để thiết kế, kéo FormLayout ra giao diện:

Bước 2: Để thêm các row layout, ví dụ trong bài này ta thiết kế màn hình đăng ký: Bấm chuột phải vào QFormLayout -> Chọn Add form layout row…

Thêm nhãn Full Name và QLineEdit nhập full name

Label text: Nhập tiêu đề

Label name: Đặt tên cho Label (dùng cho lập trình truy suất widget)

Field type: Chọn QLineEdit là ô nhập liệu

Field name: Đặt tên cho QLineEdit (dùng cho lập trình truy suất widget)

Sau đó nhấn OK để tạo widget.

Lưu ý tùy vào mục đích sử dụng mà ta có thể chọn các Field type khác nhau:

Bước 3: Tương tự, ta lần lượt tạo thêm User Name, Password và Confirm Password:

Bước 4: Ta bổ sung thêm QPushButton và cấu hình như bên dưới:

Kéo PushButton vào ô thứ 2, chỉnh width và height trong Property Editor như hình.

Ngoài ra các ô Password và Confirm Password, ta chỉnh echoMode qua Password:

Lưu lại giao diện với tên “MainWindow.ui

Bước 5: Generate Python code cho giao diện “MainWindow.ui” trong 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(423, 285)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.formLayout = QtWidgets.QFormLayout()
        self.formLayout.setObjectName("formLayout")
        self.fullNameLabel = QtWidgets.QLabel(parent=self.centralwidget)
        self.fullNameLabel.setObjectName("fullNameLabel")
        self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.fullNameLabel)
        self.fullNameLineEdit = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.fullNameLineEdit.setObjectName("fullNameLineEdit")
        self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.fullNameLineEdit)
        self.userNameLabel = QtWidgets.QLabel(parent=self.centralwidget)
        self.userNameLabel.setObjectName("userNameLabel")
        self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.userNameLabel)
        self.userNameLineEdit = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.userNameLineEdit.setObjectName("userNameLineEdit")
        self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.userNameLineEdit)
        self.passwordLabel = QtWidgets.QLabel(parent=self.centralwidget)
        self.passwordLabel.setObjectName("passwordLabel")
        self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.LabelRole, self.passwordLabel)
        self.passwordLineEdit = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.passwordLineEdit.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
        self.passwordLineEdit.setObjectName("passwordLineEdit")
        self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.passwordLineEdit)
        self.confirmPasswordLabel = QtWidgets.QLabel(parent=self.centralwidget)
        self.confirmPasswordLabel.setObjectName("confirmPasswordLabel")
        self.formLayout.setWidget(3, QtWidgets.QFormLayout.ItemRole.LabelRole, self.confirmPasswordLabel)
        self.confirmPasswordLineEdit = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.confirmPasswordLineEdit.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
        self.confirmPasswordLineEdit.setObjectName("confirmPasswordLineEdit")
        self.formLayout.setWidget(3, QtWidgets.QFormLayout.ItemRole.FieldRole, self.confirmPasswordLineEdit)
        self.pushButton = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton.setMinimumSize(QtCore.QSize(100, 30))
        self.pushButton.setMaximumSize(QtCore.QSize(100, 30))
        self.pushButton.setObjectName("pushButton")
        self.formLayout.setWidget(4, QtWidgets.QFormLayout.ItemRole.FieldRole, self.pushButton)
        self.verticalLayout.addLayout(self.formLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 423, 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"))
        self.fullNameLabel.setText(_translate("MainWindow", "Full Name:"))
        self.userNameLabel.setText(_translate("MainWindow", "User Name:"))
        self.passwordLabel.setText(_translate("MainWindow", "Password:"))
        self.confirmPasswordLabel.setText(_translate("MainWindow", "Confirm Password:"))
        self.pushButton.setToolTip(_translate("MainWindow", "<html><head/><body><p><span style=\" font-size:11pt; font-weight:600; color:#ff0000;\">Click here to Signup</span></p></body></html>"))
        self.pushButton.setText(_translate("MainWindow", "Sign Up"))

Bước 6: Tạo lớp Python kế thừa “MainWindowEx.py” từ “MainWindow.py” để 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
    def show(self):
        self.MainWindow.show()

Bước 7: Tạo “MyApp.py” để 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 phần mềm lên ta có kết quả:

Các bạn có thể tải mã nguồn của bài QFormLayout ở đây:

https://www.mediafire.com/file/u3553qa1fxit4q8/LearnQFormLayout.rar/file

Như vậy tới đây Tui đã trình bày xong các Layout quan trọng và thường sử dụng trong Qt Designer/ PyQt6. Các bạn có thể phối hợp các layout lại để thiết kế các bố cục giao diện theo yêu cầu của khách hàng.

Các bài học sau Tui sẽ trình bày chi tiết về các Widgets cơ bản trong PyQt6, thiết kế trong Qt Designer cũng như cách thức lập trình xử lý sự kiện tương tác người dùng. Mỗi bài Tui sẽ cố gắng cung cấp các ví dụ sát với thực tế để các bạn dễ hiểu bài, vững lý thuyết chắc tay nghề.

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