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

Bài 9: QGridLayout-Layout Management

Như vậy các bạn đã biết cách bố cục giao diện bằng QVBoxLayout theo phương đứng và QHBoxLayout theo phương ngang, tuy nhiên có nhiều giao diện chúng ta phải bố cục theo dạng dòng và cột (Grid). PyQt6 cung cấp GridLayout để chúng ta thực hiện điều này.

GridLayout chia giao diện thành các dòng và cột, dòng chạy từ trên xuống dưới theo index bắt đầu từ 0, cột chạy từ trái qua phải theo index bắt đầu từ 0.

Giao giữa Dòng và Cột được gọi là Ô. Mỗi Ô như vậy ta có thể để 1 Control/widget vào bên trong, do đó muốn có nhiều control/widget trong một Ô thì ta có thể khai báo thêm 1 Layout, layout này chứa nhiều control/widget.

Trong Qt Designer, ta kéo GridLayout ra giao diện, sau đó kéo các Widget vào GridLayout. Mỗi lần kéo Widget vào layout nó sẽ xuất hiện các đường kẻ màu xanh để cho chúng ta lựa chọn nơi đặt:

Ngoài ra ta có thể trộn nhiều cột, trộn nhiều dòng lại với nhau. Chúng ta chỉ cần kéo Widget chiếm qua các ô khác thì sẽ được trộn dòng hoặc cột tùy hướng chúng ta kéo (ta cần làm các ô mà ta muốn trộn ở trạng thái rỗng không có chứa bất kỳ controls/widgets nào). Ví dụ:

Bây giờ ta trộn 3 cột cho Button có nhãn (0,1), lúc này ta chỉ cần chỉnh size của Button này bằng cách kéo nó qua luôn 3 ô liên tục:

Hay trộn Button (3,1) với 2 cột và 3 dòng:

  • Kéo size Button qua trái chiếm 2 cột:
  • Kéo size Button xuống dưới chiếm 3 dòng:

Tương tự, Nếu muốn trộn 3 dòng cho Button có nhãn (3,4) thì ta kéo size của button này chiếm 3 dòng:

Nếu Widget trong mỗi Ô chiếm không gian chưa hết Ô đó thì ta có thể căn lên các Widget này theo nhiều cách:

(gif: nguồn pythontutorial.net)

Dưới đây là một số thuộc tính liên quan tới căn lề mà chúng ta có thể lập trình:

Alignment FlagÝ nghĩa
AlignAbsoluteNếu hướng từ trái sang phải thì AlignLeft là căn chỉnh với cạnh phải. Tuy nhiên, nếu muốn AlignLeft luôn được căn chỉnh với cạnh trái, thì kết hợp AlignLeft với tùy chọn AlignAbsolute .
AlignBaselineCăn chỉnh widget với đường cơ sở.
AlignBottomCăn chỉnh widget ở bên dưới
AlignCenter
căn chỉnh widget ở giữa
AlignHCenterCăn wiget nằm giữa theo chiều ngang
AlignHorizontal_MaskCăn widget nằm ngang bằng cách kết hợp nhiều loại: AlignLeft | AlignRight | AlignHCenter | AlignJustify | AlignAbsolute
AlignJustifyCăn điều text
AlignLeftCăn widget theo lề trái
AlignRightCăn widget theo lề phải
AlignTopCăn widget theo hướng bên trên
AlignVCenterCăn wiget nằm giữa theo chiều đứng
AlignVertical_MaskCăn widget nằm đứng bằng cách kết hợp nhiều loại: AlignTop | AlignBottom | AlignVCenter | AlignBaseline

Ví dụ 1:

Ứng dụng GridLayout thiết kế màn hình đăng nhập bằng Qt Designer:

Dưới đây là cấu trúc tập tin trong dự án:

“MainWindow.ui” là file giao diện được thiết kế bởi Qt Designer, và “MainWindow.py” là file mã lệnh tạo giao diện bằng cách Generate code Python:

# 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(524, 261)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.gridLayout = QtWidgets.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.pushButtonLogin = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonLogin.setObjectName("pushButtonLogin")
        self.horizontalLayout.addWidget(self.pushButtonLogin)
        self.pushButtonExit = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonExit.setObjectName("pushButtonExit")
        self.horizontalLayout.addWidget(self.pushButtonExit)
        self.gridLayout.addLayout(self.horizontalLayout, 4, 1, 1, 1)
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        self.label.setMinimumSize(QtCore.QSize(0, 48))
        self.label.setMaximumSize(QtCore.QSize(16777215, 50))
        self.label.setStyleSheet("color: rgb(255, 0, 0);\n"
"font: 75 14pt \"MS Shell Dlg 2\";")
        self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label.setObjectName("label")
        self.gridLayout.addWidget(self.label, 0, 0, 1, 2)
        self.lineEditPassword = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditPassword.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
        self.lineEditPassword.setObjectName("lineEditPassword")
        self.gridLayout.addWidget(self.lineEditPassword, 2, 1, 1, 1)
        self.chkSaveInformation = QtWidgets.QCheckBox(parent=self.centralwidget)
        self.chkSaveInformation.setObjectName("chkSaveInformation")
        self.gridLayout.addWidget(self.chkSaveInformation, 3, 1, 1, 1)
        self.lineEditUsername = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditUsername.setObjectName("lineEditUsername")
        self.gridLayout.addWidget(self.lineEditUsername, 1, 1, 1, 1)
        self.label_3 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_3.setObjectName("label_3")
        self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1)
        self.label_2 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_2.setObjectName("label_2")
        self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
        self.verticalLayout.addLayout(self.gridLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 524, 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", "Tran Duy Thanh"))
        self.pushButtonLogin.setText(_translate("MainWindow", "Login"))
        self.pushButtonExit.setText(_translate("MainWindow", "Exit"))
        self.label.setText(_translate("MainWindow", "Login Screen"))
        self.chkSaveInformation.setText(_translate("MainWindow", "Save login information"))
        self.label_3.setText(_translate("MainWindow", "Password:"))
        self.label_2.setText(_translate("MainWindow", "User Name:"))

“MainWindowEx.py” là file mã lệnh được viết kế thừa nhằm xử lý các sự kiện liên quan mà không ảnh hưởng tới giao diện khi có thay đổi trong tương lai:

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 như thường lệ được trình bày trong các bài học trước, ta tạp file “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 có kết quả:

Như vậy ta thấy sau khi tạo GridLayout, việc thêm widget vào layout sẽ làm theo các bước:

Bước 1:

Tạo đối tượng QGridLayout:

layout = QGridLayout()

Bước 2:

Thiết lập layout cho parent:

parent.setLayout(layout)

Bước 3:

Gán các Widget/Control vào trong layout:

layout.addWidget(widget, row, column, rowSpan, columnSpan, alignment)
  • widget là các control/widget mà ta muốn sắp xếp trên layout QGridLayout
  • row là vị trí dòng mà ta muốn gán wiget/control trong QGridLayout, tính theo index từ 0
  • column là vị trí cột mà ta muốn gán wiget/control trong QGridLayout, tính theo index từ 0.
  • rowSpan là số dòng mà ta muốn widget sẽ chiếm trên giao diện.
  • columnSpan là số cột mà ta muốn widget sẽ chiếm trên giao diện.
  • alignment là cách căn lề widget trong ô trên QGridLayout

Code của ví dụ 1 các bạn có thể tải ở đây:

https://www.mediafire.com/file/8ky1vqmb9vrgf8w/LearnQGridLayout.rar/file

Ví dụ 2: Caro game

Tiếp theo Tui sẽ minh họa cách dùng QGridLayout để vẽ giao diện màn hình chơi Caro lúc runtime, đồng thời hướng dẫn cách xử lý gán sự kiện cho các Button bằng signal lúc runtime để biết được Button nào đang thực hiện:

Tạo một dự án tên “Carogame” có cấu trúc như dưới đây:

“MainWindow.ui” được thiết kế bởi Qt Designer có giao diện như sau:

Giao diện trên ta tiết lập QVBoxLayout cho màn hình chính, các ô nhập liệu và button “Draw Caro” nằm trong QHBoxLayout. Cuối cùng ở bên dưới ta có 1 ScrollArea, ScrollArea này chứa QGridLayout để vẽ bàn cờ Caro.

“MainWindow.py” khi generate python code của giao diện:

# Form implementation generated from reading ui file 'MainWindow.ui'
#
# Created by: PyQt6 UI code generator 6.4.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt6 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(758, 722)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetMinimumSize)
        self.horizontalLayout.setContentsMargins(-1, -1, -1, 0)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        self.label.setObjectName("label")
        self.horizontalLayout.addWidget(self.label)
        self.lineEditRows = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditRows.setObjectName("lineEditRows")
        self.horizontalLayout.addWidget(self.lineEditRows)
        self.label_2 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_2.setObjectName("label_2")
        self.horizontalLayout.addWidget(self.label_2)
        self.lineEditColumn = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditColumn.setObjectName("lineEditColumn")
        self.horizontalLayout.addWidget(self.lineEditColumn)
        self.pushButtonDrawCaro = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonDrawCaro.setObjectName("pushButtonDrawCaro")
        self.horizontalLayout.addWidget(self.pushButtonDrawCaro)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.verticalLayout_2.addLayout(self.verticalLayout)
        self.scrollArea = QtWidgets.QScrollArea(parent=self.centralwidget)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        self.scrollAreaWidgetContents_2 = QtWidgets.QWidget()
        self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 734, 608))
        self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2")
        self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents_2)
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.gridLayoutCaro = QtWidgets.QGridLayout()
        self.gridLayoutCaro.setSpacing(0)
        self.gridLayoutCaro.setObjectName("gridLayoutCaro")
        self.verticalLayout_3.addLayout(self.gridLayoutCaro)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents_2)
        self.verticalLayout_2.addWidget(self.scrollArea)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 758, 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", "MainWindow"))
        self.label.setText(_translate("MainWindow", "Number Of Rows:"))
        self.lineEditRows.setText(_translate("MainWindow", "50"))
        self.label_2.setText(_translate("MainWindow", "Number Of Columns:"))
        self.lineEditColumn.setText(_translate("MainWindow", "50"))
        self.pushButtonDrawCaro.setText(_translate("MainWindow", "Draw Caro"))

Tương tự như các bài trước, ta tạo “MainWindowEx.py” kế thừa từ lớp generate giao diện để xử lý các sự kiện cũng như không bị ảnh hưởng mã lệnh khi tương lai Giao diện có sự thay đổi:

from functools import partial

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QPushButton

from MainWindow import Ui_MainWindow


class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        self.previous=''
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.pushButtonDrawCaro.clicked.connect(self.processDrawCaro)
    def processDrawCaro(self):
        row=int(self.lineEditRows.text())
        column=int(self.lineEditColumn.text())
        for i in  range(row):
            self.gridLayoutCaro.setRowStretch(i,1)
            for j in range(column):
                self.gridLayoutCaro.setColumnStretch(j,1)
                btn= QPushButton()
                btn.setFixedWidth(50)
                btn.setFixedHeight(50)
                btn.sizePolicy().setHorizontalStretch(1)
                btn.sizePolicy().setVerticalStretch(1)
                self.gridLayoutCaro.addWidget(btn, i, j,
                         alignment=Qt.AlignmentFlag.AlignVertical_Mask|Qt.AlignmentFlag.AlignHorizontal_Mask)
                btn.clicked.connect(partial(self.processClicked, btn))
    def processClicked(self,btn):
        if len(btn.text()) > 0:
            return
        if self.previous=="X":
            self.previous="O"
        else:
            self.previous = "X"
        if len(btn.text())==0:
            btn.setText(self.previous)
        else:
            btn.setText(self.previous)
    def show(self):
        self.MainWindow.show()

Ta quan sát slot “processDrawCaro” để vẽ các Button lúc runtime.

trong dòng 30 ta học thêm kỹ thuật mới đó là “partial” có 2 đối số: đối số thứ nhất là slot xử lý sự kiện khi Button được nhấn, đối số thứ 2 ta truyền đối tượng button vào để quá trình xử lý ta biết được chính xác Button nào được nhấn.

Slot “processClicked” sẽ có 2 đối số, đối số thứ 2 là biến btn , biến này chính là PushButton được nhấn, dựa vào Button này ta sẽ xử lý chính xác Button nào đang được nhấn.

Ngoài ra coding còn bổ sung biến previous, biến này đánh dấu là trước đó ‘X’ hay ‘O’ đã được chọn.

Cuối cùng ta 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 chương trình lên ta có giao diện:

Ta nhấn nút “Draw Caro”, chương trình sẽ vẽ:

Coding đầy đủ của bài “Caro Game” này các bạn tải ở đây:

https://www.mediafire.com/file/ilcxryavbdk6teq/Carogame.rar/file

Như vậy Tui đã trình bày xong cách sử dụng QGridLayout, với 2 ví dụ quan trọng: thiết kế giao diện lúc Design Time và thiết kế giao diện lúc Runtime thông qua bài vẽ bàn cơ caro. Khi thiết kế màn hình với dạng dòng và cột thì các bạn có thể liên tưởng tới cách sử dụng QGridLayout.

Ngoài ra trong các ví dụ ta cũng thấy được sự kết hợp của nhiều loại layout khác nhau, cũng như cách dùng ScrollArea để thiết kế các màn hình mà có nội dung vượt quá khả năng lưu trữ của màn hình.

Bài học sau Tui sẽ hướng dẫn QFormlayout để sử dụng trong việc thiết kế giao diện có dạng nhập liệu (data-entry) form.

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

Bài 8: QHBoxLayout-Layout Management

Trong bài 7 Chúng ta đã biết cách dùng QVBoxLayout để sắp xếp các controls/widgets theo phương đứng. Trong bài này Tui sẽ trình bày về QHBoxLayout để bố cục các controls/widgets theo phương ngang. Cũng như cách thức phối hợp QVBoxLayout và QHBoxLayout để thiết kế màn hình giải phương trình bậc 1.

Tạo một dự án tên là “LearnQHBoxLayout” trong Pycharm, sau đó dùng Qt Designer để thiết kế màn hình theo các bước như dưới đây:

Bấm chuột phải vào dự án ==> chọn External Tools==> Chọn Create new Qt Designer.

Ta kéo thả “Horizontal Layout” vào giao diện của MainWindow

Tiếp tục kéo thả thêm 3 Button như hình dưới đây:

Quan sát thấy 3 Button sẽ được sắp xếp theo phương nằm ngang

Tương tự như QVBoxLayout, ta có thể căn Trái, Căn Phải, Căn Giữa các Controls/Widgets trong layout bằng cách dùng Spacer, cụ thể là dùng Horizontal Spacer.

Để căn trái ta kéo Horizontal Spacer vào layout như hình dưới đây:

Nếu kéo Horizontal Spacer như trên thì các Controls/Widgets sẽ được căn trái trong Layout, tương tự như chức năng Dock Left.

Để căn phải ta kéo Horizontal Spacer vào layout như hình dưới đây:

Ta lưu ý khi nhấn Spacer hay bất kỳ 1 Widget nào vào layout thì sau khi kéo Widget vào ta khoan hãy nhả chuột ra, mà cần rê rê chuột tới vị trí ta muốn thả, khi nào có đường kẻ màu xanh xuất hiện thì ta nhả chuột ra thì nó sẽ được bố cục chính xác nơi mà ta mong muốn:

Để căn giữa layout ta kéo 2 Spacer nằm ở 2 bên của Layout thì lúc này các Widgets sự được căn giữa layout.

Để thăm các Spacer giữa các Widgets trong một layout ta chỉ việc kéo Spacer đó chèn vào giữa các Widgets mà ta muốn bố cục. Ví dụ:

Lưu ý là muốn Spacer nằm ở đâu thì lúc kéo vào giao diện ta rê rê chuột, khi nào thanh màu xanh xuất hiện thì ta nhả chuột ra:

Tương tự như QVBoxLayout, thì layout này cũng hỗ trợ các Margin để ta định vị thêm các khoảng cách tương đối của các Widgets nằm bên trong Layout so với nó. Khoảng cách này ta gọi là Margin:

Ví dụ như sau khi chỉnh sửa Margin ta có:

Ở giao diện trên ta chỉnh:

  • layoutLeftMargin =85
  • layoutTopMargin=92
  • layoutRightMargin=100
  • layoutBottomMargin=90

Bây giờ ta thử kết hợp QVBoxLayout và QHBoxLayout để thiết kế giao diện màn hình giải phương trình bậc 1:

Bước 1:

Tiếp tục bấm chuột phải vào dự án “LearnQHBoxLayout” ==> Chọn External Tools==>Chọn Create new Qt Designer.

Chọn QMainWindow để thiết kế giao diện, Lưu giao diện lại với tên “MainWindowFirstDegreeEquation.ui

Bước 2:

Kéo Vertical Layout vào giao diện

Tiếp tục hiệu chỉnh layout của MainWindow qua Lay out Vertically bằng cách bấm chuột phải vào vùng trống bất kỳ của MainWindow==> Chọn “Lay out” ==> Chọn “Lay Out Vertically“:

Lúc này giao diện sẽ tự động bố cục như dưới đây:

Bước 3:

Kéo thêm một Label vào giao QVBoxLayout và đặt nhãn là “First Degree Equation“, chỉnh Font Size=15 và Bold=True trong cửa sổ Property:

Bước 4:

Kéo thêm một QHBoxLayout vào bên trong QVBoxLayout và cho nó ở bên dưới “First Degree Equation”, layout này để hiển thị nhãn Hệ số A và ô nhập liệu Hệ số A:

Kéo Label và LineEdit vào QHBoxLayout ở trên để làm ô nhập liệu cho hệ số a:

Bước 5:

Tương tự như vậy, ta lặp lại bước 4 để làm cho hệ số b.

Kéo thêm một QHBoxLayout vào bên trong QVBoxLayout và cho nó ở bên dưới QHBoxLayout hệ số a:

Kéo Label và LineEdit vào QHBoxLayout ở trên để làm ô nhập liệu cho hệ số b:

Bước 6:

Tương tự như vậy, ta lặp lại bước 5 để thêm QHBoxLayout kết quả:

Kéo thêm một QHBoxLayout vào bên trong QVBoxLayout và cho nó ở bên dưới QHBoxLayout Hệ số b:

Kéo Label và LineEdit vào QHBoxLayout ở trên để làm ô hiển thị kết quả giải phương trình:

Bước 7:

Tương tự như vậy, ta lặp lại bước 6 để thêm 3 Button “Solution” (giải phương trình), “Clear” (xóa dữ liệu trên giao diện để nhập phương trình mới), “Exit” (Thoát phần mềm).

Kéo thêm một QHBoxLayout vào bên trong QVBoxLayout và cho nó ở bên dưới QHBoxLayout Kết quả:

Tiếp tục lần lượt kéo 3 Button vào QHBoxLayout ở trên:

Trong bài này chúng ta chưa tiến hành giải phương trình, mà chúng ta chỉ dừng lại ở việc thiết kế giao diện thông qua sự kết hợp giữa QHBoxLayout và QVBoxLayout. Các bạn có thể bổ sung thêm các Spacer, hay hiệu chỉnh lại font chữ của các Widget cho phù hợp theo ý mình.

Các bạn tiến hành đặt tên cho các control/widgets tương ứng với các nhãn (Bấm chuột phải vào Widget/chọn change Object Name.., Hoặc trong Property sửa object Name). Chi tiết các control này sẽ được học trong phần Basic Widgets.

Sau khi cập nhập giao diện thì nhấn nút lưu để đảm bảo giao diện này được cập nhật mới nhất vì nó đồng bộ qua dự án bên Pycharm.

Bước 8:

Generate code giao diện “MainWindowFirstDegreeEquation.ui” bằng cách bấm chuột phải vào nó ==> Chọn “External Tools” ==> Chọn “Generate Python Code with PyUIC”:

Lúc này file mã nguồn của giao diện sẽ tự động được tạo ra “MainWindowFirstDegreeEquation.py”:

# Form implementation generated from reading ui file 'MainWindowFirstDegreeEquation.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(500, 235)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        font = QtGui.QFont()
        font.setPointSize(15)
        font.setBold(True)
        font.setWeight(75)
        self.label.setFont(font)
        self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label.setObjectName("label")
        self.verticalLayout.addWidget(self.label)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label_2 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_2.setObjectName("label_2")
        self.horizontalLayout.addWidget(self.label_2)
        self.lineEditCoefficientA = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditCoefficientA.setObjectName("lineEditCoefficientA")
        self.horizontalLayout.addWidget(self.lineEditCoefficientA)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        self.label_3 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_3.setObjectName("label_3")
        self.horizontalLayout_3.addWidget(self.label_3)
        self.lineEditCoefficientB = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditCoefficientB.setObjectName("lineEditCoefficientB")
        self.horizontalLayout_3.addWidget(self.lineEditCoefficientB)
        self.verticalLayout.addLayout(self.horizontalLayout_3)
        self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_4.setObjectName("horizontalLayout_4")
        self.label_4 = QtWidgets.QLabel(parent=self.centralwidget)
        self.label_4.setMinimumSize(QtCore.QSize(0, 0))
        self.label_4.setObjectName("label_4")
        self.horizontalLayout_4.addWidget(self.label_4)
        self.lineEditsolution = QtWidgets.QLineEdit(parent=self.centralwidget)
        self.lineEditsolution.setObjectName("lineEditsolution")
        self.horizontalLayout_4.addWidget(self.lineEditsolution)
        self.verticalLayout.addLayout(self.horizontalLayout_4)
        self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_6.setObjectName("horizontalLayout_6")
        self.pushButtonSolution = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonSolution.setObjectName("pushButtonSolution")
        self.horizontalLayout_6.addWidget(self.pushButtonSolution)
        self.pushButtonClear = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonClear.setObjectName("pushButtonClear")
        self.horizontalLayout_6.addWidget(self.pushButtonClear)
        self.pushButtonExit = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButtonExit.setObjectName("pushButtonExit")
        self.horizontalLayout_6.addWidget(self.pushButtonExit)
        self.verticalLayout.addLayout(self.horizontalLayout_6)
        self.verticalLayout_2.addLayout(self.verticalLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 500, 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", "MainWindow"))
        self.label.setText(_translate("MainWindow", "First Degree Equation"))
        self.label_2.setText(_translate("MainWindow", "Coefficient a:"))
        self.label_3.setText(_translate("MainWindow", "Coefficient b:"))
        self.label_4.setText(_translate("MainWindow", "The Solution:"))
        self.pushButtonSolution.setText(_translate("MainWindow", "Solution"))
        self.pushButtonClear.setText(_translate("MainWindow", "Clear"))
        self.pushButtonExit.setText(_translate("MainWindow", "Exit"))

Tạo một lớp “MainWindowFirstDegreeEquationEx.py” kế thừa từ “MainWindowFirstDegreeEquation.py”

from MainWindowFirstDegreeEquation import Ui_MainWindow


class MainWindowFirstDegreeEquationEX(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.MainWindow.setWindowTitle("Tran Duy Thanh - Demo Layout Management")
    def show(self):
        self.MainWindow.show()

Tiếp tục tạo một lớp “MyApp.py”

from PyQt6.QtWidgets import QApplication, QMainWindow

from MainWindowFirstDegreeEquatioEx import MainWindowFirstDegreeEquationEX

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

Cấu trúc dự án đầy đủ:

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

Như vậy giao diện đã chạy đúng như mong muốn thiết kế của chúng ta

Bước 9:

Bây giờ chúng ta tiếp tục bổ sung mã lệnh cho “MainWindowFirstDegreeEquatioEx.py” để xử lý các chức năng cho các Button “Solution”, “Clear” và “Exit”:

from MainWindowFirstDegreeEquation import Ui_MainWindow


class MainWindowFirstDegreeEquationEX(Ui_MainWindow):
    def __init__(self):
        pass
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.MainWindow.setWindowTitle("Tran Duy Thanh - Demo Layout Management")
        self.pushButtonExit.clicked.connect(self.process_exit)
        self.pushButtonClear.clicked.connect(self.process_clear)
        self.pushButtonSolution.clicked.connect(self.process_solution)
    def process_solution(self):
        a = float(self.lineEditCoefficientA.text())
        b = float(self.lineEditCoefficientB.text())
        if a==0 and b ==0:
            self.lineEditsolution.setText("Infinities solutions")
        elif a==0 and b!=0:
            self.lineEditsolution.setText("No solution")
        else:
            self.lineEditsolution.setText("X="+str(-b/a))
    def process_clear(self):
        self.lineEditCoefficientA.setText("")
        self.lineEditCoefficientB.setText("")
        self.lineEditsolution.setText("")
        self.lineEditCoefficientA.setFocus()
    def process_exit(self):
        self.MainWindow.close()
    def show(self):
        self.MainWindow.show()

Chạy “MyApp.py” ta thử nghiệm một số phương trình và có các kết quả như hình dưới đây:

  • Thử nghiệm với phương trình vô số nghiệm
  • Thử nghiệm với phương trình vô nghiệm
  • Thử nghiệm với phương trình có nghiệm

Như vậy tới đây Tui đã hướng dẫn đầy đủ và chi tiết cách thức sử dụng QHBoxLayout cũng như cách phối hợp với QVBoxLayout để thiết kế giao diện, cùng với việc sử dụng một số Widget cơ bản như QLineEdit, QPushButton, QLabel để thiết kế giao diện giải phương trình bậc 1. Đồng thời cũng ôn tập lại cách thức khai báo và sử dụng Signal với Slot.

Dựa vào ví dụ ở trên để các bạn có thể thiết kế các màn hình giao diện tương tự, ví dụ như màn hình Giải phương trình bậc 2, hay các màn hình tính toán số học, máy tính bỏ túi…

Giao diện và mã lệnh của dự án này các bạn tải ở đây:

https://www.mediafire.com/file/n68gedyqrejy8hr/LearnQHBoxLayout.rar/file

Bài học sau Tui sẽ hướng dẫn chi tiết về cách sử dụng QGridLayout

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

Bài 7: QVBoxLayout-Layout Management

PyQt cũng tương tự như các nền tảng khác, nó cung cấp một số layout management để giúp ta có thể thêm nhiều control/Widget và bố cục giao diện một cách dễ dàng hơn. Trong các bài học này Tui sẽ trình bày về:

  • QVBoxLayout (sắp xếp các control/Widget theo chiều đứng)
  • QHBoxLayout(sắp xếp các control/Widget theo chiều ngang)
  • QGridLayout (sắp xếp các control/Widget theo dòng và cột)
  • QFormLayout (sắp xếp các control/Widget theo dạng Data-Entry)

Trước tiên ta làm quen với QVBoxLayout . Tạo một dự án tên “LearnQVBoxLayout” trong Pycharm, sau đó dùng QT Designer để thiết kế giao diện đặt tên file là MainWindow.ui , sau đó sử dụng QVBoxLayout:

Cách tạo dự án và cách sử dụng QT Designer đã được hướng dẫn chi tiết ở các bài trước, nên bài này Tui không nói lại:

Trong mục Layouts, ta kéo “Vertical Layout” vào màn hình thiết kế.

Ngoài ra, với QMainWindow, nếu bạn muốn thiết lập layout cho nó thì nhấn chuột phải vào vị trí trống bất kỳ trên giao diện -> sau đó chọn “Lay out”-> rồi chọn các loại Layout mà mình muốn bố cục, ví dụ này Tui chọn “Layout vertically”

Lúc này QVBoxLayout mà bạn kéo vào nó có hình dạng như dưới đây:

Và nếu bạn muốn bỏ thiết lập layout thì ta Bấm chuột phải vào Layout đó rồi chọn “Layout out” –> chọn “Break Layout“:

Bây giờ trong QVBoxLayout mà bạn kéo thả ở trên, ta kéo thêm 4 Buttons vào bên trong:

Ta quan sát thấy, các Control khi kéo vào nó sẽ sắp xếp theo phương đứng như trên.

Chúng ta có thể căn lề các Controls/Widgets bằng cách dùng Spacer, ví dụ muốn căn lề Bottom (muốn các Controls/widgets chỉ nằm ở phía bên dưới của Layout) ta kéo Vertical Spacer như sau:

Bạn có thể Generate Python code (MainWindow.py) để xem cách thức PyQt6 tạo giao diện:

# 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(397, 284)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
        self.verticalLayout.addItem(spacerItem)
        self.pushButton_2 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_2.setObjectName("pushButton_2")
        self.verticalLayout.addWidget(self.pushButton_2)
        self.pushButton_3 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_3.setObjectName("pushButton_3")
        self.verticalLayout.addWidget(self.pushButton_3)
        self.pushButton_4 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_4.setObjectName("pushButton_4")
        self.verticalLayout.addWidget(self.pushButton_4)
        self.pushButton_5 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_5.setObjectName("pushButton_5")
        self.verticalLayout.addWidget(self.pushButton_5)
        self.verticalLayout_2.addLayout(self.verticalLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 397, 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.pushButton_2.setText(_translate("MainWindow", "PushButton 1"))
        self.pushButton_3.setText(_translate("MainWindow", "PushButton 2"))
        self.pushButton_4.setText(_translate("MainWindow", "PushButton 3"))
        self.pushButton_5.setText(_translate("MainWindow", "PushButton 4"))

Ta thấy khi Generate code, dòng 22, 23 là các mã lệnh để thêm Spacer vào Layout.

Muốn căn lề Top (Muốn các Controls/Widgets nằm ở phía bên trên layout), ta kéo (di chuyển) Vertical Spacer như sau:

Bạn có thể hiểu nó giống như chức năng DOCK, gắn cố định các control ở một góc nào đó trên giao diện.

Bạn có thể Generate Python code (MainWindow.py) để xem cách thức PyQt6 tạo giao diện có liên quan tới căn lề Top ở trên:

# 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(397, 284)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.pushButton_2 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_2.setObjectName("pushButton_2")
        self.verticalLayout.addWidget(self.pushButton_2)
        self.pushButton_3 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_3.setObjectName("pushButton_3")
        self.verticalLayout.addWidget(self.pushButton_3)
        self.pushButton_4 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_4.setObjectName("pushButton_4")
        self.verticalLayout.addWidget(self.pushButton_4)
        self.pushButton_5 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_5.setObjectName("pushButton_5")
        self.verticalLayout.addWidget(self.pushButton_5)
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
        self.verticalLayout.addItem(spacerItem)
        self.verticalLayout_2.addLayout(self.verticalLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 397, 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.pushButton_2.setText(_translate("MainWindow", "PushButton 1"))
        self.pushButton_3.setText(_translate("MainWindow", "PushButton 2"))
        self.pushButton_4.setText(_translate("MainWindow", "PushButton 3"))
        self.pushButton_5.setText(_translate("MainWindow", "PushButton 4"))

Ta thấy khi Generate code, dòng 34, 35 là các mã lệnh để thêm Spacer vào Layout.

Muốn Căn lề giữa, ta kéo 2 Verticle Spacer:

Bạn có thể Generate Python code (MainWindow.py) để xem cách thức PyQt6 tạo giao diện có liên quan tới căn lề Giữa ở trên:

# 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(397, 284)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
        self.verticalLayout.addItem(spacerItem)
        self.pushButton_2 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_2.setObjectName("pushButton_2")
        self.verticalLayout.addWidget(self.pushButton_2)
        self.pushButton_3 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_3.setObjectName("pushButton_3")
        self.verticalLayout.addWidget(self.pushButton_3)
        self.pushButton_4 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_4.setObjectName("pushButton_4")
        self.verticalLayout.addWidget(self.pushButton_4)
        self.pushButton_5 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_5.setObjectName("pushButton_5")
        self.verticalLayout.addWidget(self.pushButton_5)
        spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
        self.verticalLayout.addItem(spacerItem1)
        self.verticalLayout_2.addLayout(self.verticalLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 397, 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.pushButton_2.setText(_translate("MainWindow", "PushButton 1"))
        self.pushButton_3.setText(_translate("MainWindow", "PushButton 2"))
        self.pushButton_4.setText(_translate("MainWindow", "PushButton 3"))
        self.pushButton_5.setText(_translate("MainWindow", "PushButton 4"))

Ta thấy khi Generate code, dòng 22,23 và 36, 37 là các mã lệnh để thêm Spacer vào Layout để căn lề giữa.

Ngoài ra ta có thể thêm các Spacer giữa các Control/Widgets với nhau, ví dụ:

Bạn có thể Generate Python code (MainWindow.py) để xem cách thức PyQt6 tạo giao diện có liên quan tới căn lề Giữa ở trên:

# 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(397, 284)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.verticalLayout = QtWidgets.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
        self.verticalLayout.addItem(spacerItem)
        self.pushButton_2 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_2.setObjectName("pushButton_2")
        self.verticalLayout.addWidget(self.pushButton_2)
        self.pushButton_3 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_3.setObjectName("pushButton_3")
        self.verticalLayout.addWidget(self.pushButton_3)
        self.pushButton_4 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_4.setObjectName("pushButton_4")
        self.verticalLayout.addWidget(self.pushButton_4)
        spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
        self.verticalLayout.addItem(spacerItem1)
        self.pushButton_5 = QtWidgets.QPushButton(parent=self.centralwidget)
        self.pushButton_5.setObjectName("pushButton_5")
        self.verticalLayout.addWidget(self.pushButton_5)
        spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
        self.verticalLayout.addItem(spacerItem2)
        self.verticalLayout_2.addLayout(self.verticalLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 397, 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.pushButton_2.setText(_translate("MainWindow", "PushButton 1"))
        self.pushButton_3.setText(_translate("MainWindow", "PushButton 2"))
        self.pushButton_4.setText(_translate("MainWindow", "PushButton 3"))
        self.pushButton_5.setText(_translate("MainWindow", "PushButton 4"))

Ta thấy khi Generate code, dòng 22,23 và 33, 34 và 38,39 là các mã lệnh để thêm Spacer vào Layout để căn lề cho các Controls/Widgets.

Để thử nghiệm giao diện thì ta vào menu Form/ Chọn Preview:

Qt Designer sẽ hiển thị giao diện cho ta thử nghiệm, nó sẽ chạy đúng như vậy khi ta nạp vào mã lệnh Python trong Pycharm hoặc các tool desktop application khác.

Dưới đây là mã lệnh Python:

  • MainWindow.ui –> là file giao diện được tạo ra từ Qt Designer
  • MainWindow.py –> là file Python được Generate từ PyUIC (đã cung cấp ở bên trên)
  • MainWindowEx.py–> là file Python mà ta sẽ viết 1 lớp kế thừa từ MainWindow.py (UI_MainWindow), mục đích tách ra lớp kế thừa để bất cứ khi nào ta đổi giao diện và generate lại python thì không bị ảnh hưởng tới các mã lệnh mà ta đã viết trước đó.
  • MyApp.py –> file python khai báo để chạy dự án này

Dưới đây là mã lệnh củ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()

Như vậy, khi ta muốn bổ sung thêm bất kỳ mã lệnh nào để xử lý cho giao diện này thì chỉ nên viết trong “MainWindowEx.py“. Không nên viết trong file “MainWindow.py”, vì nếu viết trong “MainWindow.py” thì khi có nhu cầu thay đổi giao diện và generate lại code python sẽ làm mất các mã lệnh mới của mình trước đó.

Còn đây là mã lệnh của MyApp.py:

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” ta có kết quả như mong muốn.

QVBoxLayout còn nhiều thuộc tính và các tính năng khác, tuy nhiên chúng ta chỉ cần nắm tới đây là đủ để bố trí giao diện nằm đứng theo ý mình rồi.

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

https://www.mediafire.com/file/ma2kylupmdh175c/LearnQVBoxLayout.rar/file

Bài sau Tui sẽ trình bày về cách sử dụng QHBoxLayout để bố cục các control/widgets theo phương nằm ngang.

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