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):布局与边缘的距离
- ✅ 布局嵌套:布局里可以再加布局,组合出复杂界面
经验分享
- 能用布局就别用setGeometry:除非有特殊需求,否则尽量用布局管理器
- 从外到内设计:先想最外层是什么布局,再一层层往里加
- 表单优先用QFormLayout:做输入表单,QFormLayout最方便
- 善用addStretch:弹簧是个好东西,居中、靠边都靠它
- 嵌套不要太深:一般3-4层就够了,太深了不好维护
十、明日预告
明天我们将学习QT中的对话框(QDialog)。
做软件的时候,经常需要弹出一些小窗口,比如:
- 消息提示框
- 确认对话框
- 输入对话框
- 文件选择对话框
- 颜色选择对话框
- 字体选择对话框
QT已经帮我们做好了这些常用的对话框,直接用就行!
我们还会学习自定义对话框,做自己的弹窗。
📝 学习建议:布局管理器是QT的重点,一定要多练。
建议练习:
- 把今天的注册表单代码亲手敲一遍
- 试试修改拉伸因子,看看效果有什么不同
- 试试用网格布局重新做一遍注册表单
- 自己设计一个简单的计算器界面
布局管理器掌握了,做界面就得心应手了!明天见,继续加油!💪