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

Leave a Reply