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

Leave a Reply