QT入门第四天:布局管理器详解 | 零基础学QT

QT入门第四天:布局管理器详解 | 零基础学QT

前言

前三天我们学习了环境搭建、信号与槽、常用控件,今天我们来学习一个非常重要的东西------布局管理器(Layout)

还记得昨天我们做注册表单的时候,是用setGeometry(x, y, width, height)一个个手动设置位置的吗?这样写有个大问题:

❌ 窗口大小改变时,控件不会跟着变

❌ 不同分辨率下显示效果不一样

❌ 增加/删除控件时,所有位置都要重新算

❌ 代码写起来很麻烦,位置全靠猜

布局管理器就是来解决这些问题的!它会自动帮你排列控件,让界面更灵活、更专业。

今天我们学习4种最常用的布局:

  • 水平布局(QHBoxLayout):从左到右排
  • 垂直布局(QVBoxLayout):从上到下排
  • 网格布局(QGridLayout):按表格排
  • 表单布局(QFormLayout):两列的表单

一、为什么要用布局管理器

1.1 手动布局的痛点

我们先看一个例子,用setGeometry手动放两个按钮:

cpp 复制代码
QPushButton *btn1 = new QPushButton("按钮1", this);
btn1->setGeometry(50, 50, 100, 30);

QPushButton *btn2 = new QPushButton("按钮2", this);
btn2->setGeometry(170, 50, 100, 30);

看起来没问题,但你试试拉伸窗口...按钮还是在原地,不会跟着变大变小,也不会跟着移动。

1.2 布局管理器的优势

自动适应 :窗口大小改变时,控件自动调整位置和大小

代码简洁 :不用一个个算坐标,告诉布局怎么排就行

易于维护 :增加/删除控件,其他的自动调整位置

跨平台一致:在不同系统、不同分辨率下都能正常显示

💡 一句话:布局管理器就像一个智能的"排列工",你告诉它规则,它帮你排得整整齐齐。

二、水平布局 QHBoxLayout

2.1 什么是水平布局

水平布局就是把控件从左到右排成一行,就像排队一样。

2.2 基本用法

cpp 复制代码
#include <QHBoxLayout>

// 创建水平布局
QHBoxLayout *layout = new QHBoxLayout(this);

// 创建按钮
QPushButton *btn1 = new QPushButton("按钮1");
QPushButton *btn2 = new QPushButton("按钮2");
QPushButton *btn3 = new QPushButton("按钮3");

// 把控件添加到布局中
layout->addWidget(btn1);
layout->addWidget(btn2);
layout->addWidget(btn3);

// 把布局设置到窗口上
this->setLayout(layout);

运行一下,你会看到三个按钮从左到右排成一行,均匀分布。拉伸窗口,按钮也会跟着变宽!

2.3 常用方法

cpp 复制代码
// 添加控件
layout->addWidget(btn);

// 在指定位置插入控件(索引从0开始)
layout->insertWidget(1, newBtn);

// 移除控件(把控件从布局中拿出来,但不删除)
layout->removeWidget(btn);

// 获取布局中控件的数量
int count = layout->count();

// 设置间距(控件之间的距离)
layout->setSpacing(20);

// 设置边距(布局与窗口边缘的距离)
layout->setContentsMargins(10, 10, 10, 10);  // 左、上、右、下

三、垂直布局 QVBoxLayout

3.1 什么是垂直布局

垂直布局就是把控件从上到下排成一列,和水平布局正好垂直。

3.2 基本用法

cpp 复制代码
#include <QVBoxLayout>

// 创建垂直布局
QVBoxLayout *layout = new QVBoxLayout(this);

// 添加控件
layout->addWidget(new QPushButton("按钮1"));
layout->addWidget(new QPushButton("按钮2"));
layout->addWidget(new QPushButton("按钮3"));

this->setLayout(layout);

是不是和水平布局几乎一样?只是把QHBoxLayout换成了QVBoxLayout

3.3 拉伸因子(Stretch)

这是布局管理器中非常实用的功能。拉伸因子决定了控件在多余空间中如何分配。

cpp 复制代码
QVBoxLayout *layout = new QVBoxLayout(this);

QPushButton *btn1 = new QPushButton("按钮1");
QPushButton *btn2 = new QPushButton("按钮2");
QPushButton *btn3 = new QPushButton("按钮3");

// addWidget的第二个参数就是拉伸因子
layout->addWidget(btn1, 1);  // 占1份
layout->addWidget(btn2, 2);  // 占2份
layout->addWidget(btn3, 1);  // 占1份

this->setLayout(layout);

运行一下,你会发现:

  • 按钮2最高,是按钮1和按钮3的2倍
  • 总共有 1+2+1 = 4 份,按钮2占了 2/4 = 1/2 的高度

💡 理解:拉伸因子就像分蛋糕,比例是 1:2:1,总共4份,中间那个拿2份。

如果拉伸因子都是0(默认值),那控件就保持自己的推荐大小,多余的空间空着。

四、网格布局 QGridLayout

4.1 什么是网格布局

网格布局就像Excel表格,把控件放在指定的行和列中。这是最灵活的布局。

4.2 基本用法

cpp 复制代码
#include <QGridLayout>

// 创建网格布局
QGridLayout *layout = new QGridLayout(this);

// 添加控件到指定位置(行, 列)
layout->addWidget(new QPushButton("(0,0)"), 0, 0);
layout->addWidget(new QPushButton("(0,1)"), 0, 1);
layout->addWidget(new QPushButton("(0,2)"), 0, 2);
layout->addWidget(new QPushButton("(1,0)"), 1, 0);
layout->addWidget(new QPushButton("(1,1)"), 1, 1);
layout->addWidget(new QPushButton("(1,2)"), 1, 2);

this->setLayout(layout);

这样就得到了一个 2行3列 的按钮矩阵。

4.3 跨行跨列

控件还可以跨越多行或多列,就像Excel的合并单元格:

cpp 复制代码
QGridLayout *layout = new QGridLayout(this);

// 参数:控件, 起始行, 起始列, 占几行, 占几列
layout->addWidget(new QPushButton("跨2列"), 0, 0, 1, 2);
layout->addWidget(new QPushButton("(0,2)"), 0, 2);
layout->addWidget(new QPushButton("跨2行"), 1, 0, 2, 1);
layout->addWidget(new QPushButton("(1,1)"), 1, 1);
layout->addWidget(new QPushButton("(1,2)"), 1, 2);
layout->addWidget(new QPushButton("(2,1)"), 2, 1);
layout->addWidget(new QPushButton("(2,2)"), 2, 2);

this->setLayout(layout);

4.4 设置行/列的拉伸

cpp 复制代码
// 设置第0列的拉伸因子为1
layout->setColumnStretch(0, 1);

// 设置第1列的拉伸因子为2
layout->setColumnStretch(1, 2);

// 设置第0行的拉伸因子为1
layout->setRowStretch(0, 1);

五、表单布局 QFormLayout

5.1 什么是表单布局

表单布局专门用来做两列表单,左边是标签,右边是输入框。做登录、注册这种界面特别方便。

5.2 基本用法

cpp 复制代码
#include <QFormLayout>

// 创建表单布局
QFormLayout *layout = new QFormLayout(this);

// 添加一行(标签 + 控件)
layout->addRow("用户名:", new QLineEdit());
layout->addRow("密  码:", new QLineEdit());
layout->addRow("邮  箱:", new QLineEdit());

this->setLayout(layout);

是不是特别简洁?三行代码就做出了一个三行的表单!

5.3 更多用法

cpp 复制代码
// 插入一行
layout->insertRow(1, "确认密码:", new QLineEdit());

// 删除一行(移除但不删除控件)
layout->removeRow(0);

// 获取行数
int rows = layout->rowCount();

// 设置标签的对齐方式
layout->setLabelAlignment(Qt::AlignRight);  // 标签右对齐

六、布局嵌套

复杂的界面不可能只用一种布局,我们可以把布局嵌套起来用。

比如:一个窗口,上面是表单,下面是两个按钮(确定和取消)。

cpp 复制代码
#include <QVBoxLayout>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton>

// 1. 最外层:垂直布局
QVBoxLayout *mainLayout = new QVBoxLayout(this);

// 2. 上面:表单布局
QFormLayout *formLayout = new QFormLayout();
formLayout->addRow("用户名:", new QLineEdit());
formLayout->addRow("密  码:", new QLineEdit());

// 3. 下面:水平布局(放两个按钮)
QHBoxLayout *btnLayout = new QHBoxLayout();
btnLayout->addStretch();  // 加一个弹簧,把按钮挤到右边
btnLayout->addWidget(new QPushButton("确定"));
btnLayout->addWidget(new QPushButton("取消"));

// 4. 把两个布局加到主布局中
mainLayout->addLayout(formLayout);    // 添加子布局
mainLayout->addLayout(btnLayout);

this->setLayout(mainLayout);

💡 重点:布局不仅能addWidget加控件,还能addLayout加另一个布局!这样就能组合出各种复杂的界面。

addStretch() 是什么?

addStretch()就是加一个"弹簧",它会把多余的空间都占掉,把旁边的控件挤到一边。

cpp 复制代码
QHBoxLayout *layout = new QHBoxLayout();
layout->addStretch();        // 左边加弹簧
layout->addWidget(btn1);     // 按钮1
layout->addWidget(btn2);     // 按钮2
layout->addStretch();        // 右边加弹簧

这样两个按钮就会居中显示,因为左右两个弹簧把它们挤到中间了。

七、间距和边距

7.1 间距(Spacing)

间距是控件与控件之间的距离。

cpp 复制代码
layout->setSpacing(20);  // 设置间距为20像素

7.2 边距(Margins)

边距是布局与父窗口边缘的距离。

cpp 复制代码
// 分别设置左、上、右、下的边距
layout->setContentsMargins(20, 10, 20, 10);

// 四个边距都设为一样
layout->setContentsMargins(20, 20, 20, 20);

默认的边距有点大,如果你想让内容更紧凑,可以把边距调小一点。

八、综合实战:用布局管理器重做注册表单

学了这么多布局,我们来把昨天的注册表单用布局管理器重做一遍!

8.1 界面分析

我们的注册表单包含:

  • 用户名(标签 + 输入框)
  • 密码(标签 + 输入框)
  • 性别(标签 + 两个单选框水平排列)
  • 学历(标签 + 下拉框)
  • 爱好(标签 + 四个复选框水平排列)
  • 简介(标签 + 文本编辑框)
  • 同意协议(复选框,单独一行)
  • 注册按钮(居中)

8.2 完整代码

创建新的QT Widgets项目,修改mainwindow.cpp

cpp 复制代码
#include "mainwindow.h"
#include <QVBoxLayout>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QRadioButton>
#include <QComboBox>
#include <QCheckBox>
#include <QTextEdit>
#include <QPushButton>
#include <QMessageBox>
#include <QGroupBox>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setWindowTitle("用户注册(布局版)");
    resize(450, 500);

    // 中心部件(QMainWindow必须设置一个中心部件)
    QWidget *centralWidget = new QWidget(this);
    setCentralWidget(centralWidget);

    // ===== 最外层:垂直布局 =====
    QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget);
    mainLayout->setSpacing(15);
    mainLayout->setContentsMargins(30, 30, 30, 30);

    // ===== 标题 =====
    QLabel *titleLabel = new QLabel("用户注册");
    titleLabel->setAlignment(Qt::AlignCenter);
    titleLabel->setStyleSheet("font-size: 24px; font-weight: bold; color: #333;");
    mainLayout->addWidget(titleLabel);

    // ===== 表单部分 =====
    QFormLayout *formLayout = new QFormLayout();
    formLayout->setSpacing(15);
    formLayout->setLabelAlignment(Qt::AlignRight);  // 标签右对齐

    // 用户名
    QLineEdit *userEdit = new QLineEdit();
    userEdit->setPlaceholderText("请输入用户名");
    formLayout->addRow("用户名:", userEdit);

    // 密码
    QLineEdit *pwdEdit = new QLineEdit();
    pwdEdit->setPlaceholderText("请输入密码");
    pwdEdit->setEchoMode(QLineEdit::Password);
    formLayout->addRow("密  码:", pwdEdit);

    // 性别(用水平布局放两个单选框)
    QHBoxLayout *genderLayout = new QHBoxLayout();
    QRadioButton *maleRadio = new QRadioButton("男");
    QRadioButton *femaleRadio = new QRadioButton("女");
    maleRadio->setChecked(true);
    genderLayout->addWidget(maleRadio);
    genderLayout->addWidget(femaleRadio);
    genderLayout->addStretch();  // 加弹簧,靠左对齐
    formLayout->addRow("性  别:", genderLayout);

    // 学历
    QComboBox *eduCombo = new QComboBox();
    eduCombo->addItems(QStringList() << "高中及以下" << "大专" << "本科" << "硕士" << "博士");
    eduCombo->setCurrentIndex(2);
    formLayout->addRow("学  历:", eduCombo);

    // 爱好(用水平布局放四个复选框)
    QHBoxLayout *hobbyLayout = new QHBoxLayout();
    QCheckBox *readCheck = new QCheckBox("阅读");
    QCheckBox *musicCheck = new QCheckBox("音乐");
    QCheckBox *sportCheck = new QCheckBox("运动");
    QCheckBox *travelCheck = new QCheckBox("旅行");
    hobbyLayout->addWidget(readCheck);
    hobbyLayout->addWidget(musicCheck);
    hobbyLayout->addWidget(sportCheck);
    hobbyLayout->addWidget(travelCheck);
    hobbyLayout->addStretch();
    formLayout->addRow("爱  好:", hobbyLayout);

    // 简介
    QTextEdit *descEdit = new QTextEdit();
    descEdit->setPlaceholderText("请简单介绍一下自己...");
    descEdit->setFixedHeight(80);  // 固定高度
    formLayout->addRow("简  介:", descEdit);

    mainLayout->addLayout(formLayout);

    // ===== 同意协议 =====
    QCheckBox *agreeCheck = new QCheckBox("我已阅读并同意用户协议和隐私政策");
    mainLayout->addWidget(agreeCheck);

    // ===== 注册按钮 =====
    QHBoxLayout *btnLayout = new QHBoxLayout();
    QPushButton *registerBtn = new QPushButton("立即注册");
    registerBtn->setFixedHeight(40);
    registerBtn->setStyleSheet(
        "QPushButton {"
        "   background-color: #0099ff;"
        "   color: white;"
        "   font-size: 16px;"
        "   border-radius: 5px;"
        "}"
        "QPushButton:hover {"
        "   background-color: #0088ee;"
        "}"
        );
    btnLayout->addStretch();
    btnLayout->addWidget(registerBtn);
    btnLayout->addStretch();
    mainLayout->addLayout(btnLayout);

    // 加个弹簧,让内容靠上
    mainLayout->addStretch();

    // ===== 注册逻辑(和昨天一样) =====
    connect(registerBtn, &QPushButton::clicked, this, [=](){
        QString username = userEdit->text();
        QString password = pwdEdit->text();
        QString gender = maleRadio->isChecked() ? "男" : "女";
        QString education = eduCombo->currentText();

        QStringList hobbies;
        if (readCheck->isChecked()) hobbies << "阅读";
        if (musicCheck->isChecked()) hobbies << "音乐";
        if (sportCheck->isChecked()) hobbies << "运动";
        if (travelCheck->isChecked()) hobbies << "旅行";

        QString description = descEdit->toPlainText();

        if (username.isEmpty()) {
            QMessageBox::warning(this, "提示", "请输入用户名!");
            return;
        }
        if (password.isEmpty()) {
            QMessageBox::warning(this, "提示", "请输入密码!");
            return;
        }
        if (!agreeCheck->isChecked()) {
            QMessageBox::warning(this, "提示", "请先同意用户协议!");
            return;
        }

        QString info = QString("注册成功!\n\n"
                               "用户名:%1\n"
                               "性别:%2\n"
                               "学历:%3\n"
                               "爱好:%4\n"
                               "简介:%5")
                           .arg(username)
                           .arg(gender)
                           .arg(education)
                           .arg(hobbies.join("、"))
                           .arg(description);

        QMessageBox::information(this, "注册成功", info);
    });
}

8.3 运行效果

Ctrl + R 运行,你会发现:

  • ✅ 界面自动排列得整整齐齐
  • ✅ 拉伸窗口,输入框会自动变宽
  • ✅ 代码更清晰,结构更明确
  • ✅ 以后要加一行,只需要addRow就行

对比昨天手动setGeometry的版本,是不是专业多了?

💡 小提示:QMainWindow比较特殊,它有自己的布局(菜单栏、工具栏、状态栏),所以不能直接给QMainWindow设置布局,要先创建一个中心部件setCentralWidget(),然后给中心部件设置布局。

九、今日总结

今天我们学习了QT的布局管理器,这是做专业界面的必备技能!

四种布局对比

布局 用途 特点
QHBoxLayout 水平排列 从左到右一行
QVBoxLayout 垂直排列 从上到下一列
QGridLayout 网格排列 最灵活,可跨行跨列
QFormLayout 表单布局 两列表单,专门做输入界面

重要概念

  • 拉伸因子(stretch):控制控件在多余空间中的分配比例
  • addStretch():添加弹簧,把控件挤到一边或居中
  • 间距(spacing):控件之间的距离
  • 边距(margins):布局与边缘的距离
  • 布局嵌套:布局里可以再加布局,组合出复杂界面

经验分享

  1. 能用布局就别用setGeometry:除非有特殊需求,否则尽量用布局管理器
  2. 从外到内设计:先想最外层是什么布局,再一层层往里加
  3. 表单优先用QFormLayout:做输入表单,QFormLayout最方便
  4. 善用addStretch:弹簧是个好东西,居中、靠边都靠它
  5. 嵌套不要太深:一般3-4层就够了,太深了不好维护

十、明日预告

明天我们将学习QT中的对话框(QDialog)

做软件的时候,经常需要弹出一些小窗口,比如:

  • 消息提示框
  • 确认对话框
  • 输入对话框
  • 文件选择对话框
  • 颜色选择对话框
  • 字体选择对话框

QT已经帮我们做好了这些常用的对话框,直接用就行!

我们还会学习自定义对话框,做自己的弹窗。


📝 学习建议:布局管理器是QT的重点,一定要多练。

建议练习:

  • 把今天的注册表单代码亲手敲一遍
  • 试试修改拉伸因子,看看效果有什么不同
  • 试试用网格布局重新做一遍注册表单
  • 自己设计一个简单的计算器界面

布局管理器掌握了,做界面就得心应手了!明天见,继续加油!💪