Qt入门07 Qt中的窗口布局——大丙+Gemini
7. Qt中的窗口布局——大丙+Gemini
https://www.bilibili.com/video/BV1Jp4y167R9
7.0 布局的样式
在 Qt 开发中,布局管理器(Layout Manager) 负责自动管理窗口中子控件的尺寸与排列位置。
7.0.1 绝对定位的缺陷 和 自动布局的优势
在传统开发中,若使用绝对坐标(即基于几何坐标 setGeometry)固定控件位置,会导致以下严重问题:
- 窗口缩放失效:当用户拉伸或最大化窗口时,子控件的大小和相对位置保持不变,导致界面出现大片空白或控件越界隐藏。
- 跨平台适配极差:不同操作系统、不同屏幕分辨率(如高分屏 High-DPI)以及系统字体大小的差异,会导致固定坐标下的文本被截断或控件重叠。
- 国际化支持困难:同一段文本,翻译成不同语言(如英文、德文)后的长度差异极大,固定宽度的控件无法自适应文本长度。
自动布局的优势:
- 自动响应窗口大小调整,动态拉伸或压缩子控件。
- 自动适配不同的屏幕分辨率、DPI 缩放以及系统字体变化。
- 具备极强的灵活性,支持无限层级的布局嵌套,可构建任意复杂的工业级界面。
7.0.2 Qt中最常用的三种布局样式
| 布局样式 | 对应类名 | 描述 | 排列规则 |
|---|---|---|---|
| 水平布局 | QHBoxLayout |
内部所有控件在水平方向上线性排列 | 1行,N列($N \ge 1$) |
| 垂直布局 | QVBoxLayout |
内部所有控件在垂直方向上线性排列 | N行($N \ge 1$),1列 |
| 网格(栅格)布局 | QGridLayout |
内部控件按行与列组成的二维网格矩阵排列 | N行,N列($N \ge 1$) |
补充说明:此外还存在形如表单布局(
QFormLayout)和堆栈布局(QStackedLayout)等特殊布局,其中表单布局专 center 用于“标签-输入框”的双列结构,而栅格布局(QGridLayout)由于其强大的坐标覆盖能力,完全可以替代并实现更为复杂的表格化排列。
7.1 在UI窗口中设置布局(Qt Designer)
在Qt Creator的UI设计器中,设置布局主要有以下两种规范化的操作方式:
7.1.1 方式一:使用独立布局控件(不推荐)
- 拖拽布局:从左侧工具箱(Widget Box)的“Layouts”分组中选择需要的布局(如 Horizontal Layout 或 Vertical Layout),将其拖拽至UI设计画布中。
- 置入控件:将需要排列的子控件逐个拖入该布局框的内部,控件将根据布局类型自动实现线性对齐。
可以动态转换:若需更改已有布局的类型,可选中该布局,点击鼠标右键,在弹出的上下文菜单中选择“布局(Lay out)”,在其子菜单中将其转换为其他目标布局。
7.1.2 方式二:对容器/窗口直接应用布局(推荐)
对于结构复杂的界面,推荐先放置容器,再对容器内的组件进行整体布局。
-
放置容器:从工具箱中拖拽一个容器类组件(Containers)至画布,通常首选
QWidget。QWidget充当隐形容器,在程序运行时不具有任何视觉边框。 -
放置子组件:将所有需要归类的子控件放置到该
QWidget容器内部。在此阶段,无需刻意对齐控件位置。 -
激活布局:
-
方法 1(右键菜单):选中该
QWidget容器(或直接选中顶层窗口MainWindow),点击鼠标右键,选择“布局(Lay out)” -> 选择对应的布局模式(如“水平布局”或“垂直布局”)。 -
方法 2(工具栏快捷键):除了右键菜单,亦可在选中父容器后,直接点击Qt Designer顶部工具栏上的布局快捷按钮(如布局水平、布局垂直、布局栅格等)来实现一键布局。
-
7.1.3 Spacer(空间间隔组件/弹簧)的应用
Spacer n. 垫片、间隔物、分度器
- 单词本身没有弹簧的意思,正确叫法应该是动态间距组件,这是一个约定俗成的问题翻译。
Spacer(在中文环境中常被称为弹簧)用于控制组件之间的间距,或实现控件的靠边、居中对齐。
Spacer 分为两种样式:
- Horizontal Spacer(水平间隔器):在水平方向上提供推力,用于横向挤压控件。
- Vertical Spacer(垂直间隔器):在垂直方向上提供推力,用于纵向挤压控件。
Spacer 的核心属性为 sizeType(大小策略),其常用取值如下:
Fixed:固定尺寸。间隔器的长度/宽度被限制为设定的像素值,不再随窗口缩放而改变,常用于固定控件之间的绝对间距。Expanding:可伸缩尺寸(默认值)。间隔器会最大限度地吸收剩余的空白空间,将两侧的控件推向边缘,常用于实现靠边或居中对齐。
7.1.4 布局属性的调整
当为一个窗口或容器成功设置布局后,选中该窗口,在右侧的属性编辑器(Property Editor)中会出现对应的布局属性栏(通常以 layout 开头)。
主要调整参数包括:
layoutXXXXXMargin:设置布局内侧与外部容器边界的边距(单位:像素)。具体包括:layoutLeftMargin(左边距)layoutTopMargin(上边距)layoutRightMargin(右边距)layoutBottomMargin(下边距)
layoutSpacing:设置该布局内部相邻各个控件之间的间隙大小。
7.1.5 布局的规范检查与注意事项
在UI设计器的右上角“对象查看器(Object Inspector)”的树状列表中,可以清晰看到各级控件的依赖关系。
- 未布局警告:如果某个窗口或容器的图标右下角出现红色禁止标记或未关联标识,说明该窗口尚未被指定任何主布局。
- 未布局后果:未设置顶层主布局的窗口,其内部子控件在窗口大小改变时将完全静止,甚至在不同系统平台下运行时发生控件遮挡或无法显示的问题。因此,所有UI界面的根节点窗口,最终都必须指定一个主布局。
7.2 通过C++ API设置布局
主要是Gemini生成
在Qt底层架构中,布局管理器由专门的类进行控制。其核心继承关系如下:
7.2.1 QLayout——纯虚基类
QLayout 是一个纯虚基类(Abstract Base Class),无法直接进行实例化。它是所有布局管理器的基类,定义了操纵布局中组件的核心通用接口。
QLayout核心成员函数原型:
void QLayout::addWidget(QWidget *w);
- 功能:向布局的末尾添加一个子控件。
void QLayout::removeWidget(QWidget *widget)
-
功能:将指定控件从布局管理中移除。
-
机制:此操作仅将组件移出布局的几何计算队列,并不销毁(delete)或隐藏该组件,亦不会改变组件的父对象。该控件仍保留在界面上,但不再随窗口缩放而自动调整尺寸。
void QLayout::setContentsMargins(int left, int top, int right, int bottom);
- 功能:设置布局内侧与外部容器边界四个方向的内边距(单位:像素)。
void QLayout::setSpacing(int spacing);
- 功能:统一设置布局内部相邻各个控件之间的间隙大小(单位:像素)。
7.2.2 控件添加顺序与对象所有权机制
1. 构建顺序的常规流程
在代码中,通常先创建控件并布局对象->addWidget(控件)到布局,随后再 QWidget对象->setLayout(layout):
创建子控件 -> 实例化布局 -> 控件加入布局 -> 布局应用至窗口
- 这并非硬性规定的绝对顺序,代码编写时先创建布局还是先创建控件在语法上皆可。仅实际开发中通常使用此顺序。
2. 子控件的真正父对象(Parent)是 QWidget对象,而不是 Layout布局对象
QLayout 并不是一个 QWidget(它继承自 QObject),它没有视觉实体,不负责渲染,只负责计算位置坐标。
- 底层机制:当执行
布局对象->addWidget(控件)时,布局仅仅是将控件记录在自己的管理队列中。当执行QWidget对象->setLayout(布局对象)时,系统会自动触发协同机制,将该布局内管理的所有子控件的父对象(Parent)隐式且强制地重定向设置为该QWidget对象。
7.2.3 QHBoxLayout 与 QVBoxLayout 实例化示例
1. 水平布局(QHBoxLayout)
以下代码展示了在 QMainWindow 的构造函数中,通过纯代码方式实现子控件的横向线性排列(不在构造时直接关联):
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->resize(800, 500);
// 1. 创建中间容器部件,指定当前窗口为其父对象
QWidget* btnWindow = new QWidget(this);
// 2. 创建垂直布局管理器,在构造时不指定具体归属,直接给 this
QHBoxLayout* hlayout = new QHBoxLayout(this);
// 3. 实例化子控件对象
QPushButton* btn1 = new QPushButton("One");
QPushButton* btn2 = new QPushButton("Two");
QPushButton* btn3 = new QPushButton("Three");
QPushButton* btn4 = new QPushButton("Four");
QPushButton* btn5 = new QPushButton("Five");
// 4. 将子控件按顺序添加至垂直布局管理器中,实现横向排列
hlayout->addWidget(btn1);
hlayout->addWidget(btn2);
hlayout->addWidget(btn3);
hlayout->addWidget(btn4);
hlayout->addWidget(btn5);
// 5. 指定其归属于 btnWindow,将设置好的布局应用交给中间容器部件
btnWindow->setLayout(hlayout);
// 6. 展示窗口
btnWindow->show();
btnWindow->resize(500, 100);
}
2. 垂直布局(QVBoxLayout)
以下代码展示了利用构造函数传参特性,直接将垂直布局关联至容器并设置为中心部件的规范写法:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->resize(800, 500);
// 1. 创建中间容器窗口,指定 MainWindow 为其父对象
QWidget* btnWindow = new QWidget(this);
// 2. 创建垂直布局管理器,并在构造时直接指定其归属于 btnWindow
QVBoxLayout* vlayout = new QVBoxLayout(btnWindow);
// 3. 创建子控件对象
QPushButton* btn1 = new QPushButton("One");
QPushButton* btn2 = new QPushButton("Two");
QPushButton* btn3 = new QPushButton("Three");
QPushButton* btn4 = new QPushButton("Four");
QPushButton* btn5 = new QPushButton("Five");
// 4. 将子控件按顺序添加至垂直布局管理器中
vlayout->addWidget(btn1);
vlayout->addWidget(btn2);
vlayout->addWidget(btn3);
vlayout->addWidget(btn4);
vlayout->addWidget(btn5);
// 5. 配置容器为主中心部件
this->setCentralWidget(btnWindow);
}
7.2.4 网格布局(QGridLayout)
网格布局(又称栅格布局)将窗口空间划分为行和列构感组成的二维矩阵。控件可以精确放置在指定的单元格内,亦可跨越多个行或列。
1. QGridLayout核心成员函数
1.1 构造函数
QGridLayout::QGridLayout(QWidget *parent = nullptr);
1.2 核心功能:添加控件
// 添加控件到指定的单元格(占用单格空间)
void QGridLayout::addWidget(QWidget *widget, int row, int column, Qt::Alignment alignment = Qt::Alignment());
// 添加控件并支持跨行列合并(占用多格空间)
void QGridLayout::addWidget(QWidget *widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment = Qt::Alignment());
- 参数解析:
widget:需要插入布局的子控件指针。fromRow:目标单元格的起始行索引(从 0 开始计数)。fromColumn:目标单元格的起始列索引(从 0 开始计数)。rowSpan:该控件在垂直方向上需要合并及占用的总行数。columnSpan:该控件在水平方向上需要合并及占用的总列数。alignment:控件在单元格内部的对齐方式(Qt::Alignment枚举值)。
2. 对齐方式(Qt::Alignment)枚举值:
Qt::Alignment 实际上是一个枚举值(Flags)。
-
默认值
Qt::Alignment():返回0的函数/构造函数,表示完全占据该单元格(格子)内部的全部空间。 -
其余值:最常用的核心值可以分为水平和垂直两个维度:
维度 枚举标识符 描述 水平方向 (Horizontal) Qt::AlignLeft靠左对齐 Qt::AlignRight靠右对齐 Qt::AlignHCenter水平居中对齐 Qt::AlignJustify两端对齐 垂直方向 (Vertical) Qt::AlignTop靠上对齐 Qt::AlignBottom靠下对齐 Qt::AlignVCenter垂直居中对齐 复合对齐 (Combined) Qt::AlignCenter正中心对齐(等价于 `Qt::AlignHCenter
3. 尺寸与间距控制 API
// 1. 设置指定行的最小高度。
void QGridLayout::setRowMinimumHeight(int row, int minSize)
// 2. 设置指定列的最小宽度。
void QGridLayout::setColumnMinimumWidth(int column, int minSize)
// 3. 设置布局内所有控件的水平方向间距。
void QGridLayout::setHorizontalSpacing(int spacing)
// 4. 设置布局内所有控件的垂直方向间距。
void QGridLayout::setVerticalSpacing(int spacing)
4. QGridLayout 复合网格布局示例
一个包含文本编辑框(占用多行多列)与数个标准按钮的复合网格布局界面,以下是mainwindow.cpp构造函数:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->resize(800, 500);
// 1. 初始化容器与布局
QWidget* window = new QWidget(this);
QGridLayout* layout = new QGridLayout(window);
// 2. 实例化子控件
QPushButton *button1 = new QPushButton("One");
QPushButton *button2 = new QPushButton("Two");
QPushButton *button3 = new QPushButton("Three");
QPushButton *button4 = new QPushButton("Four");
QPushButton *button5 = new QPushButton("Five");
QPushButton *button6 = new QPushButton("Six");
QTextEdit* txedit = new QTextEdit();
txedit->setText("当前文本编辑框占用了两行两列的空间。");
// 3. 将子控件精确部署至网格矩阵中
// 第 0 行排列前三个标准按钮
layout->addWidget(button1, 0, 0);
layout->addWidget(button2, 0, 1);
layout->addWidget(button3, 0, 2);
// 第 1 行、第 0 列开始,使文本框纵跨 2 行,横跨 2 列
layout->addWidget(txedit, 1, 0, 2, 2);
// 在文本框右侧依次排列垂直按钮
layout->addWidget(button4, 1, 2); // 第 1 行,第 2 列
layout->addWidget(button5, 2, 2); // 第 2 行,第 2 列
// 第 3 行、第 0 列开始,使按钮 6 横跨 3 列,形成底部长条按钮
layout->addWidget(button6, 3, 0, 1, 3);
// 4. 应用布局与展示
window->setLayout(layout);
this->setCentralWidget(window);
}
7.2.5 代码中动态间距与空间间隔器(Spacer)的控制
在纯代码开发中,若需实现等同于 UI 设计器中 Spacer 的推力效果,可通过以下两种标准的 API 路径实现:
1. 使用快捷拉伸函数 QBoxLayout::addStretch()
对于 QHBoxLayout 和 QVBoxLayout,可以直接调用 addStretch 函数。该方法会在当前布局队列的末尾插入一个具备可伸缩特性的空白项。
// 函数原型
void QBoxLayout::addStretch(int stretch = 0);
-
参数
stretch = 0(缺省值):代表标准线性拉伸。-
特性:该空白项没有固定的初始尺寸,但具备无限的拉伸能力。
-
作用:当窗口存在剩余空间时,该项将膨胀占据全部剩余空间,把周围的控件推到最边缘。
-
QVBoxLayout *vlayout = new QVBoxLayout(this); vlayout->addStretch(); // Spacer在上面,参数为 0 vlayout->addWidget(btn); // Spacer会占据上方所有的剩余空间,把按钮推到窗口最底部。
-
-
参数
stretch > 0:代表加权比例拉伸-
当放入多个空间间隔器,且传入了不同的非零数字(例如
1,2,3等)时:参数代表的是权重比例(Ratio)。网格/箱式布局在分配窗口富余的空间时,会按照比例把空间分给这些空间间隔器。
-
示例一:实现右对齐(控件靠右,拉伸项在左)
QWidget* window = new QWidget(this);
QHBoxLayout* layout = new QHBoxLayout(window);
layout->addStretch(); // 将左侧空间全部占满,把按钮推向右侧
layout->addWidget(new QPushButton("One"));
layout->addWidget(new QPushButton("Two"));
window->setLayout(layout);
window->setStyleSheet("background-color: lightBlue;");
window->resize(400, 100);
示例二:实现左对齐(控件靠左,拉伸项在右)
QWidget* window = new QWidget(this);
QHBoxLayout* layout = new QHBoxLayout(window);
layout->addWidget(new QPushButton("One"));
layout->addWidget(new QPushButton("Two"));
layout->addStretch(); // 将后续空间全部占满,把按钮推向左侧
window->setLayout(layout);
window->setStyleSheet("background-color: lightBlue;");
window->resize(400, 100);
示例三:实现非对称比例对齐(左右两侧加拉伸项,分配空间比例为 1:2)
QWidget* window = new QWidget(this);
QHBoxLayout* layout = new QHBoxLayout(window);
layout->addStretch(1); // 左侧拉伸项,权重为 1(短)
layout->addWidget(new QPushButton("One"));
layout->addWidget(new QPushButton("Two"));
layout->addStretch(2); // 右侧拉伸项,权重为 2(长)
window->setLayout(layout);
window->setStyleSheet("background-color: lightBlue;");
window->resize(400, 100);
2. 实例化QSpacerItem对象
若需要对显式尺寸策略进行精确控制,或在 QGridLayout 中应用空间间隔,必须实例化 QSpacerItem 类,并通过 addItem 函数手动加入布局。
// QSpacerItem 构造函数原型
QSpacerItem::QSpacerItem(int w, int h, QSizePolicy::Policy hPolicy = QSizePolicy::Minimum, QSizePolicy::Policy vPolicy = QSizePolicy::Minimum);
参数解析:
w与h:指定弹簧的期望宽度与期望高度(单位:像素)。hPolicy与vPolicy:指定水平与垂直方向的大小策略(如QSizePolicy::Fixed或QSizePolicy::Expanding)。
参数 w 与 h 并非最终绝对尺寸,其官方定义为 “期望尺寸(Hint Size)” 或 “初始参考尺寸”。而布局管理器(Layout)是一个动态调整的机制,当用户拉伸窗口、放大全屏、或者往窗口里添加新控件时,布局必须决定如何分配这些暴增的或者缩水的空间。
QSizePolicy::Policy 在 QSpacerItem 中的核心枚举值及行为表现:假设设定窗口w = 50,该窗口在不同枚举值下:
QSizePolicy::Fixed(固定策略)
- 规则:
w=50,其值永远是 50,不再改变。 - 拉伸表现:窗口拉得再大,这个弹簧也绝不伸长一像素。多出来的空间全给别人。
-
QSizePolicy::Minimum(最小限制策略:有底线的被动型)- 规则:
w=50是窗口尺寸最小值,无论窗口如何缩小宽度都不能小于 50。 - 拉伸表现:窗口拉大时,如果旁边有更强力的控件(比如输入框),弹簧会把空间让给别人,自己依然保持 50;如果旁边都是死板的按钮,没人要空间,那弹簧才会被动地稍微变长一点。
- 规则:
-
QSizePolicy::Expanding(扩展策略:贪婪的强力型)-
规则:
w=50仅作为初始基准线,是最小值。 -
拉伸表现:窗口只要一拉大,它就会疯狂膨胀。原本是 50,拉大后可能变成 200、500 甚至 1000 像素,直到把旁边的控件压扁到极限为止。
-
示例一:在 QHBoxLayout 中将按钮推向最右侧(使用 Expanding 策略)
// 1. 创建布局和按钮
QHBoxLayout *layout = new QHBoxLayout(this);
QPushButton *button = new QPushButton("确 定", this);
// 2. 实例化一个水平方向贪婪(Expanding)的弹簧
// 宽度设为 40(但在 Expanding 面前,初始宽度不重要,它会无限拉伸)
// 高度设为 20(水平布局中高度通常设为 0 或 20 均可,因为垂直策略是 Fixed)
QSpacerItem *horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Fixed);
// 3. 按顺序加入布局:先加弹簧,再加按钮
layout->addSpacer(horizontalSpacer); // 注意:也可以用 layout->addSpacerItem(horizontalSpacer);
layout->addWidget(button);
// 效果:弹簧在左边疯狂膨胀,把“确定”按钮死死推到最右边
示例二:在 QGridLayout 中实现精确定位间距(使用 Fixed 策略)
QGridLayout *gridLayout = new QGridLayout(this);
QPushButton *btn1 = new QPushButton("左上角按钮", this);
// 创建一个在水平和垂直方向都固定为 50 像素的死弹簧(主要用来撑开格子距离)
QSpacerItem *fixedSpacer = new QSpacerItem(50, 50, QSizePolicy::Fixed, QSizePolicy::Fixed);
// 塞入网格布局
gridLayout->addWidget(btn1, 0, 0); // 第 0 行,第 0 列
gridLayout->addItem(fixedSpacer, 0, 1); // 第 0 行,第 1 列(在按钮右侧强行留出 50px 空隙)

浙公网安备 33010602011771号