Qt入门07 Qt中的窗口布局——大丙+Gemini

7. Qt中的窗口布局——大丙+Gemini

https://www.bilibili.com/video/BV1Jp4y167R9

7.0 布局的样式

在 Qt 开发中,布局管理器(Layout Manager) 负责自动管理窗口中子控件的尺寸与排列位置。

7.0.1 绝对定位的缺陷 和 自动布局的优势

在传统开发中,若使用绝对坐标(即基于几何坐标 setGeometry)固定控件位置,会导致以下严重问题:

  1. 窗口缩放失效:当用户拉伸或最大化窗口时,子控件的大小和相对位置保持不变,导致界面出现大片空白或控件越界隐藏。
  2. 跨平台适配极差:不同操作系统、不同屏幕分辨率(如高分屏 High-DPI)以及系统字体大小的差异,会导致固定坐标下的文本被截断或控件重叠。
  3. 国际化支持困难:同一段文本,翻译成不同语言(如英文、德文)后的长度差异极大,固定宽度的控件无法自适应文本长度。

自动布局的优势

  • 自动响应窗口大小调整,动态拉伸或压缩子控件。
  • 自动适配不同的屏幕分辨率、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 方式一:使用独立布局控件(不推荐)

  1. 拖拽布局:从左侧工具箱(Widget Box)的“Layouts”分组中选择需要的布局(如 Horizontal Layout 或 Vertical Layout),将其拖拽至UI设计画布中。
  2. 置入控件:将需要排列的子控件逐个拖入该布局框的内部,控件将根据布局类型自动实现线性对齐。

可以动态转换:若需更改已有布局的类型,可选中该布局,点击鼠标右键,在弹出的上下文菜单中选择“布局(Lay out)”,在其子菜单中将其转换为其他目标布局。

7.1.2 方式二:对容器/窗口直接应用布局(推荐)

对于结构复杂的界面,推荐先放置容器,再对容器内的组件进行整体布局。

  1. 放置容器:从工具箱中拖拽一个容器类组件(Containers)至画布,通常首选 QWidgetQWidget 充当隐形容器,在程序运行时不具有任何视觉边框。

  2. 放置子组件:将所有需要归类的子控件放置到该 QWidget 容器内部。在此阶段,无需刻意对齐控件位置。

  3. 激活布局:

    • 方法 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()

对于 QHBoxLayoutQVBoxLayout,可以直接调用 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);

参数解析:

  • wh:指定弹簧的期望宽度与期望高度(单位:像素)。
  • hPolicyvPolicy:指定水平与垂直方向的大小策略(如 QSizePolicy::FixedQSizePolicy::Expanding)。

参数 wh 并非最终绝对尺寸,其官方定义为 “期望尺寸(Hint Size)”“初始参考尺寸”。而布局管理器(Layout)是一个动态调整的机制,当用户拉伸窗口、放大全屏、或者往窗口里添加新控件时,布局必须决定如何分配这些暴增的或者缩水的空间。

QSizePolicy::PolicyQSpacerItem 中的核心枚举值及行为表现:假设设定窗口w = 50,该窗口在不同枚举值下:

  1. QSizePolicy::Fixed(固定策略)
  • 规则w=50,其值永远是 50,不再改变。
  • 拉伸表现:窗口拉得再大,这个弹簧也绝不伸长一像素。多出来的空间全给别人。
  1. QSizePolicy::Minimum(最小限制策略:有底线的被动型)

    • 规则w=50 是窗口尺寸最小值,无论窗口如何缩小宽度都不能小于 50。
    • 拉伸表现:窗口拉大时,如果旁边有更强力的控件(比如输入框),弹簧会把空间让给别人,自己依然保持 50;如果旁边都是死板的按钮,没人要空间,那弹簧才会被动地稍微变长一点
  2. 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 空隙)
posted @ 2026-06-06 21:25  学习笔记草稿存放账号  阅读(12)  评论(0)    收藏  举报