整理:Qt 界面开发必会:四大 Layout 布局管理器与 Spacer 弹簧机制详解

最近在折腾项目的时候碰到了这个知识点,查了不少资料,索性整理出来分享给大家。

🔥个人主页:Cx330🌸

❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》

《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔

《Git深度解析》:版本管理实战全解 《Qt 极境架构》MySQL 核心技术与实战

🌟心向往之行必能


🎥Cx330🌸的简介:


目录

前言:

一、容器类控件:Group Box & Tab Widget(控件分组与切换)

1.1 Group Box(控件分组框)

1.2 Tab Widget(标签页切换控件)

二、布局管理器:界面自适应的核心(垂直 / 水平 / 网格 / 表单布局)

2.1 QVBoxLayout(垂直布局)

2.2 QHBoxLayout(水平布局)

2.3 QGridLayout(网格布局)

2.4 QFormLayout(表单布局)

2.5 QSpacerItem(空白控件)

三、 本章模块避坑指南(覆盖所有收尾知识点)

四、 总结


前言:

在桌面 GUI 开发中,界面控件的排版与自适应缩放是极为核心的一环。如果在 Qt 中通过硬编码绝对坐标(如 setGeometry)来放置控件,当窗口尺寸改变或在不同分辨率的屏幕上运行时,界面极易出现错位、遮挡或空白大面积留白等尴尬情况。
为了解决这一问题,Qt 给出了强大的 Layout Manager(布局管理器) 系统。这篇文章结合课堂板书重点,系统梳理 Qt 中的四大核心布局、弹簧机制(Spacer)以及 Qt 控件的面向对象扩展能力。

一、容器类控件:Group Box & Tab Widget(控件分组与切换)

容器类控件的核心作用是对界面上的控件进行分组管理,让界面结构更清晰,同时给出分组交互(如标签页切换),包含 QGroupBox(分组框)和 QTabWidget(标签页控件)两个核心组件。

1.1 Group Box(控件分组框)

QGroupBox 是基础分组控件,通过边框和标题将相关控件归类(如 “用户信息”“订单信息” 分组),支持 “勾选启用” 功能(checkable 属性),仅美化界面、优化结构,不影响控件功能,是界面模块化 的首选。

核心属性与 API:

  • setTitle(const QString& title):设置分组框标题;
  • setCheckable(bool flag):设置是否支持勾选(勾选后分组内控件才可用);
  • setChecked(bool checked):设置默认勾选状态;
  • setFlat(bool flat):设置是否为扁平模式(无边框,仅显示标题)。

实战示例:麦当劳点餐分组(汉堡 + 小食 + 饮料分组)
将下拉框和微调框按 “汉堡”“小食”“饮料” 分组,界面结构更清晰:这里直接用ui的模式来实现

1.2 Tab Widget(标签页切换控件)

QTabWidget 是多页面切换的核心控件,通过标签页将不同功能模块的控件分离(如 “基本信息”“高级设置” 标签页),节省界面空间,提升交互效率,支持标签页新增、删除、移动,是繁琐界面的必备组件。

核心属性与 API:

标签页管理:

  • addTab(QWidget* page, const QString& label):新增标签页(page 为标签页对应的控件容器);
  • removeTab(int index):删除指定下标(从 0 开始)的标签页;
  • currentIndex():获取当前选中的标签页下标;
  • setCurrentIndex(int index):切换到指定下标标签页;
核心属性:
  • setTabsCloseable(bool flag):设置标签页是否支持关闭(显示关闭按钮);
  • setMovable(bool flag):设置标签页是否可拖动调整顺序;
核心信号:
  • currentChanged(int index):标签页切换时触发(返回新标签页下标);
  • tabCloseRequested(int index):点击标签页关闭按钮时触发。
实战示例:标签页新增 / 删除 / 切换 实现 “新建标签页”“删除当前标签页” 功能,标签页切换时打印下标:

#include "widget.h"
#include "ui_widget.h"
#include
#include

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 先在每个标签页中,添加一个Label
    QLabel* label1 = new QLabel(ui->tab);
    label1->setText("标签页1");
    label1->resize(100,50);

    QLabel* label2 = new QLabel(ui->tab_2);
    label2->setText("标签页2");
    label2->resize(100,50);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    // 使用 addTab 方法来创建新的标签页
    // 参数1 要指定一个 QWidget
    // 参数2 指定这个标签页的标题 text (标题), 此处标题就叫做 Tab + 数字
    int count = ui->tabWidget->count(); // 获取到标签页的数量
    QWidget* w = new QWidget();
    ui->tabWidget->addTab(w,QString("Tab ") + QString::number(count + 1));
    // 添加一个 QLabel 显示内容
    QLabel* label = new QLabel(w);
    label->setText(QString("标签页 ") + QString::number(count + 1));
    label->resize(100,50);
    // 设置新标签页被选中
    ui->tabWidget->setCurrentIndex(count);
}

void Widget::on_pushButton_2_clicked()
{
    // 先获取到当前选中的标签页的下标
    int index = ui->tabWidget->currentIndex();
    // 删除标签页
    ui->tabWidget->removeTab(index);
}

void Widget::on_tabWidget_currentChanged(int index)
{
    qDebug()addWidget(button3);

 	// 自定义布局样式
    layout->setSpacing(20); // 控件间距20px
    layout->setContentsMargins(30, 30, 30, 30); // 布局内边距30px
    // 把布局管理器添加到窗口中
    this->setLayout(layout);
}

Widget::~Widget()
{
    delete ui;
}

补充:直接使用ui也是可以做到的,有两种做法、

2.2 QHBoxLayout(水平布局)

控件按水平方向从左到右排列,适用于 “单行控件” 场景(如工具栏按钮、搜索框 + 按钮),API 与垂直布局完全一致,仅排列方向不同。

实战示例1:垂直布局管理三个按钮

#include "widget.h"
#include "ui_widget.h"
#include
#include

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QPushButton* btn1 = new QPushButton("按钮1");
    QPushButton* btn2 = new QPushButton("按钮2");
    QPushButton* btn3 = new QPushButton("按钮3");

    QHBoxLayout* layout = new QHBoxLayout();
    layout->addWidget(btn1);
    layout->addWidget(btn2);
    layout->addWidget(btn3);

    this->setLayout(layout);
}

Widget::~Widget()
{
    delete ui;
}

2.3 QGridLayout(网格布局)

控件按M 行 N 列的网格排列,支持控件跨行列,适用于繁琐界面(如计算器键盘、数据表格表单)

核心 API:

  • addWidget(QWidget* widget, int row, int column, int rowSpan, int columnSpan):
row/column:控件所在的行 / 列(从 0 开始);
  • rowSpan/columnSpan:控件跨的行 / 列数(默认 1 行 1 列);
  • setRowStretch(int row, int stretch):设置行的拉伸系数(系数越大,窗口缩放时占比越大);
  • setColumnStretch(int column, int stretch):设置列的拉伸系数。
实战示例:初步演示使用

#include "widget.h"
#include "ui_widget.h"
#include
#include

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QPushButton* btn1 = new QPushButton("按钮1");
    QPushButton* btn2 = new QPushButton("按钮2");
    QPushButton* btn3 = new QPushButton("按钮3");
    QPushButton* btn4 = new QPushButton("按钮4");

    QGridLayout* layout = new QGridLayout();
//    layout->addWidget(btn1, 0, 0);
//    layout->addWidget(btn2, 0, 1);
//    layout->addWidget(btn3, 1, 0);
//    layout->addWidget(btn4, 1, 1);

    // 这个写法就相当于水平布局了
    // 此时大家的行数一样就可以,不一定非得是0
//    layout->addWidget(btn1, 0, 0);
//    layout->addWidget(btn2, 0, 1);
//    layout->addWidget(btn3, 0, 2);
//    layout->addWidget(btn4, 0, 3);

    // 这个写法就相当于是垂直布局了
//    layout->addWidget(btn1, 0, 0);
//    layout->addWidget(btn2, 1, 0);
//    layout->addWidget(btn3, 2, 0);
//    layout->addWidget(btn4, 3, 0);

    // 这个写法是每个按钮独占一行和一列
    // 即使这是成 100,100,也不搞出很大的空白空间
    // 此时设置的行数和列数, 只是用来决定控件之前的相对位置
    layout->addWidget(btn1, 0, 0);
    layout->addWidget(btn2, 1, 1);
    layout->addWidget(btn3, 2, 2);
    layout->addWidget(btn4, 3, 3);
//    layout->addWidget(btn4, 100, 100);

    this->setLayout(layout);
}

Widget::~Widget()
{
    delete ui;
}

实战示例2:水平拉伸系数

#include "widget.h"
#include "ui_widget.h"
#include
#include

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 创建6个按钮,使用网格布局管理器按照2*3方式排列
    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");
    QPushButton* button3 = new QPushButton("按钮3");
    QPushButton* button4 = new QPushButton("按钮4");
    QPushButton* button5 = new QPushButton("按钮5");
    QPushButton* button6 = new QPushButton("按钮6");

    // 创建网格布局管理器,把这些控件添加进去
    QGridLayout* layout = new QGridLayout();
    layout->addWidget(button1,0,0);
    layout->addWidget(button2,0,1);
    layout->addWidget(button3,0,2);
    layout->addWidget(button4,1,0);
    layout->addWidget(button5,1,1);
    layout->addWidget(button6,1,2);

    this->setLayout(layout);

    // 设置水平拉伸系数
    // 0,1,2列比例大小为 1:1:2
    layout->setColumnStretch(0,1);
    layout->setColumnStretch(1,1);
    layout->setColumnStretch(2,2);

}

Widget::~Widget()
{
    delete ui;
}

实战示例3:垂直拉伸系数(要注意关键步骤)

#include "widget.h"
#include "ui_widget.h"
#include
#include

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 创建6个按钮,按照3行2列的方式进行排列
    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");
    QPushButton* button3 = new QPushButton("按钮3");
    QPushButton* button4 = new QPushButton("按钮4");
    QPushButton* button5 = new QPushButton("按钮5");
    QPushButton* button6 = new QPushButton("按钮6");

    button1->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    button2->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    button3->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    button4->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    button5->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    button6->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);

    // 创建layout
    QGridLayout* layout = new QGridLayout();
    layout->addWidget(button1,0,0);
    layout->addWidget(button2,0,1);
    layout->addWidget(button3,1,0);
    layout->addWidget(button4,1,1);
    layout->addWidget(button5,2,0);
    layout->addWidget(button6,2,1);

    this->setLayout(layout);

    // 设置拉伸系数
    layout->setRowStretch(0,1);
    layout->setRowStretch(1,1);
    layout->setRowStretch(2,2);

}

Widget::~Widget()
{
    delete ui;
}

2.4 QFormLayout(表单布局)

专门用于两列表单(左侧标签 + 右侧输入控件),适用于用户信息填写、参数配置等场景,自动对齐标签和输入框,界面整洁规范

核心 API :

  • addRow(const QString& labelText, QWidget* fieldWidget):添加一行(标签文本 + 输入控件);
  • addRow(QWidget* labelWidget, QWidget* fieldWidget):添加一行(标签控件 + 输入控件);
  • setLabelAlignment(Qt::Alignment alignment):设置标签的对齐方式。
实战示例:用户注册表单

#include "widget.h"
#include "ui_widget.h"
#include
#include
#include
#include

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 设置成3行2列
    QFormLayout* layout = new QFormLayout();
    this->setLayout(layout);

    // 创建3个label
    QLabel* label1 = new QLabel("姓名");
    QLabel* label2 = new QLabel("年龄");
    QLabel* label3 = new QLabel("电话");

    // 创建3个输入框作为第2列
    QLineEdit* edit1 = new QLineEdit();
    QLineEdit* edit2 = new QLineEdit();
    QLineEdit* edit3 = new QLineEdit();

    // 把上述控件添加到表单布局中
    layout->addRow(label1,edit1);
    layout->addRow(label2,edit2);
    layout->addRow(label3,edit3);

    // 创建一个提交按钮
    QPushButton* button = new QPushButton("提交");
    layout->addRow(nullptr,button);

}

Widget::~Widget()
{
    delete ui;
}

2.5 QSpacerItem(空白控件)

用于在布局中添加空白区域,实现控件的灵活排版(如按钮居左 / 居右 / 两端对齐),核心属性是 “宽度 / 高度” 和 “拉伸策略”,支持水平和垂直两种空白。

实战示例:按钮之间加上空白部分

#include "widget.h"
#include "ui_widget.h"
#include
#include

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QHBoxLayout* layout = new QHBoxLayout();
    this->setLayout(layout);

    QPushButton* button1 = new QPushButton("按钮1");
    QPushButton* button2 = new QPushButton("按钮2");

    // 创建spacer使两个按钮之间存在空白
    QSpacerItem* spacer = new QSpacerItem(200,20);

    // 当前是把空白添加到两个按钮之间
    layout->addWidget(button1);
    layout->addSpacerItem(spacer);
    layout->addWidget(button2);

}

Widget::~Widget()
{
    delete ui;
}


三、 本章模块避坑指南(覆盖所有收尾知识点)

  • Tree Widget:
节点删除一定要区分顶层 / 子节点,且手动释放内存,否则会内存泄漏;
  • 多列节点需先设置setColumnCount(int count),再给节点设置对应列文本。
  • 容器控件:
Group Box 的checkable属性为 true 时,需通过isChecked()判断分组是否启用,再控制子控件可用性;
  • Tab Widget 新增标签页时,需创建独立的 QWidget 作为容器,否则控件会重叠。
布局管理器:
  • 一个 Widget 只能设置一个顶层布局,嵌套布局需通过addLayout添加到父布局;
  • 网格布局的**rowSpan**/**columnSpan**参数是 “跨行列数”,不是 “结束行列号”,避免混淆;
  • 拉伸系数(setRowStretch/setColumnStretch)仅对 “未固定尺寸的控件” 生效,固定尺寸控件不会拉伸。
  • Spacer 控件:
水平 Spacer 的QSizePolicy::Expanding作用于水平方向,垂直 Spacer 作用于垂直方向,需按需选择;
  • Spacer 的宽度 / 高度是 “最小空白尺寸”,拉伸时会超过该值。

四、 总结

掌握 Qt 布局系统是编写高质量桌面软件的关键一步:

1. 选择合适布局:简单的单向排版选 QHBoxLayout/QVBoxLayout;网格式卡片选 QGridLayout;表单填写选 QFormLayout。
2. 巧妙利用 Spacer:结合 QSpacerItem 与 QSizePolicy 掌控界面的空白拉伸。
3. 面向对象封装:将经常复用的 UI 结构封装成继承自 QWidget 的自定义子类,打造高内聚、低耦合的优雅代码架构。


就写这么多吧,内容比较基础,适合入门回顾。有补充的地方欢迎留言一起完善。

评论 (0)

暂无评论