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。如果你希望界面在所有平台上看起来一致,建议显式设置 setContentsMarginssetSpacing

26.6 知识体系总览

下面这张图,把本章的布局类型和关系梳理了一下。你可以把它当作一个「布局选择指南」。

Qt 布局管理器知识体系 布局管理器 QHBoxLayout 水平排列 按钮栏、工具栏 QVBoxLayout 垂直排列 主窗口、列表页 QGridLayout 网格排列 计算器、表格 QFormLayout 标签+输入框配对 登录、设置界面 嵌套布局:用 addLayout() 组合多种布局

从图上可以看出来:

  • 水平/垂直布局是最基础的,适合简单的单方向排列。
  • 网格布局适合表格类界面,可以跨行跨列。
  • 表单布局是专门为「标签-输入框」场景设计的。
  • 嵌套布局是实际项目中最常用的方式,把上面几种组合起来。

我的建议:刚开始学布局时,不要追求「一步到位」。先搭一个简单的垂直布局,把主要区域分好,再往里面嵌套水平布局或网格布局。边写边调,比一次性画完所有布局要靠谱得多。

好了,布局管理就讲到这里。记住一句话:能用布局器解决的问题,绝不用手写坐标。你的界面会因此更健壮,你的维护成本也会更低。

公众号:蓝海资料掘金营,微信deep3321