Bài 3: Ý nghĩa các thành phần trong dự án PyQt6-Qt Designer

Trong bài 1 bài 2 các bạn đã biết được cách tạo dự án với giao diện PyQt6 và công cụ thiết kế giao diện Qt Designer, cũng như biết cách tích hợp chúng vào Pycharm để hỗ trợ việc lập trình xử lý giao diện được nhanh chóng và dễ dàng.

Trong bài này, Tui sẽ giải thích chi tiết từng mã lệnh thành phần của một dự án PyQt6 – Qt Designer cũng như cơ chế hoạt động của event loop.

Bước 1: Tạo một dự án tên “MyFirstApplication” trong Pycharm

Bước 2: Mở phần mềm Qt Designer bằng cách bấm chuột phải vào dự án MyFirstApplication/Chọn “Create new Qt Designer“. Lưu ý bạn phải học qua bài số 2 thì mới thấy các công cụ trong External Tools.

Sau khi chọn Create new Qt Designer, phần mềm này sẽ được kích hoạt, chúng ta chọn MainWindow như hình dưới đây:

Nhấn nút “Create” để tạo màn hình cửa sổ giao diện

Mục 1: Mục này được gọi là Widget Box, nó chứa hầu hết các layout, controls của giao diện, được chia thành 8 nhóm với hơn 50 layout và controls thường sử dụng được cung cấp. Lập trình viên có thể kéo thả các control này vào phần thiết kế giao diện ở mục số 2.

  • Nhóm Layouts: Nhóm này cung cấp cách thức bố trí cấu trúc giao diện, như vậy trước khi thiết kế một giao diện thì chúng ta cần quan sát xem cấu trúc tổng quan của giao diện là gì để có thể lựa chọn và phối hợp các layout này lại với nhau nhằm tạo ra một giao diện như mong muốn. Các Layout được cung cấp gồm “Vertical Layout”, “Horizontal Layout”, “Grid Layout” và “Form Layout”.
  • Nhóm Spacers: Nhóm này dùng để tạo và định nghĩa các vùng trống của các Control, có 2 loại Spacer được cung cấp đó là “Horizontal Spacer” và “Verticla Spacer”
  • Nhóm Buttons: Là nhóm các control liên quan tới các nút lệnh, chẳng hạn như “Push Button”, “Radio Button”, “Checkbox”, “Command Link Button”, “Dialog Button Box” và “Tool Button”
  • Nhóm Item Views (Model-Based): Nhóm này chứa các control hiển thị dữ liệu dạng bảng, dạng cây, các control bao gồm “List View”, “Tree View”, “Table View” và “Column View”
  • Nhóm Item Widget (Item-Based): Nhóm chứa các Widget dạng danh sách, cây, bảng, các widget bao gồm “List Widget”, “Tree Widget”, và “Table Widget”
  • Nhóm Container: Nhóm này được xem là các đối tượng khung chứa để chứa các controls, phân nhóm các control trên giao diện tương tác để cho nó rõ ràng và dễ sử dụng hơn. Các Containers bao gồm “Group Box”
  • Nhóm Input Widgets: Đây là các nhóm Controls mà người sử dụng thường dùng để nhập và hiển dữ liệu, các controls bao gồm “Combo Box”, “Font Combo Box”, “Line Edit”, “Text Edit”, “Plain Text Edit”, “Spin Box”, “Double Spin Box”, Các control liên quan tới ngày tháng năm như “Time Edit”, “Date Edit”, “Date/Time Edit”, và các control khác như Dial, Key Sequence Edit, cùng với các slider và Scroll Bar như “Vertical Slider”, “Horizontal Slider”, “Vertical Scroll Bar” và “Horizontal Scroll Bar”
  • Nhóm Display Widgets: Nhóm này là các controls thường được dùng để hiển thị mà không cho nhập liệu, chẳng hạn như “Label”, “Text Browser”, “Graphics View”, “Calendar Widget”, “LCD Number”, “Progress Bar”, “Horizontal Line”, “Vertical Line” và “Open GL Widget”

Mục 2: Mục 2 là mục thiết kế giao diện chính của phần mềm, các control, layout trong mục 1 sẽ được kéo thả và mục này, các bố cục của giao diện sẽ được hiển thị chi tiết trong mục số 3 Object Inspector. Để thay đổi (định dạng) các control nào thì ta nhấn chuột và control đó rồi chỉnh trong mục 4 Property Editor, để thêm các Signal và Slot thì chỉnh trong mục 5.

Như vậy có thể nói, mục 2 là mục trung tâm của thiết kế giao diện tương tác người dùng. Trong mục 2 cũng cung cấp Context Menu (nhấn chuột phải) để sử dụng thêm các cấu hình khác:

Mục 3: Mục 3 là mục Object Inspector. Mục này hiển thị chi tiết các bố cục của giao diện, các control được hiển thị trong table với 2 cột, cột Object là cột thể hiện các tên biến control, cột Class là thể hiện Object này được tạo ra từ class nào. Dựa vào mô tả sơ lược này mà ta có thể dễ dàng biết được các control thuộc lớp nào để lập trình tương tác cho đúng:

Ví dụ hình ở trên ta thấy Object có tên là “myTitle” thuộc class QLabel.

Và nhìn vào hình trên ta cũng biết được cấu trúc (bố cục) tổng quan của giao diện.

Mục 4: Đây là mục Property Editor, là mục dùng để thiết lập các thuộc tính cho Control trên giao diện, rất quan trọng.

Tùy vào từng đối tượng chúng ta chọn trên giao diện mà trong mục Property Editor này sẽ hiển thị các thông số khác nhau, sau đó ta có thể tùy chỉnh giá trị của các thuộc tính trong này một cách dễ dàng.

Ví dụ minh họa các thuộc tính của màn hình MainWindow:

Ví dụ màn hình các thuộc tính của QLabel:

Mục 5: Là mục gán các Signal/Slot Editor (event) cho các control trên giao diện

Chẳng hạn như muốn gán Signal nhấn nút lệnh cho control thì chúng ta khai báo trong này (kéo thả bổ sung thêm 1 Push Button vào giao diện như dưới đây):

minh họa ở trên: Khi nhấn vào nút “Click Me” thì dữ liệu trên QLabel title sẽ bị xóa trắng.

Mục 6: Mục này là các Menu và tool bars, các lệnh thường dùng sẽ được hiển thị ở đây để lập trình viên lựa chọn một cách nhanh chóng.

Như vậy là các thành phần trong giao diện Qt Designer đã được trình bày ở trên, chi tiết các thành phần này sẽ được giải thích cặn kẽ về lý thuyết cũng như cách thức lập trình ở các bài học tương ứng.

Bây giờ chúng ta lưu giao diện này vào dự án “MyFirstApplication” trong Pycharm để chúng ta tiếp tục giải thích ý nghĩa của các kỹ thuật lập trình đối với giao diện MainWindow này. Đặt tên giao diện là “MyFirstApplication.ui

Ta tiến hành dùng External tool để tạo mã lệnh Python cho giao diện:

Sau khi bấm “Generate Python Code with PyUIC” thì mã lệnh “MyFirstApplication.py” được tạo ra như dưới đây:

Tạm thời trong bài này chúng ta chưa tiến hành hiệu chỉnh “MyFirstApplication.py”, các bài sau chúng ta sẽ tiến hành hiệu chỉnh bằng cách kế thừa từ lớp này để vẫn đảm bảo được cấu trúc tự động Generate Coding và vẫn giữ được các mã lệnh mới bổ sung trong các lớp kế thừa (tránh chỉnh sửa trực tiếp trong lớp Generate này vì nó sẽ bị mất nếu ta Generate lưu đè lên code cũ).

# Form implementation generated from reading ui file 'MyFirstApplication.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(958, 796)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.myTitle = QtWidgets.QLabel(parent=self.centralwidget)
        self.myTitle.setGeometry(QtCore.QRect(70, 10, 421, 81))
        font = QtGui.QFont()
        font.setPointSize(20)
        self.myTitle.setFont(font)
        self.myTitle.setObjectName("myTitle")
        self.clickMe = QtWidgets.QPushButton(parent=self.centralwidget)
        self.clickMe.setGeometry(QtCore.QRect(110, 150, 171, 41))
        self.clickMe.setObjectName("clickMe")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 958, 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.clickMe.clicked.connect(self.myTitle.clear) # type: ignore
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.myTitle.setText(_translate("MainWindow", "https://tranduythanh.com/"))
        self.clickMe.setText(_translate("MainWindow", "Click Me To Clear Title"))

Tiếp theo ta tạo thêm một file Python với tên “MyApp.py“:

Bổ sung mã lệnh cho “MyApp.py“:

from PyQt6.QtWidgets import QApplication, QMainWindow
# import sys for Accessing to command line arguments
import sys

from MyFirstApplication import Ui_MainWindow

# You need one (and only one) QApplication instance per application.
# Pass in sys.argv to allow command line arguments for your app.

app=QApplication(sys.argv)
# If you know you won't use command line arguments QApplication([]) works too
#app=QApplication([])

#Create QMainWindow
qMainWindow=QMainWindow()
#Call Ui_MainWindow in the MyFirstApplication.py, of course we can change the name
myWindow=Ui_MainWindow()
#Call setupUi method
myWindow.setupUi(qMainWindow)
#call show method
qMainWindow.show()
#Start the Event loop
app.exec()

Chạy “MyApp.py” ta có giao diện như mong muốn:

Nếu như chúng ta nhấn nút lệnh “Click Me To Clear Title” thì title sẽ biến mất.

Bây giờ chúng ta khám phá các mã lệnh trong file “MyApp.py“:

from PyQt6.QtWidgets import QApplication, QMainWindow

Trong mã lệnh của chúng ta có dùng các lớp QApplication và QMainWindow nên chúng ta import nó, các lớp này nằm trong gói PyQt6.QtWidgets

Mỗi một phần mềm chúng ta khai báo 1 và chỉ 1 đối tượng QApplication. Đối tượng này có thể không nhận bất kỳ đối số nào hoặc nhận đối số từ command line thông quy sys.argv:

app=QApplication(sys.argv)

Nếu không muốn nhận thông số nào cả (parameter):

app=QApplication([])

Đối tượng này sẽ nắm giữ toàn bộ Event Loops (có thể hiểu là nắm giữ toàn bộ các sự kiện phát sinh xảy ra trên màn hình cửa sổ)

Tiếp theo ta cần khai báo 1 đối tượng QMainWindow()

#Create QMainWindow
qMainWindow=QMainWindow()

Đối tượng qMainWindow sẽ được truyền vào lớp Ui_MainWindow thông qua hàm setupUi để khởi tạo giao diện:

#Call Ui_MainWindow in the MyFirstApplication.py, of course we can change the name
myWindow=Ui_MainWindow()
#Call setupUi method
myWindow.setupUi(qMainWindow)

Lưu ý rằng lớp Ui_MainWindow là do chương trình tự tạo ra và tự đặt tên khi tạo mã lệnh từ giao diện, ta có thể đổi tên tùy ý.

hàm setupUi(qMainWindow) sẽ tiến hành nạp lại giao diện.

sau đó ta gọi hàm show() để hiển thị giao diện:

#call show method
qMainWindow.show()

Nhưng nhớ rằng, nó sẽ tự động tắt màn hình giao diện ngay lập tức thì lúc này ta chưa kích hoạt Event loop. Do đó ta bắt buộc phải gọi lệnh sau:

#Start the Event loop
app.exec()

Vậy Event Loop là gì? nó đóng vai trò gì trong chương trình?

Lõi của Qt Application là lớp QApplication , mỗi một ứng dụng cần một và chỉ một đối tượng QApplication. Đối tượng này nắm giữ và điều khiển các Even loop của ứng dụng (các thao tác người dùng). Mọi thao tác người dùng trên giao diện sẽ được đẩy vào hàng đợi gọi là Event Queue, sau đó các Event này sẽ được xử lý luôn hoặc chuyển tiếp tới các tác vụ khác. Ví dụ như bạn Click click click bạn nhấn nhấn nhấn ….. trên các control hay trên Window thì các sự kiện này sẽ được đưa vào hàng đợi và nó sẽ được xử lý tuần tự, hết event này sẽ tới event khác trong queue. Và tại một thời điểm thì chỉ có một event loop được chạy cho mỗi ứng dụng.

Như vậy tới đây Tui đã trình bày xong Ý nghĩa các thành phần trong dự án PyQt6-Qt Designer. Các bạn đã biết cách tạo giao diện, hiểu được cấu trúc thành phần trên giao diện cũng như ý nghĩa của chúng. Đặc biệt hiểu cách thức gọi mã lệnh để tương tác người dùng, hiểu được tại sao phải dùng các dòng lệnh đó cũng như nắm rõ quy trình hoạt động của Event loop.

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

https://www.mediafire.com/file/52m26det0ho3zh7/MyFirstApplication.rar/file

Bài sau Tui sẽ trình bày chi tiết về MainWindow cũng như nói rõ về cơ chế hoạt động của SignalsSlots trên ứng dụng.

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

One thought on “Bài 3: Ý nghĩa các thành phần trong dự án PyQt6-Qt Designer”

Leave a Reply