Bài 6: QMainWindow và kỹ thuật kế thừa để tùy chỉnh Signals/Slots trong PyQt6 và Qt Designer (p2)

Trong bài số 5, Tui đã hướng dẫn hoàn chỉnh cách thiết kế giao diện QMainWindow bằng hình thức kéo thả trong Qt Designer, các thuộc tính, phương thức và Slots quan trọng thường dùng Tui cũng đã giới thiệu qua. Đặc biệt kỹ thuật tạo lớp kế thừa từ QMainWindow và cách tùy chỉnh các Signals/slots rất quan trọng và hữu dụng cũng được trình bày kỹ lưỡng. Các bạn cần thực hành nhiều lần bài này để hiểu lý thuyết cũng như cách thức xử lý. Nhất là tại vì sao ta nên tạo lớp kế thừa khi xử lý các tác vụ cho các giao diện.

Trong Bài 6 này Tui sẽ tiếp tục hướng dẫn các chức năng còn lại của bài 5:

  • Cách thiết kế cửa sổ QMainWindow lúc runtime (sử dụng coding PyQt6 – Python)
  • Cách truyền Tín hiệu (dữ liệu) qua lại giữa các QMainWindow

Dĩ nhiên để cho đơn giản trong quá trình tạo ra các giao diện tương tác thì chúng ta nên dùng luôn Qt Designer hoặc Qt Creator để tạo các file UI sau đó dùng tool để tự động tạo ra các mã lệnh. Tuy nhiên Trong bài này thì Tui chủ ý không dùng công cụ Designer, mà sẽ viết code runtime (các bạn có thể dùng tool), sau đó Tui sẽ hướng dẫn một số kỹ thuật truyền dữ liệu qua lại giữa các cửa sổ giao diện. Việc truyền dữ liệu này vô cùng quan trọng vì thực tế khi triển khai phần mềm ta sẽ có nhiều màn hình giao diện khác nhau và hiển nhiên việc truyền dữ liệu qua lại sẽ sảy ra. Ví dụ như ta có màn hình Đăng nhập, đăng nhập thành công thì mở màn hình Chính lên, vậy làm sao truyền được thông tin User từ màn đăng nhập qua màn hình chính? Hay giả sử ta có màn hình A và màn hình B bất kỳ thì làm sao để A truyền dữ liệu qua cho B và ngược lại?

Cụ thể trong bài này ta sẽ làm tiếp tục làm các chức năng sau (mở lại dự án trong bài số 5):

Khi khởi động phần mềm, ta có màn hình chính như dưới đây:

  • Khi nhấn nút “Send Name“, thì một màn hình QMainWindow mới (Tui đặt tiêu đề là Second Window) sẽ hiển thị như dưới đây, đồng thời dữ liệu trong ô QLineEdit cũng được truyền qua Second Window, và lưu ý là chỉ cho phép mở một màn hình Second Window duy nhất (tức là nếu Second Window đã hiển thị lên rồi thì có nhấn bao nhiêu lần nút “Send Name” thì nó cũng không tạo ra thêm bất kỳ màn hình Second Window nào nữa):

Màn hình Second Window sẽ thực hiện các tác vụ chính sau:

  • Khi người sử dụng nhấn vào nút “Red” của Second Window thì màn hình ban đầu sẽ đổi qua nền đỏ:
  • Khi người sử dụng nhấn vào nút “Yellow” của Second Window thì màn hình ban đầu sẽ đổi qua nền Vàng:
  • Khi người sử dụng nhập dữ liệu trong ô QLineEdit của Second Window thì dữ liệu này sẽ tự động truyền lại màn hình ban đầu và cập nhật realtime:
  • Khi nhấn nút “Close” trong màn hình Second Window thì sẽ đóng cửa sổ này để quay lại cửa sổ ban đầu

Ta bắt đầu nhé, trước tiên tạo thêm một lớp giao diện tên là “SecondWindow.py” trong dự án “LearnQMainWindow” (bạn có thể tạo bằng Qt Designer, còn bài này mục đích của Tui là hướng dẫn các bạn cách tự coding ra giao diện):

Dưới đây là chi tiết mã lệnh của “SecondWindow.py“:

from PyQt6 import QtCore
from PyQt6.QtWidgets import QLabel, QLineEdit, QPushButton

class SecondWindow(object):
    def __init__(self,parent):
        self.parent=parent
    def setupUi(self, MainWindow):
        self.MainWindow=MainWindow
        self.MainWindow.setWindowTitle("Tran Duy Thanh - Second Window")
        self.MainWindow.resize(381, 110)

        self.label = QLabel(parent=MainWindow)
        self.label.setText("Change Name:")
        self.label.setGeometry(QtCore.QRect(20, 10, 91, 16))

        self.lineEditFullName = QLineEdit(parent=MainWindow)
        self.lineEditFullName.setGeometry(QtCore.QRect(20, 40, 351, 22))

        self.pushButtonRed = QPushButton(parent=MainWindow)
        self.pushButtonRed.setText("Red")
        self.pushButtonRed.setGeometry(QtCore.QRect(40, 70, 93, 28))

        self.pushButtonYellow = QPushButton(parent=MainWindow)
        self.pushButtonYellow.setText("Yellow")
        self.pushButtonYellow.setGeometry(QtCore.QRect(150, 70, 93, 28))

        self.pushButtonClose = QPushButton(parent=MainWindow)
        self.pushButtonClose.setGeometry(QtCore.QRect(250, 70, 93, 28))
        self.pushButtonClose.setText("Close")

        self.lineEditFullName.setText(self.parent.lineEditName.text())

        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        self.lineEditFullName.textChanged.connect(self.parent.lineEditName.setText)
        self.pushButtonClose.clicked.connect(self.processClose)
        self.pushButtonRed.clicked.connect(self.parent.changeRedColor)
        self.pushButtonYellow.clicked.connect(self.parent.changeYellowColor)
    def processClose(self):
        self.MainWindow.close()
        self.parent.secondWindow=None

Trong lớp trên, ta thấy:

  • Dòng 5-6 định nghĩa một constructor của SecondWindow có đối số truyền vào là parent, và parent chính lớp “MyQMainWindowExt“:
def __init__(self,parent):
    self.parent=parent
  • Dòng 35 là Signal truyền thông báo dữ liệu trong ô nhập liệu của Second Window bị thay đổi qua cho màn hình Parent ( “MyQMainWindowExt“), lúc này ô QLineEdit của màn hình Parent sẽ tự động cập nhật theo Second Window.
self.lineEditFullName.textChanged.connect(self.parent.lineEditName.setText)
  • Dòng 36 là Signal để đóng cửa sổ hiện tại:
self.pushButtonClose.clicked.connect(self.processClose)

Signal này dùng hàm processClose để làm Slot, slot này sẽ đóng cửa sổ hiện tại, đồng thời đánh dấu nó = None trong màn hình parent (“MyQMainWindowExt“), nó được khai báo trong dòng 39 tới 41:

 def processClose(self):
     self.MainWindow.close()
     self.parent.secondWindow=None
  • Dòng 37 và 38: Màn hình Second Window sẽ truyền 2 Signals qua màn hình parent (“MyQMainWindowExt“) là changeRedColor (yêu cầu parent tô nền đỏ), và changeYellowColor (yêu cầu parent tô nền vàng):
self.pushButtonRed.clicked.connect(self.parent.changeRedColor)
self.pushButtonYellow.clicked.connect(self.parent.changeYellowColor)

Bước tiếp theo ta tiến hành chỉnh sửa mã lệnh trong lớp parent (“MyQMainWindowExt“), đây là mã lệnh đầy đủ:

from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QMessageBox, QMainWindow

from MyQMainWindow import Ui_MainWindow
from SecondWindow import SecondWindow


class MyQMainWindowExt(Ui_MainWindow):
    #override setupUi
    #just define attribute MainWindow for reuse in later
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.secondWindow=None
    #define methods for assigning Signals and Slots
    def processSignalAndSlot(self):
        self.pushButtonExit.clicked.connect(self.processExit)
        self.pushButtonVisitBlog.clicked.connect(self.openMyBlog)
        self.pushButtonSendName.clicked.connect(self.openSecondWindow)
    #define slot exit window
    def processExit(self):
        dlg = QMessageBox(self.MainWindow)
        dlg.setWindowTitle("Exit Confirmation")
        dlg.setText("Are you sure you want to Exit?")
        dlg.setStandardButtons(
            QMessageBox.StandardButton.Yes
            | QMessageBox.StandardButton.No
        )
        dlg.setIcon(QMessageBox.Icon.Question)
        button = dlg.exec()
        # check the user confirmation
        button = QMessageBox.StandardButton(button)
        if button == QMessageBox.StandardButton.Yes:
            self.MainWindow.close()
        else:
            pass#do nothing
    def openMyBlog(self):
        import webbrowser
        webbrowser.open('https://tranduythanh.com/')
    def openSecondWindow(self):
        if self.secondWindow==None or self.qmainWindow.isVisible()==False:
            self.qmainWindow = QMainWindow()
            self.secondWindow=SecondWindow(self)
            self.secondWindow.setupUi(self.qmainWindow)
            self.qmainWindow.show()
    def changeRedColor(self):
        self.MainWindow.setStyleSheet("background-color: red;")
    def changeYellowColor(self):
        self.MainWindow.setStyleSheet("background-color: yellow;")
  • Dòng 14: Ta bổ sung một biến secondWindow để lưu lại bộ nhớ tham chiếu nhằm xử lý tác vụ cửa sổ này đã mở hay chưa, nếu mở rồi thì không cho mở nữa:
self.secondWindow=None
  • Dòng 19 ta truyền Signal cho nút “Send Name”, khi nhấn vào nút này thì nó sẽ mở màn hình Second Window:
self.pushButtonSendName.clicked.connect(self.openSecondWindow)

Ta định nghĩa hàm “openSecondWindow” để làm Slot cho Signal này, chi tiết được viết trong dòng 40 tới dòng 45:

    def openSecondWindow(self):
        if self.secondWindow==None or self.qmainWindow.isVisible()==False:
            self.qmainWindow = QMainWindow()
            self.secondWindow=SecondWindow(self)
            self.secondWindow.setupUi(self.qmainWindow)
            self.qmainWindow.show()

Hàm trên sẽ kiểm tra xem secondWindow đã được mở hay chưa, nếu chưa thì sẽ mở của sổ này lên. Lưu ý dòng 43:

self.secondWindow=SecondWindow(self)

Dòng 43 tạo một đối tượng SecondWindow với đối số truyền vào là self (self chính là đối tượng hiện tại, cửa sổ hiện tại “MyQMainWindowExt”. Nó được lưu vào biến parent trong Second Window.

  • Cuối cùng là dòng 46 tới dòng 49 là các phương thức được định nghĩa để làm slot đổi màu nền (từ SecondWindow truyền tín hiệu qua cho Parent):
    def changeRedColor(self):
        self.MainWindow.setStyleSheet("background-color: red;")
    def changeYellowColor(self):
        self.MainWindow.setStyleSheet("background-color: yellow;")

Các hàm đổi màu nền này ta dùng setStyleSheet như cú pháp ở trên.

Chạy “MyApp.py” ta có kết quả như mô tả.

Như vậy tới đây Tui đã hướng dẫn đầy đủ và chi tiết xong:

  • Cách thiết kế cửa sổ QMainWindow lúc runtime (sử dụng coding PyQt6 – Python)
  • Cách truyền Tín hiệu (dữ liệu) qua lại giữa các QMainWindow

Đây là kỹ thuật rất quan trọng và thường xuyên sử dụng trong phần mềm, các bạn chú ý thực hiện lại và cố gắng đọc hiểu cũng như tự tay lập trình được các bài tương tự.

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

https://www.mediafire.com/file/3hjwq8xk0gofe7x/LearnQMainWindow_p2.rar/file

Bài học sau Tui sẽ trình bày về các loại Layout trong bố cục giao diện. Muốn làm giao diện theo ý mình thì bắt buộc phải nắm chắc cách thức bố cục thông qua các đối tượng Layout:

  • QHBoxLayout
  • QVBoxLayout
  • QGridLayout
  • QStackedLayout

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

Bài 5: QMainWindow và kỹ thuật kế thừa để tùy chỉnh Signals/Slots trong PyQt6 và Qt Designer

QMainWindow là một lớp cửa sổ cho ta bố trí và thiết kế các control trên giao diện của cửa sổ này để tạo ra các màn hình tương tác đáp ứng yêu cầu của người sử dụng. QMainWindow nó tương tự như Form trong C# Winform, MainWindow trong C# WPF, JFrame trong Java…

Ta có thể thiết kế giao diện bằng Qt Designer (kéo thả – Design time) hoặc bằng coding PyQt6 – Python (Run time). Đồng thời ta cũng có thể viết các kỹ thuật kế thừa từ lớp giao diện được Generate để tách lập hoàn toàn giao diện với xử lý mã lệnh cho các tác vụ trong tương lai, và khi giao diện thay đổi mà Generate lại thì các mã lệnh xử lý mà ta mới bổ sung vẫn không bị ảnh hưởng.

Vì vậy trong bài học này, Tui sẽ hướng dẫn các kiến thức và kỹ thuật liên quan tới QMainWindow như sau:

  1. Cách thiết kế cửa sổ QMainWindow lúc Design time (sử dụng Qt Designer hoặc Qt Creator)
  2. Các thuộc tính và phương thức/slots thường sử dụng trong QMainWindow
  3. Cách tạo lớp kế thừa QMainWindow và cách tùy chỉnh các Signals/Slots

Ta tạo một dự án trong Pycharm đặt tên là “LearnQMainWindow“, Bạn cần thành thạo bài học số 4 trước khi học bài này, cần học tuần tự từ bài đầu tiên cho tới bài cuối cùng. Do đó từ bài sau trở đi Tui sẽ không nhắc lại các bước tạo dự án cũng như mở Qt Designer, đồng thời thao tác tự động Generate Python code cho các Giao diện cũng không được nhắc lại.

Trong dự án “LearnQMainWindow” tạo một cửa sổ QMainWindow bằng công cụ Qt Designer và đặt tên là “MyQMainWindow.ui” (trong màn hình New Form chọn Main Window rồi nhấn nút Create). Màn hình này có giao diện như dưới đây:

Ta thiết kế màn hình giao diện như trên bằng cách kéo thả các control:

Các chức năng bao gồm (chi tiết các loại Control sẽ được học ở những bài sau):

Loại ControlTên ControlDữ liệu/nhãn hiển thịChức năng
QLineEditlineEditNameTran Duy ThanhDùng để nhập dữ liệu, đồng thời sẽ chuyển dữ liệu này qua màn hình khác khi nhấn nút “Send Name“, và sẽ tự động cập nhật lại dữ liệu khi màn hình mới thay đổi dữ liệu.
QPushButtonpushButtonSendNameSend NameNút lệnh này sẽ truyền dữ liệu trong ô lineEditName qua màn hình mới. (sẽ được học ở bài số 6)
QPushButtonpushButtonVisitBlogVisit BlogNút lệnh này sẽ mở blog: https://tranduythanh.com/
QPushButtonpushButtonExitExitNút lệnh này sẽ thoát chương trình, tuy nhiên nó sẽ hiển thị Dialog xác thực có muốn thoát hay không.

Để đặt tên control và các nhãn hiển thị thì ta nhấn chuột vào từng control đó trước, sau đó thiết lập các thông số trong mục Property Editor (xem lại bài số 3 được trình bày rất kỹ).

Với nhóm QPushButton ta chọn objectName để đổi tên control, chọn thuộc tính Text trong QAbstractButton để hiển thị nhãn dữ liệu:

Đối với QLineEdit ta chọn objectName để đổi tên control, chọn thuộc tính Text trong QLineEdit để hiển thị nhãn dữ liệu:

Sau khi thiết kế xong, để kiểm tra giao diện trước khi đi qua lập trình xem có phù hợp hay không thì ta vào menu Form/ chọn Preview… hoặc nhấn tổ hợp phím Ctrl+R.

Màn hình Preview… hiển thị ra như dưới đây để ta test giao diện:

Dưới đây là các thuộc tính và phương thức(slots) quan trọng thường sử dụng của QMainWindow:

Thuộc tính/phương thức(Slots)Chức năng
objectNameThiết lập tên của cửa sổ, dùng cho triệu gọi khi lập trình tương tác cửa sổ
windowTitleThiết lập tiêu đề của cửa sổ
windowIconThiết lập Icon cho cửa sổ
fontThiết lập font chữ cho các control trong cửa số, font chữ bao gồm tên font chữ, kích thước font chữ, kiểu font chữ như In đậm, In nghiêng, gạch chân…
cursorThiết lập biểu tượng con trỏ chuột hiển thị khi di chuyển trong cửa sổ
toolTipThiết lập gợi ý chức năng cho cửa sổ khi di chuyển chuột vào
geometryThiết lập vị trí xuất hiện của cửa sổ đồng thời xác định chiều rộng và chiều dài cho cửa sổ
minimumSizeKích thước tối thiểu của cửa sổ
maximumSizeKích thước tối đa của cửa sổ
styleSheetThuộc tính dùng cho thiết lập các định dạng nâng cao của cửa sổ dạng HTML. Ta có thể thêm các tài nguyên như hình ảnh, phối màu, font chữ… cho đối tượng
isVisible()Phương thức kiểm tra xem cửa sổ có đang hiển thị hay không
hide()Phương thức dùng để ẩn cửa sổ
show()Phương thức dùng để hiển thị cửa sổ
close()Phương thức dùng để đóng cửa sổ

QMainWindow còn nhiều các thuộc tính và slots khác nữa, trong quá trình học tập, Tui sẽ trình bày thêm khi gặp. Hoặc trong quá trình thực hiện dự án, gặp trường hợp nào thì bạn search theo trường hợp đó, bạn tìm hiểu thêm ở đây.

Bây giờ ta sẽ lưu “MyQMainWindow.ui” lại lần cuối và tiến hành Generate Python code cho giao diện này trong dự án “LearnQMainWindow”:

File “MyQMainWindow.py” sẽ được tạo ra như dưới đây, có lớp tên là “Ui_MainWindow” được tự động tạo ra bởi công cụ Generate:

# Form implementation generated from reading ui file 'MyQMainWindow.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(565, 245)
        font = QtGui.QFont()
        font.setPointSize(11)
        MainWindow.setFont(font)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButtonSendName = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonSendName.setGeometry(QtCore.QRect(40, 130, 151, 41))
        self.pushButtonSendName.setObjectName("pushButtonSendName")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        self.label.setGeometry(QtCore.QRect(20, 10, 151, 41))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.lineEditName = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditName.setGeometry(QtCore.QRect(40, 70, 491, 31))
        self.lineEditName.setObjectName("lineEditName")
        self.pushButtonVisitBlog = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonVisitBlog.setGeometry(QtCore.QRect(210, 130, 151, 41))
        self.pushButtonVisitBlog.setObjectName("pushButtonVisitBlog")
        self.pushButtonExit = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonExit.setGeometry(QtCore.QRect(380, 130, 151, 41))
        self.pushButtonExit.setObjectName("pushButtonExit")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 565, 31))
        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 - QMainWindow"))
        self.pushButtonSendName.setText(_translate("MainWindow", "Send Name"))
        self.label.setText(_translate("MainWindow", "Your Name:"))
        self.lineEditName.setText(_translate("MainWindow", "Tran Duy Thanh"))
        self.pushButtonVisitBlog.setText(_translate("MainWindow", "Visit Blog"))
        self.pushButtonExit.setText(_translate("MainWindow", "Exit"))

Bạn cần nhớ là Giao diện thì ta có thể phải thay đổi liên tục trong quá trình phát triển (hiển nhiên), do đó tránh việc viết mã lệnh trong file tự động Generate này, vì mỗi lần đổi giao diện mà Generate lại thì toàn bộ mã lệnh mà ta bổ sung trước đó sẽ ra đi không kèn không trống.

Vậy làm sao để vẫn thay đổi giao diện thoải mái nhưng các mã lệnh bổ sung vẫn trơ gan cùng tuế nguyệt?

Câu trả lời hoàn hảo đó là “Kế thừa

Trong dự án “LearnQMainWindow”, ta tạo thêm 1 file tên là “MyQMainWindowExt.py” + ta tạo lớp cùng tên và kế thừa từ lớp “Ui_MainWindow” (là lớp mà tự động được generate ra trong file “MyQMainWindow.py”. Mọi mã lệnh bổ sung thêm ta viết trong lớp mới này (tức là từ rày về sau ta không viết thêm bất kỳ một mã lệnh nào trong Ui_MainWindow)

Mã lệnh của MyQMainWindowExt như dưới đây:

Trong lớp “MyQMainWindowExt”, Tui viết nó kế thừa từ Ui_MainWindow (xem dòng số 5)

và lớp này Tui định nghĩa lại hàm setupUi, hàm này đơn giản chỉ là gọi lại hàm setupUi ở lớp cha của nó (lớp Ui_MainWindow) và Tui tạo thêm 1 biến thuộc tính cho lớp này ở dòng số 10, mục đích là tái sử dụng mọi nơi trong lớp này

Tui định nghĩa thêm một hàm processSignalAndSlot(). Hàm này có nhiệm vụ triệu gọi tất cả các Signals và Slots nếu có trong quá trình xử lý tác vụ.

Ví dụ trong trường hợp này Tui chỉ mới làm signals là nhấn vào nút Exit thì thoát chương trình, nhưng Tui có hiệu chỉ là khi nhấn vào nút Exit thì sẽ gọi Slots processExit do Tui tạo mới này (thực ra nó là 1 function thôi, nhưng vì nó được triệu gọi trong Signals nên được gọi là Slots). Function này sẽ sử dụng QMesssageBox ở dòng số 16 để xác thực người sử dụng có chắc chắn muốn thoát phần mềm hay không.

Dưới đây là mã lệnh đầy đủ cho lớp “MyQMainWindowExt“:

from PyQt6.QtWidgets import QMessageBox

from MyQMainWindow import Ui_MainWindow

class MyQMainWindowExt(Ui_MainWindow):
    #override setupUi
    #just define attribute MainWindow for reuse in later
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
    #define methods for assigning Signals and Slots
    def processSignalAndSlot(self):
        self.pushButtonExit.clicked.connect(self.processExit)
    #define slot exit window
    def processExit(self):
        dlg = QMessageBox(self.MainWindow)
        dlg.setWindowTitle("Exit Confirmation")
        dlg.setText("Are you sure you want to Exit?")
        dlg.setStandardButtons(
            QMessageBox.StandardButton.Yes
            | QMessageBox.StandardButton.No
        )
        dlg.setIcon(QMessageBox.Icon.Question)
        button = dlg.exec()
        # check the user confirmation
        button = QMessageBox.StandardButton(button)
        if button == QMessageBox.StandardButton.Yes:
            self.MainWindow.close()
        else:
            pass#do nothing

Tiếp theo, Chúng ta tạo 1 file “MyApp.py” để triệu gọi “MyQMainWindowExt”:

Dưới đây là coding đầy đủ cho MyApp.py:

from PyQt6.QtWidgets import QApplication, QMainWindow

from MyQMainWindowExt import MyQMainWindowExt
#Create QApplication instance
app=QApplication([])
#Create QMainWindow instance
qMainWindow=QMainWindow()
#Create MyQMainWindowExt instance
myWindow=MyQMainWindowExt()
#call setupUi method for MyQMainWindowExt
myWindow.setupUi(qMainWindow)
#call methods for Signal and slots processing
myWindow.processSignalAndSlot()
#call show method to show Window
qMainWindow.show()
#start Event loop
app.exec()

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

Bây giờ ta nhấn nút “Exit”, phần mềm sẽ hiển thị QMessageBox để xác thực:

Nếu nhấn Yes, chương trình sẽ gọi slots self.MainWindow.close() để thoát phần mềm. Nếu nhấn No thì không làm gì

Tiếp theo ta bổ sung Slots cho nút lệnh “Visit Blog“, nhấn vào nó sẽ mở blog https://tranduythanh.com/

Trong “MyQMainWindowExt.py” ta bổ sung lệnh cho hàm processSignalAndSlot:

    #define methods for assigning Signals and Slots
    def processSignalAndSlot(self):
        self.pushButtonExit.clicked.connect(self.processExit)
        self.pushButtonVisitBlog.clicked.connect(self.openMyBlog)

Đồng thời viết hàm openMyBlog để làm Slots:

    def openMyBlog(self):
        import webbrowser
        webbrowser.open('https://tranduythanh.com/')

mã lệnh đầy đủ của “MyQMainWindowExt“:

from PyQt6.QtWidgets import QMessageBox

from MyQMainWindow import Ui_MainWindow

class MyQMainWindowExt(Ui_MainWindow):
    #override setupUi
    #just define attribute MainWindow for reuse in later
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
    #define methods for assigning Signals and Slots
    def processSignalAndSlot(self):
        self.pushButtonExit.clicked.connect(self.processExit)
        self.pushButtonVisitBlog.clicked.connect(self.openMyBlog)
    #define slot exit window
    def processExit(self):
        dlg = QMessageBox(self.MainWindow)
        dlg.setWindowTitle("Exit Confirmation")
        dlg.setText("Are you sure you want to Exit?")
        dlg.setStandardButtons(
            QMessageBox.StandardButton.Yes
            | QMessageBox.StandardButton.No
        )
        dlg.setIcon(QMessageBox.Icon.Question)
        button = dlg.exec()
        # check the user confirmation
        button = QMessageBox.StandardButton(button)
        if button == QMessageBox.StandardButton.Yes:
            self.MainWindow.close()
        else:
            pass#do nothing
    def openMyBlog(self):
        import webbrowser
        webbrowser.open('https://tranduythanh.com/')

Chạy “MyApp.py”. Nhấn nút “Visit Blog” chương trình sẽ mở màn hình Blog trên trình duyệt:

Như vậy tới đây Tui đã hướng dẫn hoàn chỉnh cách thiết kế giao diện QMainWindow bằng hình thức kéo thả trong Qt Designer, các thuộc tính, phương thức và Slots quan trọng thường dùng Tui cũng đã giới thiệu qua. Đặc biệt kỹ thuật tạo lớp kế thừa từ QMainWindow và cách t ùy chỉnh các Signals/slots rất quan trọng và hữu dụng cũng được trình bày kỹ lưỡng. Các bạn cần thực hành nhiều lần bài này để hiểu lý thuyết cũng như cách thức xử lý. Nhất là tại vì sao ta nên tạo lớp kế thừa khi xử lý các tác vụ cho các giao diện.

Bài 6 Tui sẽ tiếp tục làm chức năng còn lại của bài này, đó là Tui sẽ hướng dẫn các bạn 2 kỹ thuật chính:

  • Cách thiết kế cửa sổ QMainWindow lúc runtime (sử dụng coding PyQt6 – Python)
  • Cách truyền Tín hiệu (dữ liệu) qua lại giữa các QMainWindow

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

https://www.mediafire.com/file/ocou1o8050qz1td/LearnQMainWindow.rar/file

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

Bài 4: Xử lý Signals và Slots trong PyQt6 và Qt Designer

Hầu hết mọi giao diện đều cung cấp các chức năng cho người sử dụng tương tác trên giao diện. Các ngôn ngữ như C#, Java chúng ta thường nghe tới thuật ngữ “Event”. Tuy nhiên đối với Python, cụ thể là PyQt6 & Qt Designer thì chúng ta sẽ nghe thêm thuật ngữ Signals Slots.

Signals hiểu nôm na là các hành động được phát đi (truyền đi) bởi các đối tượng trên giao diện (gọi chung là các Widget) khi có cái gì đó xảy ra. Ví dụ như dữ liệu thay đổi trong Input Box, hay thao tác nhấn trên Button… Các Signals này thường được phát sinh bởi các hành động của người sử dụng. Các Signals này khi phát đi nó có thể đính kèm cả dữ liệu mà nó muốn phát. 1 Sender (đối tượng) sẽ có nhiều Signals.

Slots hiểu nôm na là các đối tượng nhận Signals (trong Python thì Hàm/Function cũng có thể được dùng làm Slots, ví dụ như khi nhấn vào Nút Thoát thì ta truyền Signals tới hàm XuLyThoat (hàm này gọi là Slots) thì phần mềm sẽ thoát.). 1 Receiver(đối tượng) sẽ có nhiều Slot.

Các Signals Slots ta có thể cấu hình ngay trên Qt Designer hoặc ta tự lập trình trong code Python.

Các đối tượng trong PyQt6 được xây dựng nhiều Slots có sẵn (gọi là built-in Slots), do đó ta có thể sử dụng trực tiếp các Slot này.

Phần mềm dưới đây Tui sẽ minh họa một số ví dụ của Signals và Slots:

  • Nhập dữ liệu trong QLineEdit thì phát tín hiệu (signals) tới QLabel là QLineEdit đang thay đổi dữ liệu đồng thời truyền dữ liệu từ QLineEdit qua cho QLabel (Như vậy bên QLabel gọi là Slots).
  • Tạo Signals khi nhấn vào 1 QPushButton thì màu nền của MainWindow đổi qua màu đỏ
  • Tạo Signals khi nhấn vào 1 QPushButton thì thoát phần mềm.

Các Signals và Slots ở trên có thể dụng built-in hoặc ta tự viết thêm các hàm để làm Slots.

Tạo một dự án tên “LearnSignalsAndSlots” trong Pycharm (cách tạo dự án và giao diện Qt Designer xem lại bài 2bài 3).

Chọn và Thiết kế giao diện MainWindow như hình dưới đây (lưu với tên file “MyMainWindow.ui” vào dự án “LearnSignalsAndSlots” trong Pycharm). Lưu ý màu nền, màu chữ ta tìm thuộc tính styleSheet để cấu hình:

Trong bài này, các Bạn cứ kéo thả control ra và đặt tên như trong Object Inspector. Chi tiết các controls này sẽ được hướng dẫn ở các bài học tiếp theo. Trong bài này chúng ta chỉ focus vào Signals Slots.

Bước 1: Tạo Signal cho QLineEdit (lineEditName) và Slot cho QLabel (labelName):

  • Nhấn biểu tượng dấu + màu xanh trong mục Signal/Slot Editor:

Lúc này một dòng <sender> <signal> <Receiver> <slot> xuất hiện. Ta chọn như sau:

  • Mục <sender>: Chọn lineEditName
  • Mục <Signal> là gán tín hiêu nào sẽ được truyền đi đối với lineEditName. Ta chọn textChanged(QString):
  • Mục <Receiver> là chọn control labelName nào để nhận Signal.
  • Mục <slot> chọn setText(QString)

Bước 2: Tạo Signal cho pushButtonExit, Slot cho MainWindow. Cách nào tương tự như cho QLineEdit và QLabel nên trong bước này chỉ hiển thị kết quả cuối cùng, còn thao tác các bạn lặp lại như hướng dẫn ở trên.

  • Mục <Sender>: Chọn pushButtonExit
  • Mục <Signal>: Chọn clicked()
  • Mục <Receiver>: Chọn MainWindow
  • Mục <Slot>: Chọn close()

Bước 3: Tạo Signal cho pushButtonChangeColor, Slot cho MainWindow (vì ta muốn khi nhấn vào nút Change Color thì đổi màu nền của MainWindow)

Lưu ý rằng, đối với pushButtonChangeColor thì mục <slot> ta chọn đại một slot nào đó (ví dụ chọn repaint()) vì MainWindow không có sẵn slot đổi màu nền (không có built-in slot đổi màu nền). Vì thế ta sẽ lập trình thêm slot bằng cách viết hàm đổi màu nền cho MainWindow, tức là trong trường hợp này Slot của MainWindow là một hàm mới do ta định nghĩa. Nó sẽ được viết sau khi chúng ta Generate Python code cho giao diện này (sau khi viết xong ta thay thế cho hàm repaint()). Tại sao ta lại lấy đại 1 hàm nào đó làm Slot? bởi vì nếu không lấy hàm nào đó ra làm Slot thì lúc generate code nó không tạo ra lệnh Signal clicked() cho pushButtonChangeColor, mất công ta phải viết bổ sung, do đó đây chỉ là Tips để ta lập trình tạo Slot mới nhanh chóng.

Chúng ta tiến hành lưu giao diện “MyMainWindow.ui” lại để Generate code Python nó ra giao diện cuối cùng.

Sau khi Generate Python Code with PyUIC, ta có mã lệnh “MyMainWindow.py“:

Chi tiết mã lệnh của “MyMainWindow.py“:

# Form implementation generated from reading ui file 'MyMainWindow.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(487, 245)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.lineEditName = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditName.setGeometry(QtCore.QRect(50, 60, 411, 31))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.lineEditName.setFont(font)
        self.lineEditName.setObjectName("lineEditName")
        self.labelName = QtWidgets.QLabel(parent=self.centralwidget)
        self.labelName.setGeometry(QtCore.QRect(50, 110, 411, 31))
        palette = QtGui.QPalette()
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Button, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.ButtonText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Base, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Window, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Button, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.ButtonText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Base, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Window, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Button, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.ButtonText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Base, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Window, brush)
        self.labelName.setPalette(palette)
        font = QtGui.QFont()
        font.setPointSize(15)
        self.labelName.setFont(font)
        self.labelName.setAutoFillBackground(False)
        self.labelName.setStyleSheet("background-color: rgb(255, 255, 0);\n"
"color: rgb(170, 0, 255);")
        self.labelName.setText("")
        self.labelName.setObjectName("labelName")
        self.pushButtonChangeColor = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonChangeColor.setGeometry(QtCore.QRect(50, 150, 181, 41))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.pushButtonChangeColor.setFont(font)
        self.pushButtonChangeColor.setObjectName("pushButtonChangeColor")
        self.pushButtonExit = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonExit.setGeometry(QtCore.QRect(350, 150, 111, 41))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.pushButtonExit.setFont(font)
        self.pushButtonExit.setObjectName("pushButtonExit")
        self.label_2 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(120, 10, 241, 31))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.label_2.setFont(font)
        self.label_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label_2.setObjectName("label_2")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 487, 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.lineEditName.textChanged['QString'].connect(self.labelName.setText) # type: ignore
        self.pushButtonExit.clicked.connect(MainWindow.close) # type: ignore
        self.pushButtonChangeColor.clicked.connect(MainWindow.repaint) # type: ignore
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        MainWindow.setTabOrder(self.lineEditName, self.pushButtonChangeColor)
        MainWindow.setTabOrder(self.pushButtonChangeColor, self.pushButtonExit)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh - Signals & Slots"))
        self.pushButtonChangeColor.setText(_translate("MainWindow", "Change Color"))
        self.pushButtonExit.setText(_translate("MainWindow", "Exit"))
        self.label_2.setText(_translate("MainWindow", "Signals & Slots"))

Rõ ràng ta thấy dòng 121 ở trên có:

self.pushButtonChangeColor.clicked.connect(MainWindow.repaint) # type: ignore

Ta sửa thành:

self.pushButtonChangeColor.clicked.connect(self.changeBackground) # type: ignore

Bổ sung dòng lệnh dưới cùng của hàm setupUi:

self.MainWindow=MainWindow

Sau đó Ta viết thêm một hàm để làm Slot, Slot này sẽ đổi màu nền của MainWindow qua màu đỏ:

def changeBackground(self):
self.MainWindow.setStyleSheet("background-color: red;")

Chi tiết mã lệnh cuối cùng của “MyMainWindow.py“:

# Form implementation generated from reading ui file 'MyMainWindow.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(487, 245)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.lineEditName = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditName.setGeometry(QtCore.QRect(50, 60, 411, 31))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.lineEditName.setFont(font)
        self.lineEditName.setObjectName("lineEditName")
        self.labelName = QtWidgets.QLabel(parent=self.centralwidget)
        self.labelName.setGeometry(QtCore.QRect(50, 110, 411, 31))
        palette = QtGui.QPalette()
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Button, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.ButtonText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Base, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Active, QtGui.QPalette.ColorRole.Window, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Button, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.ButtonText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Base, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Inactive, QtGui.QPalette.ColorRole.Window, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.WindowText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Button, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Text, brush)
        brush = QtGui.QBrush(QtGui.QColor(170, 0, 255))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.ButtonText, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Base, brush)
        brush = QtGui.QBrush(QtGui.QColor(255, 255, 0))
        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
        palette.setBrush(QtGui.QPalette.ColorGroup.Disabled, QtGui.QPalette.ColorRole.Window, brush)
        self.labelName.setPalette(palette)
        font = QtGui.QFont()
        font.setPointSize(15)
        self.labelName.setFont(font)
        self.labelName.setAutoFillBackground(False)
        self.labelName.setStyleSheet("background-color: rgb(255, 255, 0);\n"
"color: rgb(170, 0, 255);")
        self.labelName.setText("")
        self.labelName.setObjectName("labelName")
        self.pushButtonChangeColor = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonChangeColor.setGeometry(QtCore.QRect(50, 150, 181, 41))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.pushButtonChangeColor.setFont(font)
        self.pushButtonChangeColor.setObjectName("pushButtonChangeColor")
        self.pushButtonExit = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonExit.setGeometry(QtCore.QRect(350, 150, 111, 41))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.pushButtonExit.setFont(font)
        self.pushButtonExit.setObjectName("pushButtonExit")
        self.label_2 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_2.setGeometry(QtCore.QRect(120, 10, 241, 31))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.label_2.setFont(font)
        self.label_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label_2.setObjectName("label_2")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 487, 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.lineEditName.textChanged['QString'].connect(self.labelName.setText) # type: ignore
        self.pushButtonExit.clicked.connect(MainWindow.close) # type: ignore
        self.pushButtonChangeColor.clicked.connect(self.changeBackground) # type: ignore
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        MainWindow.setTabOrder(self.lineEditName, self.pushButtonChangeColor)
        MainWindow.setTabOrder(self.pushButtonChangeColor, self.pushButtonExit)
        self.MainWindow=MainWindow
    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh - Signals & Slots"))
        self.pushButtonChangeColor.setText(_translate("MainWindow", "Change Color"))
        self.pushButtonExit.setText(_translate("MainWindow", "Exit"))
        self.label_2.setText(_translate("MainWindow", "Signals & Slots"))
    def changeBackground(self):
        self.MainWindow.setStyleSheet("background-color: red;")

Cuối cùng ta tạo một file Python “MyApp.py” trong dự án Pycharm để gọi các lệnh chạy giao diện phần mềm:

Mã lệnh của “MyApp.py” tương tự như các bài học trước, ta có chi tiết:

from PyQt6.QtWidgets import QApplication, QMainWindow

from MyMainWindow import Ui_MainWindow

app=QApplication([])

qMainWindow=QMainWindow()

myWindow=Ui_MainWindow()

myWindow.setupUi(qMainWindow)

qMainWindow.show()

app.exec()

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

Ta gõ dữ liệu trong ô QLineEdit tới đâu thì QLabel sẽ tự động cập nhật dữ liệu tới đó. Như vậy QLineEdit (Sender) đã truyền Signal “textChanged” cho QLabel (Receiver), và QLabel dùng Slot setText để nhận dữ liệu.

Tương tự như vậy ta thử nghiệm nhất vào nút QPushButton “Change Color”, lúc này màu nền của MainWindow sẽ chuyển qua màu đỏ:

Cuối cùng là khi người sử dụng nhấn vào nút QPushButton (Sender) “Exit” thì nó sẽ truyền Signal “clicked” tới MainWindow (Receiver) và MainWindow dùng Slot close để nhận tín hiệu, tức là nó thoát phần mềm.

Như vậy Tui đã hướng dẫn xong phần Signals và Slot rất chi tiết, các bạn cố gắng đọc thật kỹ lý thuyết, cũng như cơ chế hoạt động của chúng. Phân biệt đâu là Sender, đâu là Receiver, đâu là Signals, đâu là Slot. Đồng thời phải biết cách dùng Qt Designer để cấu hình các Signals và Slot, ngoài ra phải biết cách định nghĩa hàm trong Python để làm Slot.

Đây là coding đầy đủ của bài này:

https://www.mediafire.com/file/w2xr6oaxkoqud6o/LearnSignalsAndSlots.rar/file

Các bài học sau Tui sẽ trình bày chi tiết về MainWindow cũng như các thuộc tính, Signal, Slot và event của nó. Sau khi nắm chắc MainWindow thì chúng ta sẽ học về Layout để biết cách bố cục giao diện, biết được bố cục giao diện thì ta mới đi chi tiết vào từng Widgets cụ thể được.

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