Qt 界面布局与容器控件详解:从分组框到布局管理器

📑 目录

  • [📚 1. 容器类控件](#📚 1. 容器类控件)
    • [1.1 Group Box](#1.1 Group Box)
    • [1.2 Tab Widget](#1.2 Tab Widget)
  • [📚 2. 布局管理器](#📚 2. 布局管理器)
    • [2.1 垂直布局](#2.1 垂直布局)
    • [2.2 水平布局](#2.2 水平布局)
    • [2.3 网格布局](#2.3 网格布局)
    • [2.4 表单布局](#2.4 表单布局)
    • [2.5 Spacer](#2.5 Spacer)
  • [📝 3. 小结](#📝 3. 小结)
  • [🎯 4. 经典面试题](#🎯 4. 经典面试题)

📚 1.容器类控件

1.1 Group Box

使⽤ QGroupBox 实现⼀个带有标题的分组框. 可以把其他的控件放到⾥⾯作为⼀组. 这样看起来能更好看⼀点.

📌 注意, 不要把 QGroupBox 和 QButtonGroup 混淆. (之前在介绍 QRadionButton 的时候提到了 QButtonGroup ).

可以把其他的各种控件放到GroupBox中,这些内部控件的父元素就不是this了,而是这个GroupBox.

核⼼属性

属性 说明
title 分组框的标题
alignment 分组框内部内容的对齐方式
flat 是否是"扁平"模式
checkable 是否可选择。设为 true,则在 title 前方会多出一个可勾选的部分
checked 描述分组框的选择状态(前提是 checkable 为 true)

🌰 分组框只是⼀个⽤来 "美化界⾯" 的组件, 并不涉及到⽤⼾交互和业务逻辑. 属于 "锦上添花" .

代码⽰例: 给⻨当劳案例加上分组框

  1. 在界⾯上创建三个分组框, 并且在分组框内部创建下拉框和微调框.

🏆 注意:在复制粘贴控件的时候, ⼀定要先选中对应的⽗控件, 再粘贴.

  1. 编写 widget.cpp, 添加初始化下拉框的代码
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

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

    ui->comboBox->addItem("巨无霸");
    ui->comboBox->addItem("麦辣鸡腿堡");

    ui->comboBox_2->addItem("薯条");
    ui->comboBox_2->addItem("麦辣鸡翅");

    ui->comboBox_3->addItem("可乐");
    ui->comboBox_3->addItem("雪碧");

    ui->spinBox->setRange(1,10);
    ui->spinBox->setValue(1);//默认为一份

    ui->spinBox_2->setRange(1,10);
    ui->spinBox_2->setValue(1);//默认为一份

    ui->spinBox_3->setRange(1,10);
    ui->spinBox_3->setValue(1);//默认为一份

}

Widget::~Widget()
{
    delete ui;
}
  1. 运⾏程序, 观察效果

1.2 Tab Widget

📌**说明:**使⽤ QTabWidget 实现⼀个带有标签⻚的控件, 可以往⾥⾯添加⼀些 widget. 进⼀步的就可以通标签⻚来切换.

核⼼属性

属性 说明
tabPosition 标签页所在的位置: - North 上方 - South 下方 - West 左侧 - East 右侧
currentIndex 当前选中了第几个标签页(从 0 开始计算)
currentTabText 当前选中的标签页的文本
currentTabName 当前选中的标签页的名字
currentTabIcon 当前选中的标签页的图标
currentTabToolTip 当前选中的标签页的提示信息
tabsCloseable 标签页是否可以关闭
movable 标签页是否可以移动

核⼼信号

信号 说明
currentChanged(int) 在标签页发生切换时触发,参数为被点击的选项卡编号
tabBarClicked(int) 在点击选项卡的标签条的时候触发,参数为被点击的选项卡编号
tabBarDoubleClicked(int) 在双击选项卡的标签条的时候触发,参数为被点击的选项卡编号
tabCloseRequest(int) 在标签页关闭时触发,参数为被关闭的选项卡编号

代码⽰例: 使⽤标签⻚管理多组控件

  1. 在界⾯上创建⼀个 QTabWidget , 和两个按钮.
    按钮的 objectName 为 pushButton_add 和 pushButton_remove

🏠 注意,

• QTabWidget 中的每个标签⻚都是⼀个 QWidget

• 点击标签⻚, 就可以直接切换.

• 右键 QTabWidget , 可以添加标签⻚或者删除标签⻚.

  1. 编写 widget.cpp, 进⾏初始化, 给标签⻚中放个简单的 label

📌注意新创建的 label 的⽗元素, 是ui->tab和 ui->tab_2 . Qt 中使⽤⽗⼦关系决定该控件 "在哪⾥".

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QLabel>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QLabel* label = new QLabel(ui->tab);//QLabel的初始化参数是其父类
    label->setText("标签页1");
    label->resize(100,50);
    
    QLabel* label2 = new QLabel(ui->tab);//QLabel的初始化参数是其父类
    label->setText("标签页2");
    label->resize(100,50);
}
Widget::~Widget()
{
    delete ui;
}
  1. 编写按钮的 slot 函数并编写 QTabWidget 的currentChanged函数
    • 使⽤ count() 获取到标签⻚的个数.
    • 使⽤ addTab 新增标签⻚.
    • 使⽤ removeTab 删除标签⻚.
    • 使⽤ currentIndex 获取到当前标签⻚的下标.
    • 使⽤ setCurrentIndex 切换当前标签⻚.
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QLabel>
#include<QDebug>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QLabel* label = new QLabel(ui->tab);//QLabel的初始化参数是其父类
    label->setText("标签页1");
    label->resize(100,50);

    QLabel* label2 = new QLabel(ui->tab);//QLabel的初始化参数是其父类
    label->setText("标签页2");
    label->resize(100,50);
}

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


void Widget::on_pushButton_clicked()
{
    //获取当前有几个标签页
    int count = ui->tabWidget->count();
    //创建新的widget
    QWidget* w = new QWidget();
    //设置标签页名
    ui->tabWidget->addTab(w,QString("Tab ") + QString::number(count+1));
    //给widget中添加label,当前label的父类是w
    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()<<"当前选中标签页为:"<<index;
}
  1. 运⾏程序, 观察效果
    • 点击新建标签⻚, 可以创建出新的标签.
    • 点击删除当前标签⻚, 可以删除标签.
    • 切换标签⻚时, 可以看到 qDebug 打印出的标签⻚编号.

📚 2. 布局管理器

之前使⽤ Qt 在界⾯上创建的控件, 都是通过 "绝对定位" 的⽅式来设定的.

也就是每个控件所在的位置, 都需要计算坐标, 最终通过 setGeometry 或者 move ⽅式摆放过去.

这种设定⽅式其实并不⽅便. 尤其是界⾯如果内容⽐较多, 不好计算. ⽽且⼀个窗⼝⼤⼩往往是可以调整的, 按照绝对定位的⽅式, 也⽆法⾃适应窗⼝⼤⼩.

🦄 当然, 布局管理器并⾮ Qt 独有. 其他的 GUI 开发框架, 像 Android, 前端等也### 2.1 垂直布局al `的缩写.

核⼼属性

属性 说明
layoutLeftMargin 左侧边距
layoutRightMargin 右侧边距
layoutTopMargin 上方边距
layoutBottomMargin 下方边距
layoutSpacing 相邻元素之间的间距

🏠注意:Layout 只是⽤于界⾯布局, 并没有提供信号.

如果在代码中创建layout,其实是只创建了一个layout

如果在Qt Designer中创建一个layout,先创了一个Widget

然后再在这个新的Widget中添加了一个layout

代码⽰例: 使⽤ QVBoxLayout 管理多个控件.

  1. 编写代码, 创建布局管理器和三个按钮. 并且把按钮添加到布局管理器中.
    • 使⽤addWidget把控件添加到布局管理器中.
    • 使⽤ setLayout 设置该布局管理器到 widget 中.
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QPushButton>
#include<QVBoxLayout>
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");

    //创建布局管理器,并且把按钮添加进去
    //如果创建的时候指定父元素为this,则后面不需要setLayout方法了
    QVBoxLayout* layout = new QVBoxLayout();
    layout->addWidget(btn1);
    layout->addWidget(btn2);
    layout->addWidget(btn3);

    //将布局管理器添加到widget中
    this->setLayout(layout);
}
Widget::~Widget()
{
    delete ui;
}
  1. 运⾏程序, 可以看到此时界⾯上的按钮就存在于布局管理器中. 随着窗⼝尺⼨变化⽽发⽣改变.

此时三个按钮的尺⼨和位置, 都是⾃动计算出来的.

通过上述代码的⽅式, 只能给这个 widget 设定⼀个布局管理器. 实际上也可以通过 Qt Design 在⼀个窗⼝中创建多个布局管理器.

代码⽰例: 创建两个 QVBoxLayout

  1. 在界⾯上创建两个 QVBoxLayout , 每个 QVBoxLayout 各放三个按钮.
  2. 运⾏程序, 可以看到这些按钮已经⾃动排列好. 只不过当前这些按钮的位置不能随着窗⼝⼤⼩⾃动变化.

🏝️ 通过 Qt Designer 创建的布局管理器, 其实是先创建了⼀个 widget, 设置过 geometry 属性的. 再把这个layout设置到这个widget中.

实际上, ⼀个widget只能包含⼀个 layout.

打开 ui ⽂件的原始 xml, 可以看到其中的端倪.

这种情况下 layout 并⾮是窗⼝ widget 的布局管理器, 因此不会随着窗⼝⼤⼩改变.

xml 复制代码
<widget class="QWidget" name="verticalLayoutWidget">
   <property name="geometry">
    <rect>
     <x>130</x>
     <y>80</y>
     <width>160</width>
     <height>411</height>
    </rect>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QPushButton" name="pushButton_2">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="pushButton_4">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="pushButton_3">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QPushButton" name="pushButton">
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>

2.2 ⽔平布局

使⽤ QHBoxLayout表⽰垂直的布局管理器. H 是 horizontal 的缩写.

核⼼属性 (和 QVBoxLayout 属性是⼀致的)

属性 说明
layoutLeftMargin 左侧边距
layoutRightMargin 右侧边距
layoutTopMargin 上方边距
layoutBottomMargin 下方边距
layoutSpacing 相邻元素之间的间距

代码⽰例: 使⽤ ·QHBoxLayout· 管理控件

  1. 编写代码, 创建布局管理器和三个按钮. 并且把按钮添加到布局管理器中.
cpp 复制代码
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);
 // 设置 layout 到 widget 上
 this->setLayout(layout);
}
  1. 运⾏程序, 可以看到此时界⾯上的按钮就存在于布局管理器中. 随着窗⼝尺⼨变化⽽发⽣改变.

此时三个按钮的尺⼨和位置, 都是⾃动计算出来的.

Layout ⾥⾯可以再嵌套上其他的 layout, 从⽽达到更复杂的布局效果.

代码⽰例: 嵌套的 layout

  1. 在代码中创建以下内容
    • 使⽤ addLayout 给layout中添加⼦ layout.
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QVBoxLayout>
#include<QHBoxLayout>
#include<QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //创建一个垂直布局器
    QVBoxLayout* parent_layout = new QVBoxLayout();
    this->setLayout(parent_layout);

    //新建2个按钮
    QPushButton* btn1 = new QPushButton("按钮1");
    QPushButton* btn2 = new QPushButton("按钮2");
    parent_layout->addWidget(btn1);
    parent_layout->addWidget(btn2);

    //创建子layout
    QHBoxLayout* child_layout = new QHBoxLayout();
    //添加两个按钮进去
    QPushButton* btn3 = new QPushButton("按钮3");
    QPushButton* btn4 = new QPushButton("按钮4");
    child_layout->addWidget(btn3);
    child_layout->addWidget(btn4);

    //把这个子layout添加到父layout中
    parent_layout->addLayout(child_layout);
}
Widget::~Widget()
{
    delete ui;
}
  1. 执⾏程序, 观察结果

✏️笔记:刚刚创建的布局管理器,这里的控件尺寸都是均等的。当需要创建出尺寸不同的控件的时候,就可以通过拉伸系数来设置拉伸系数就相当于设置控件之间尺寸的"比例"

2.3 ⽹格布局

Qt 中还提供了 QGridLayout ⽤来实现⽹格布局的效果. 可以达到M * N的这种⽹格的效果.

核⼼属性

整体和 QVBoxLayout 以及 QHBoxLayout 相似. 但是设置 spacing 的时候是按照垂直⽔平两个⽅向来设置的.

属性 说明
layoutLeftMargin 左侧边距
layoutRightMargin 右侧边距
layoutTopMargin 上方边距
layoutBottomMargin 下方边距
layoutHorizontalSpacing 相邻元素之间水平方向的间距
layoutVerticalSpacing 相邻元素之间垂直方向的间距
layoutRowStretch 行方向的拉伸系数
layoutColumnStretch 列方向的拉伸系数

代码⽰例: 使⽤ QGridLayout 管理元素

  1. 代码中创建 QGridLayout 和 4 个按钮.
    • 使⽤ addWidget 添加控件到布局管理器中. 但是添加的同时会指定两个坐标. 表⽰放在第⼏⾏, 第⼏列.
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QGridLayout>
#include<QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //创建4个按钮,并添加到GridLayout中
    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);

    //设置layout到窗口中
    this->setLayout(layout);
}
Widget::~Widget()
{
    delete ui;
}
  1. 执⾏代码, 观察效果. 可以看到当前的这⼏个按钮是按照 2 ⾏ 2 列的⽅式排列的.
  2. 如果调整⾏列坐标为下列代码
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QGridLayout>
#include<QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //创建4个按钮,并添加到GridLayout中
    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,0,2);
    layout->addWidget(btn4,0,3);
    //设置layout到窗口中
    this->setLayout(layout);
}
Widget::~Widget()
{
    delete ui;
}
  1. 执⾏代码, 可以看到这⼏个按钮都在同⼀⾏了. 相当于 QHBoxLayout
  2. 如果调整⾏列坐标为下列代码
cpp 复制代码
 // 创建⽹格布局管理器, 并且添加元素
 QGridLayout* layout = new QGridLayout();
 layout->addWidget(btn1, 1, 0);
 layout->addWidget(btn2, 2, 0);
 layout->addWidget(btn3, 3, 0);
 layout->addWidget(btn4, 4, 0);
  1. 执⾏代码, 可以看到这⼏个按钮都在同⼀列了. 相当于 QVBoxLayout
  2. 任意调整⾏列, 即可看到不同的效果.
cpp 复制代码
 // 创建⽹格布局管理器, 并且添加元素
 QGridLayout* layout = new QGridLayout();
 layout->addWidget(btn1, 0, 0);
 layout->addWidget(btn2, 1, 1);
 layout->addWidget(btn3, 2, 2);
 layout->addWidget(btn4, 3, 3);
  1. 编写代码形如
cpp 复制代码
 QGridLayout* layout = new QGridLayout();
 layout->addWidget(btn1, 0, 0);
 layout->addWidget(btn2, 1, 0);
 layout->addWidget(btn3, 2, 0);
 layout->addWidget(btn4, 10, 0);

🥇 此处也要注意, 设置⾏和列的时候, 如果设置的是⼀个很⼤的值, 但是这个值和上⼀个值之间并没有其他的元素, 那么并不会在中间腾出额外的空间.

虽然把btn4设置在第10 ⾏, 但是由于3-9 ⾏没有元素. 因此 btn4 仍然会紧挨在btn3下⽅. 看起来和上⾯的 0 1 2 3 的情况是相同的.

代码⽰例: 设置 QGridLayout 中元素的⼤⼩⽐例.

  1. 创建 6 个按钮, 按照 2 ⾏ 3 列的⽅式排列
    • 使⽤ setColumnStretch 设置每⼀列的拉伸系数
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QPushButton>
#include<QGridLayout>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //创建6个按钮
    QPushButton* btn1 = new QPushButton("按钮1");
    QPushButton* btn2 = new QPushButton("按钮2");
    QPushButton* btn3 = new QPushButton("按钮3");
    QPushButton* btn4 = new QPushButton("按钮4");
    QPushButton* btn5 = new QPushButton("按钮5");
    QPushButton* btn6 = new QPushButton("按钮6");

    //创建网格布局管理器,并且添加元素
    QGridLayout* layout = new QGridLayout();
    layout->addWidget(btn1,0,0);
    layout->addWidget(btn2,0,1);
    layout->addWidget(btn3,0,2);
    layout->addWidget(btn4,1,0);
    layout->addWidget(btn5,1,1);
    layout->addWidget(btn6,1,2);

    //设置拉伸比例 - 三列的比例是1 : 0 : 3
    //第0列拉伸比例设为1;
    layout->setColumnStretch(0,1);
    //第一列拉伸比例设为0,即为固定大小,不参与拉伸
    layout->setColumnStretch(1,0);
    //第二列拉伸比例设为3,即为第2列的宽度是第0列的3倍
    layout->setColumnStretch(2,3);

    //设置layout到窗口中
    this->setLayout(layout);
}

Widget::~Widget()
{
    delete ui;
}
  1. 执⾏程序, 可以看到每⼀列的宽度是不同的. 并且随着窗⼝调整动态变化.

🏝️另外, QGridLayout 也提供了setRowStretch设置⾏之间的拉伸系数.

上述案例中, 直接设置setRowStretch效果不明显, 因为每个按钮的⾼度是固定的. 需要把按钮的垂直⽅向的sizePolicy属性设置为QSizePolicy::Expanding尽可能填充满布局管理器, 才能看到效果.

代码⽰例: 设置垂直⽅向的拉伸系数

  1. 编写代码, 创建 6 个按钮, 按照 3 ⾏ 2 列⽅式排列.
    使⽤setSizePolicy设置按钮的尺⼨策略. 可选的值如下:
    • QSizePolicy::Ignored : 忽略控件的尺⼨,不对布局产⽣影响。
    • QSizePolicy::Minimum : 控件的最⼩尺⼨为固定值,布局时不会超过该值。
    • QSizePolicy::Maximum : 控件的最⼤尺⼨为固定值,布局时不会⼩于该值。
    • QSizePolicy::Preferred : 控件的理想尺⼨为固定值,布局时会尽量接近该值。
    • QSizePolicy::Expanding : 控件的尺⼨可以根据空间调整,尽可能占据更多空间。
    • QSizePolicy::Shrinking : 控件的尺⼨可以根据空间调整,尽可能缩⼩以适应空间。
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QPushButton>
#include<QGridLayout>

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

    //创建6个按钮
    QPushButton* btn1 = new QPushButton("按钮1");
    QPushButton* btn2 = new QPushButton("按钮2");
    QPushButton* btn3 = new QPushButton("按钮3");
    QPushButton* btn4 = new QPushButton("按钮4");
    QPushButton* btn5 = new QPushButton("按钮5");
    QPushButton* btn6 = new QPushButton("按钮6");

    //设置按钮的sizePolicy,此时按钮的水平方向和垂直方向都会尽量舒展开
    btn1->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    btn2->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    btn3->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    btn4->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    btn5->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
    btn6->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);

    //创建网格布局管理器,并且添加元素
    QGridLayout* layout = new QGridLayout();
    layout->addWidget(btn1,0,0);
    layout->addWidget(btn2,0,1);
    layout->addWidget(btn3,1,0);
    layout->addWidget(btn4,1,1);
    layout->addWidget(btn5,2,0);
    layout->addWidget(btn6,2,1);
    //设置拉伸比例
    //第0行拉伸比例设为1
    layout->setRowStretch(0,1);
    //第1行拉伸比例设为1
    layout->setRowStretch(1,1);
    //第2行拉伸比例设为3,即为第2行的宽度(垂直方向)是第0行的3倍
    layout->setRowStretch(2,3);

    //设置layout到窗口中
    this->setLayout(layout);

}
Widget::~Widget()
{
    delete ui;
}
  1. 执⾏代码, 观察效果.
    此时的按钮垂直⽅向都舒展开了. 并且调整窗⼝尺⼨, 也会按照设定的⽐例同步变化.

    总的来说, 使⽤ QGridLayout 能够代替很多QHBoxLayout和 QVBoxLayout 嵌套的场景. 毕竟嵌套的代码写起来是⽐较⿇烦的.

另外不要忘了, QGridLayout ⾥⾯也能嵌套 QHBoxLayout 和 QVBoxLayout ,

QHBoxLayout 和 QVBoxLayout ⾥⾯也能嵌套 QGridLayout .

灵活使⽤上述布局管理器, 就可以实现出任意的复杂界⾯.

2.4 表单布局

除了上述的布局管理器之外, Qt 还提供了 QFormLayout , 属于是QGridLayout的特殊情况, 专

⻔⽤于实现两列表单的布局.

这种表单布局多⽤于让⽤⼾填写信息的场景. 左侧列为提⽰, 右侧列为输⼊框.

代码⽰例: 使⽤ QFormLayout 创建表单.

  1. 编写代码, 创建 QFormLayout , 以及三个 label 和三个 lineEdit
    • 使⽤addRow⽅法来添加⼀⾏. 每⾏包含两个控件. 第⼀个控件固定是 QLabel / ⽂本, 第⼆个控件则可以是任意控件.
    • 如果把第⼀个参数填写为 NULL, 则什么都不显⽰.
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QFormLayout>
#include<QLineEdit>
#include<QLabel>
#include<QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //1.创建一个FormLayout
    QFormLayout* layout = new QFormLayout(this);

    //创建三个label
    QLabel* label1 = new QLabel("姓名");
    QLabel* label2 = new QLabel("年龄");
    QLabel* label3 = new QLabel("电话");
    
    //创建三个lineEdit
    QLineEdit* lineEdit1 = new QLineEdit();
    QLineEdit* lineEdit2 = new QLineEdit();
    QLineEdit* lineEdit3 = new QLineEdit();

    //创建一个提交按钮
    QPushButton* commit = new QPushButton("提交");
    //把上述元素添加到layout中
    layout->addRow(label1,lineEdit1);
    layout->addRow(label2,lineEdit2);
    layout->addRow(label3,lineEdit3);
    layout->addRow(NULL,commit);
}
Widget::~Widget()
{
    delete ui;
}
  1. 执⾏程序, 可以看到以下结果.

2.5 Spacer

使⽤布局管理器的时候, 可能需要在控件之间, 添加⼀段空⽩. 就可以使⽤ QSpacerItem 来表⽰.

核⼼属性

代码⽰例: 创建⼀组左右排列的按钮.

  1. 在界⾯上创建⼀个QVBoxLayout, 并添加两个按钮.
cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QHBoxLayout>
#include<QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //创建一个水平布局器并添加两个按钮
    QHBoxLayout* layout = new QHBoxLayout(this);
    QPushButton* btn1 = new QPushButton("按钮1");
    QPushButton* btn2 = new QPushButton("按钮2");
    layout->addWidget(btn1);
    layout->addWidget(btn2);
}
Widget::~Widget()
{
    delete ui;
}
  1. 直接运⾏程序, 可以看到两个按钮是紧挨着的.

  2. 在两个按钮中间添加⼀个 spacer

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QHBoxLayout>
#include<QPushButton>
#include<QSpacerItem>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //创建一个水平布局器
    QHBoxLayout* layout = new QHBoxLayout(this);

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

    //创建一个SpacerItem
    QSpacerItem* spacer = new QSpacerItem(200,20);

    layout->addWidget(btn1);
    //在两个widget中间添加空白
    layout->addSpacerItem(spacer);
    layout->addWidget(btn2);
}
Widget::~Widget()
{
    delete ui;
}
  1. 运⾏程序, 观察代码效果. 可以看到两个按钮之间已经存在了间隔了.
    调整QSpacerItem 不同的尺⼨, 即可看到不同的间距.

在 Qt Designer 中, 也可以直接给界⾯上添加spacer.

📝 3.小结

本文主要介绍了 Qt 中两类重要的界面组织工具:容器类控件 和 布局管理器。

容器类控件 用于把多个控件组织在一起,形成逻辑上的分组:

  • QGroupBox:带标题的分组框,用于美化界面、把相关控件归为一组,不涉及业务逻辑。
  • QTabWidget:带标签页的控件,每个标签页都是一个 QWidget,通过切换标签页来展示不同的内容,并提供了丰富的信号(如 currentChanged、tabBarClicked 等)。

布局管理器 用于自动计算控件的位置和尺寸,让界面能够自适应窗口大小变化:

  • QVBoxLayout:垂直布局,控件从上到下排列。
  • QHBoxLayout:水平布局,控件从左到右排列。
  • QGridLayout:网格布局,按 M × N 的行列方式排列,可设置行列拉伸系数。
  • QFormLayout:表单布局,专用于两列表单(左侧提示、右侧输入)。
  • QSpacerItem:在控件之间添加空白间隔。

核心要点:

  • 一个 widget 只能包含一个 layout。
  • 布局管理器可以互相嵌套(addLayout),实现复杂界面。
  • 通过 setColumnStretch / setRowStretch 设置拉伸比例,配合 sizePolicy 实现控件尺寸的动态调整。
  • 使用布局管理器后,控件的位置和尺寸由系统自动计算,无需手动设置坐标。

🎯 4.经典面试题

1. QGroupBox 和 QButtonGroup 有什么区别?

答案: QGroupBox 是一个带标题的分组框,用于在界面上把相关控件组织在一起,起到美化界面的作用,本身不涉及业务逻辑。而 QButtonGroup 是一个逻辑分组工具,用于管理一组互斥的按钮(如单选按钮),它不负责界面显示,只负责按钮之间的互斥逻辑。

2. QTabWidget 中如何动态添加和删除标签页?

答案: 使用 addTab(widget, title) 方法可以新增标签页,其中第一个参数是要添加的 QWidget,第二个参数是标签页的标题;使用 removeTab(index) 方法可以删除指定下标的标签页。可以通过 count() 获取当前标签页的总数,通过 currentIndex() 获取当前选中的标签页下标。

3. QTabWidget 的 currentChanged(int) 信号在什么时候触发?

答案: 当用户切换标签页时触发,参数 int 表示被切换到的标签页的编号(从 0 开始)。可以通过连接该信号来响应标签页的切换事件,例如更新界面状态或加载对应页面的数据。

4. 什么是布局管理器?为什么需要它?

答案: 布局管理器是 Qt 中用于自动管理控件位置和尺寸的机制。在没有布局管理器时,控件需要通过绝对定位(setGeometry 或 move)手动设置坐标,这种方式在窗口大小变化时无法自适应。布局管理器可以根据窗口大小自动调整控件的位置和尺寸,使界面更加灵活和美观。

5. QVBoxLayout 和 QHBoxLayout 有什么区别?

答案: QVBoxLayout 是垂直布局管理器,控件从上到下依次排列;QHBoxLayout 是水平布局管理器,控件从左到右依次排列。两者的核心属性(如边距、间距)是一致的,只是排列方向不同。

6. 一个 widget 可以设置多个布局管理器吗?

答案: 不可以。一个 widget 只能包含一个布局管理器。如果需要在一个窗口中实现多个布局区域,可以通过嵌套布局的方式实现,即在一个布局管理器中再添加子布局管理器(addLayout),或者使用 QGridLayout 来划分多个区域。

7. QGridLayout 中如何设置不同列或行的宽度/高度比例?

答案: 使用 setColumnStretch(column, stretch) 方法设置某一列的拉伸系数,使用 setRowStretch(row, stretch) 方法设置某一行的拉伸系数。拉伸系数表示该列/行在窗口缩放时占据空间的比例。例如,设置第 0 列拉伸系数为 1、第 2 列拉伸系数为 3,则第 2 列的宽度是第 0 列的 3 倍。

8. QSizePolicy::Expanding 的作用是什么?

答案: QSizePolicy::Expanding 表示控件的尺寸可以根据可用空间进行调整,尽可能占据更多的空间。当配合布局管理器的拉伸系数使用时,可以让控件在窗口缩放时动态地填充剩余空间,实现自适应布局。

9. QFormLayout 适用于什么场景?如何使用?

答案: QFormLayout 适用于两列表单布局的场景,例如用户填写信息的界面。左侧列通常放置提示文本(QLabel),右侧列放置输入控件(如 QLineEdit)。使用 addRow(label, field) 方法添加一行,其中第一个参数是提示文本,第二个参数是输入控件;如果第一个参数传 NULL,则该行只显示右侧控件。

10. QSpacerItem 的作用是什么?如何创建?

答案: QSpacerItem 用于在布局管理器的控件之间添加一段空白间隔,起到分隔控件的作用。可以通过 new QSpacerItem(width, height) 创建,然后使用 addSpacerItem(spacer) 方法将其添加到布局管理器中。调整其宽高参数即可控制间隔的大小。

相关推荐
elseif1232 小时前
【2026 CSP-J】【逐题精讲】超详细(15道选择,3道阅读,2道完善)
java·开发语言·c++·算法·csp
智鸟科技GemeOpen开发者智能设备2 小时前
MQTT智能插座GSPM1B2 · 开发者实战指南
java·开发语言·python·物联网
Fluxproxy2 小时前
Python BeautifulSoup4 超全实战教程|解决网页乱码、标签匹配、层级查找、数据提取难题
开发语言·python
菜鸟~noob2333 小时前
【太空电子战】午夜锤行动中的电磁静默区:可逆干扰的实战验证【含matlab代码】
linux·开发语言·matlab·电子战
坤坤子吖3 小时前
Python基础语法学习:条件、循环与程序控制
开发语言·笔记·python·学习
ShineWinsu3 小时前
对于Redis:ZSet类型的解析
数据库·c++·redis·缓存·面试·有序集合·zset
吠品3 小时前
volatile到底在拦什么:SysTick->VAL读的哪格
java·服务器·开发语言
王铭诗3 小时前
QT界面安装
qt