26、Qt布局管理:QHBoxLayout、QVBoxLayout、QGridLayout、QFormLayout、嵌套布局
布局管理,说白了就是让控件在窗口里「乖乖站好」。
我刚学Qt那会儿,觉得布局这东西可有可无。直接把按钮拖到窗口上,调调位置,不也挺好?直到有一次,我做了个登录界面,在1920×1080的屏幕上完美无瑕,结果放到客户的1366×768笔记本上……按钮飞到了屏幕外面,文本框叠在了一起。嗯,从那以后,我再也不敢手写坐标了。
Qt的布局系统,就是帮你自动处理这些尺寸变化的。你只需要告诉它「这几个按钮水平排列」、「那几行垂直堆叠」,剩下的交给布局器。窗口拉伸?字体变大?它自己会算。
核心思想:布局管理器负责子控件的尺寸和位置。你不需要手动调用 resize() 或 move(),布局器会自动计算。
26.1 水平布局与垂直布局:QHBoxLayout 和 QVBoxLayout
这两个是最基础的布局器。一个横着排,一个竖着排。
QHBoxLayout:控件从左到右依次排列。
QVBoxLayout:控件从上到下依次排列。
我个人的习惯是:按钮栏用水平布局,表单行用水平布局,整个窗口用垂直布局包起来。你想想看,大部分界面不就是「上面标题、中间内容、下面按钮」这种结构吗?
// 水平布局示例
QHBoxLayout *hLayout = new QHBoxLayout;
hLayout->addWidget(button1);
hLayout->addWidget(button2);
hLayout->addWidget(button3);
// 垂直布局示例
QVBoxLayout *vLayout = new QVBoxLayout;
vLayout->addWidget(label);
vLayout->addWidget(lineEdit);
vLayout->addLayout(hLayout); // 布局可以嵌套布局
// 设置到窗口
QWidget *window = new QWidget;
window->setLayout(vLayout);
小技巧:布局器默认会平均分配空间。如果你想让某个按钮「撑满」或者「固定大小」,可以用 addStretch() 添加弹簧,或者设置控件的 sizePolicy。
26.2 网格布局:QGridLayout
网格布局,就是把窗口划分成行和列。控件可以跨行、跨列。
我在项目中做过一个计算器界面,数字按钮用网格布局简直不要太爽。0-9、加减乘除、等号,每个按钮放在对应的格子,跨两列的「0」按钮也轻松搞定。
QGridLayout *grid = new QGridLayout;
// 参数:控件,行,列,占几行,占几列
grid->addWidget(btn7, 0, 0); // 第0行第0列
grid->addWidget(btn8, 0, 1); // 第0行第1列
grid->addWidget(btn9, 0, 2); // 第0行第2列
grid->addWidget(btnDiv, 0, 3); // 第0行第3列
grid->addWidget(btn4, 1, 0);
grid->addWidget(btn5, 1, 1);
grid->addWidget(btn6, 1, 2);
grid->addWidget(btnMul, 1, 3);
// 跨两列的按钮
grid->addWidget(btn0, 3, 0, 1, 2); // 第3行,第0列,占1行,占2列
注意行号和列号都是从0开始的。跨行跨列时,后面两个参数如果不写,默认是1行1列。
我曾经踩过的坑:网格布局的行高和列宽默认是均匀分配的。如果你想让某一行「自适应内容」,需要设置 setRowStretch() 或 setColumnStretch()。否则,一个很长的文本框会把整行撑得特别高。
26.3 表单布局:QFormLayout
表单布局,专门用来做「标签+输入框」这种配对布局。比如登录界面:用户名标签 + 用户名输入框,密码标签 + 密码输入框。
用QFormLayout的好处是:它会自动把标签右对齐,输入框左对齐,而且两列宽度会自动调整。你不需要手动计算间距。
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow("用户名:", userNameEdit);
formLayout->addRow("密 码:", passwordEdit);
formLayout->addRow("邮 箱:", emailEdit);
// 也可以添加按钮
formLayout->addRow("", loginButton); // 标签留空
addRow的第一个参数是标签文本,第二个参数是控件。如果你需要更复杂的行(比如一行里有两个按钮),可以用 addRow("标签", widget) 或者 addRow(labelWidget, fieldWidget)。
我建议:表单布局非常适合做设置界面、注册界面、搜索条件面板。它比手动用QHBoxLayout拼标签和输入框要简洁得多。
26.4 嵌套布局:组合的艺术
实际项目中,很少只用一种布局。大部分界面都是「大布局套小布局」。
比如一个典型的聊天窗口:
- 整体是垂直布局(QVBoxLayout)
- 上面是消息列表(占大部分空间)
- 下面是输入区域(水平布局:输入框 + 发送按钮)
- 输入区域本身又是一个QHBoxLayout
嵌套布局的写法很简单:用 addLayout() 代替 addWidget()。
// 主布局:垂直
QVBoxLayout *mainLayout = new QVBoxLayout;
// 消息列表
QListWidget *msgList = new QListWidget;
mainLayout->addWidget(msgList);
// 输入区域:水平布局
QHBoxLayout *inputLayout = new QHBoxLayout;
QLineEdit *inputEdit = new QLineEdit;
QPushButton *sendBtn = new QPushButton("发送");
inputLayout->addWidget(inputEdit);
inputLayout->addWidget(sendBtn);
// 把输入区域嵌套进主布局
mainLayout->addLayout(inputLayout);
// 设置到窗口
window->setLayout(mainLayout);
嵌套的深度一般不要超过3层,否则代码可读性会变差。我见过有人嵌套了5层,自己都分不清哪个布局管哪块了。
经验之谈:写嵌套布局时,建议用缩进和注释把层次结构标清楚。比如:
// 主垂直布局
// ├── 标题栏(水平布局)
// │ ├── 返回按钮
// │ └── 标题文本
// ├── 内容区域(网格布局)
// │ ├── 图片
// │ └── 描述文字
// └── 底部按钮(水平布局)
// ├── 确定
// └── 取消
26.5 布局的常用属性与技巧
| 属性/方法 | 作用 | 示例 |
|---|---|---|
setSpacing(int) |
设置控件之间的间距 | layout->setSpacing(10); |
setContentsMargins(int, int, int, int) |
设置布局与窗口边缘的间距(左、上、右、下) | layout->setContentsMargins(20, 10, 20, 10); |
addStretch(int) |
添加弹簧,占据剩余空间 | hLayout->addStretch(1); |
setAlignment(widget, alignment) |
设置控件在布局中的对齐方式 | layout->setAlignment(btn, Qt::AlignRight); |
setSizePolicy(...) |
设置控件的尺寸策略(固定、扩展、最小等) | btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); |
注意:布局的间距和边距默认值在不同平台上可能不一样。Windows上默认边距是11,macOS上可能是20。如果你希望界面在所有平台上看起来一致,建议显式设置 setContentsMargins 和 setSpacing。
26.6 知识体系总览
下面这张图,把本章的布局类型和关系梳理了一下。你可以把它当作一个「布局选择指南」。
从图上可以看出来:
- 水平/垂直布局是最基础的,适合简单的单方向排列。
- 网格布局适合表格类界面,可以跨行跨列。
- 表单布局是专门为「标签-输入框」场景设计的。
- 嵌套布局是实际项目中最常用的方式,把上面几种组合起来。
我的建议:刚开始学布局时,不要追求「一步到位」。先搭一个简单的垂直布局,把主要区域分好,再往里面嵌套水平布局或网格布局。边写边调,比一次性画完所有布局要靠谱得多。
好了,布局管理就讲到这里。记住一句话:能用布局器解决的问题,绝不用手写坐标。你的界面会因此更健壮,你的维护成本也会更低。