目录
前言
书接上文【Qt】常用控件----多元素控件(QListWidget、QTableWidget、QTreeWidget),今天继续介绍【Qt】常用控件----容器类控件(QGroupBox、QTabWidget )以及布局管理器
一、多元素控件和容器类控件区别
- 多元素控件:包含的内容是一个一个自定义好的"Item"对象
- 容器类控件:包含的内容是前面我们学习的各种控件(QPushButton、QLineEdit等)
二、容器类控件
1、Group Box
- 使用QGroupBox 实现一个带有标题的分组框。可以把其他的控件放到里面作为⼀组,这样看起来能更好看一点
- 分组框只是一个用来美化界面 这样的组件,并不涉及到用户交互和业务逻辑。属于"锦上添花"

核心属性
| 属性 |
说明 |
| title |
分组框的标题 |
| alignment |
分组框内部内容的对齐方式 |
| flat |
是否是"扁平"模式 |
| checkable |
是否可选择。设为true则在title前方会多出一个可勾选的部分 |
| checked |
描述分组框的选择状态(前提是checkable为true) |
给麦当劳案例加上分组框
- 在界面上创建三个分组框,并且在分组框内部创建下拉框和微调框

- 初始化下拉框
cpp
复制代码
ui->comboBox->addItem("巨⽆霸");
ui->comboBox->addItem("⻨辣鸡腿堡");
ui->comboBox_2->addItem("薯条");
ui->comboBox_2->addItem("⻨辣鸡翅");
ui->comboBox_3->addItem("可乐");
ui->comboBox_3->addItem("雪碧");
- 使用QTabWidget 实现一个带有标签页的控件,可以往里面添加一些widget。进一步的就可以通过标签页来切换
- QTabWidget 中的每个标签页都是一个QWidget

- 如果想要插入标签页,可以右键->插入页


- 删除页:右键->3的页3->删除

核心属性
| 属性 |
说明 |
| tabPosition |
标签页所在的位置:North 上方;South 下方;West 左侧;East 右侧 |
| currentIndex |
当前选中了第几个标签页(从0开始计算) |
| currentTabText |
当前选中的标签页的文本 |
| currentTabName |
当前选中的标签页的名字 |
| currentTabIcon |
当前选中的标签页的图标 |
| currentTabToolTip |
当前选中的标签页的提示信息 |
| tabsCloseable |
标签页是否可以关闭 |
| movable |
标签页是否可以移动 |
核心信号
| 信号 |
说明 |
| currentChanged(int) |
在标签页发生切换时触发,参数为被点击的选项卡编号 |
| tabBarClicked(int) |
在点击选项卡的标签条的时候触发。参数为被点击的选项卡编号 |
| tabBarDoubleClicked(int) |
在双击选项卡的标签条的时候触发。参数为被点击的选项卡编号 |
| tabCloseRequest(int) |
在标签页关闭时触发。参数为被关闭的选项卡编号 |
使用标签页管理多组控件
- 创建一个QTabWidget标签页和两个按钮(分别是新建标签页按钮和删除当前标签页按钮)。

- 给标签页中添加一个label
cpp
复制代码
//添加标签
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);
- 首先获取标签页个数
- 使用addTab 新增标签页(参数1:QWidget对象,参数2:标签页页名。同时在标签页中添加label标签,使用setCurrentIndex 切换当前标签页)
- 使用removeTab 删除标签页:使用currentIndex 获取到当前标签页的下标
cpp
复制代码
void Widget::on_pushButton_clicked()
{
//插入标签页使用addTab
//参数1 QWidget
//参数2 标签页名text,Tab + 数字
int count = ui->tabWidget->count();
QWidget* widget = new QWidget();
ui->tabWidget->addTab(widget, QString("Tab") + QString::number(count + 1));
//添加QLabel
QLabel* label = new QLabel(widget);
label->setText("标签" + 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);
}
三、布局管理器
- 之前使用Qt在界面上创建的控件,都是通过"绝对定位"的方式来设定的。也就是每个控件所在的位置,都需要计算坐标,最终通过setGeometry 或者move 方式摆放过去
- 这种设定方式其实并不方便。尤其是界面如果内容比较多,不好计算。而且一个窗口大小往往是可以调整的,按照绝对定位的方式,也无法自适应窗口大小
- 因此Qt引入"布局管理器"(Layout)机制来解决上述问题
1、垂直布局(QVBoxLayout)
- 使用QVBoxLayout 表示垂直的布局管理器。V是vertical的缩写
核心属性
| 属性 |
说明 |
| layoutLeftMargin |
左侧边距 |
| layoutRightMargin |
右侧边距 |
| layoutTopMargin |
上方边距 |
| layoutBottomMargin |
下方边距 |
| layoutSpacing |
相邻元素之间的间距 |
使用QVBoxLayout 管理多个控件
- 编写代码创建布局管理器和三个按钮,并且把按钮添加到布局管理器中
- 使用addWidget 把控件添加到布局管理器中
- 使用setLayout 设置该布局管理器到widget中
- 下面代码我们可以发现在创建button1和layout 的时候,我们没有直接指定其父元素,后面我们使用addWidget 和setLayout 就是在将其挂在对象树上
cpp
复制代码
//创建3个按钮,使用垂直布局管理器管理起来
QPushButton* button1 = new QPushButton("按钮1");
QPushButton* button2 = new QPushButton("按钮2");
QPushButton* button3 = new QPushButton("按钮3");
//创建布局管理器
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
//将布局管理器添加到窗口
this->setLayout(layout);
- 通过上述代码的方式,只能给这个widget设定一个布局管理器。实际上也可以通过Qt Design在一个窗口中创建多个布局管理器
窗口中创建多个布局管理器(创建两个QVBoxLayout)
- 在界面上创建两个QVBoxLayout ,每个QVBoxLayout 各放三个按钮

- 运行程序可以看到这些按钮已经自动排列好。只不过当前这些按钮的位置不能随着窗口大小自动变化
- 通过Qt Designer创建的布局管理器,其实是先创建了一个widget,设置过geometry属性的。再把这个layout设置到这个widget中
- 实际上一个widget只能包含一个layout
- 打开ui文件的原始xml,可以看到其中的端倪

2、水平布局(QHBoxLayout)
- 使用QHBoxLayout 表示垂直的布局管理器。H是horizontal 的缩写
核心属性(和QVBoxLayout完全一样)
| 属性 |
说明 |
| layoutLeftMargin |
左侧边距 |
| layoutRightMargin |
右侧边距 |
| layoutTopMargin |
上方边距 |
| layoutBottomMargin |
下方边距 |
| layoutSpacing |
相邻元素之间的间距 |
使用QHBoxLayout管理控件
- 编写代码创建布局管理器和三个按钮,并且把按钮添加到布局管理器中(和QVBoxLayout操作完全一样)
cpp
复制代码
QPushButton* button1 = new QPushButton("按钮1");
QPushButton* button2 = new QPushButton("按钮2");
QPushButton* button3 = new QPushButton("按钮3");
QHBoxLayout* layout = new QHBoxLayout();
layout->addWidget(button1);
layout->addWidget(button2);
layout->addWidget(button3);
this->setLayout(layout);
Layout里面可以再嵌套上其他的layout,从而达到更复杂的布局效果
嵌套的layout
- 使用
addLayout 给layout中添加子layout
cpp
复制代码
QPushButton* button1 = new QPushButton("按钮1");
QPushButton* button2 = new QPushButton("按钮2");
//父layout
QVBoxLayout* layParent = new QVBoxLayout();
layParent->addWidget(button1);
layParent->addWidget(button2);
this->setLayout(layParent);
QPushButton* button3 = new QPushButton("按钮3");
QPushButton* button4 = new QPushButton("按钮4");
//子layout
QHBoxLayout* layChild = new QHBoxLayout();
layChild->addWidget(button3);
layChild->addWidget(button4);
layParent->addLayout(layChild);
3、网格布局(QGridLayout)
Qt中还提供了QGridLayout 用来实现网格布局的效果。可以达到M*N的这种网格的效果
核心属性
- 整体和QVBoxLayout 以及QHBoxLayout 相似。但是设置spacing的时候是按照垂直水平两个方向来设置的
| 属性 |
说明 |
| layoutLeftMargin |
左侧边距 |
| layoutRightMargin |
右侧边距 |
| layoutTopMargin |
上方边距 |
| layoutBottomMargin |
下方边距 |
| layoutHorizontalSpacing |
相邻元素之间水平方向的间距 |
| layoutVerticalSpacing |
相邻元素之间垂直方向的间距 |
| layoutRowStretch |
行方向的拉伸系数 |
| layoutColumnStretch |
列方向的拉伸系数 |
使用QGridLayout管理元素
- 代码中创建QGridLayout 和4个按钮
- 使用addWidget添加控件到布局管理器中,但是添加的同时会指定两个坐标,表示放在第几行第几列
cpp
复制代码
QPushButton* button1 = new QPushButton("按钮1");
QPushButton* button2 = new QPushButton("按钮2");
QPushButton* button3 = new QPushButton("按钮3");
QPushButton* button4 = new QPushButton("按钮4");
QGridLayout* layout = new QGridLayout();
layout->addWidget(button1, 0, 0);
layout->addWidget(button2, 0, 1);
layout->addWidget(button3, 1, 0);
layout->addWidget(button4, 1, 1);
this->setLayout(layout);
- 当QGridLayout的addWidget中的行是一样的,就相当于QHBoxLayout;列相同就相当于QVBoxLayout
- 此处也要注意,设置行和列的时候,如果设置的是一个很大的值,但是这个值和上一个值之间并没有其他的元素,那么并不会在中间腾出额外的空间
设置QGridLayout中元素的大小比例
- 上面我们创建的布局管理器的控件尺寸都是均等的
- 当我们需要创建尺寸不同的控件的时候,就可以通过拉伸系数来设置。拉伸系数就相当于设置控件之间尺寸的"比例"
水平拉伸系数
cpp
复制代码
QPushButton* button1 = new QPushButton("按钮1");
QPushButton* button2 = new QPushButton("按钮3");
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);
- 设置水平拉伸系数
- 下面代码的含义:三列按照1:1:2的方式来设置
- 如果拉伸系数设为0,意思是不参与拉伸,尺寸是固定值
cpp
复制代码
//设置水平拉伸系数
layout->setColumnStretch(0, 1);
layout->setColumnStretch(1, 1);
layout->setColumnStretch(2, 2);
垂直拉伸系数
cpp
复制代码
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, 1, 0);
layout->addWidget(button4, 1, 1);
layout->addWidget(button5, 2, 0);
layout->addWidget(button6, 2, 1);
this->setLayout(layout);
- 直接设置setRowStretch 效果不明显。因为每个按钮的高度是固定的(垂直方向默认是没有拉伸的,水平方向默认是拉伸的)。需要把按钮的垂直方向的sizePolicy
属性设置为QSizePolicy::Expanding 尽可能填充满布局管理器,才能看到效果
使用setSizePolicy 设置按钮的尺寸策略,可选的值如下:
QSizePolicy::Ignored :忽略控件的尺寸,不对布局产生影响
QSizePolicy::Minimum :控件的最小尺寸为固定值,布局时不会超过该值
QSizePolicy::Maximum :控件的最大尺寸为固定值,布局时不会小于该值
QSizePolicy::Preferred :控件的理想尺寸为固定值,布局时会尽量接近该值
QSizePolicy::Expanding :控件的尺寸可以根据空间调整,尽可能占据更多空间
QSizePolicy::Shrinking :控件的尺寸可以根据空间调整,尽可能缩小以适应空间
cpp
复制代码
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);
- 设置垂直拉伸系数
cpp
复制代码
//设置垂直拉伸系数
layout->setRowStretch(0, 1);
layout->setRowStretch(1, 1);
layout->setRowStretch(2, 2);
- 总的来说,使用QGridLayout 能够代替很多QHBoxLayout 和QVBoxLayout 嵌套的场景
- 除了上述的布局管理器之外,Qt还提供了QFormLayout ,属于是QGridLayout 的特殊情况,专们用于实现两列表单的布局
- 这种表单布局多用于让用户填写信息的场景(填表、调查问卷)。左侧列为提示,右侧列为输入框
- 编写代码创建QFormLayout 以及三个label和三个lineEdit,一个提交按钮控件
- 使用addRow方法来添加一行。每行包含两个控件:第⼀个控件固定是QLabel/文本,第二个控件则可以是任意控件
cpp
复制代码
QFormLayout* layout = new QFormLayout();
this->setLayout(layout);
QLabel* label1 = new QLabel("姓名");
QLabel* label2 = new QLabel("年龄");
QLabel* label3 = new QLabel("电话");
QLineEdit* edit1 = new QLineEdit();
QLineEdit* edit2 = new QLineEdit();
QLineEdit* edit3 = new QLineEdit();
QPushButton* button = new QPushButton("提交");
//上述控件添加到表单中
layout->addRow(label1, edit1);
layout->addRow(label2, edit2);
layout->addRow(label3, edit3);
layout->addRow(nullptr, button);
5、Spacer(QSpacerItem)
使用布局管理器的时候,可能需要在控件之间添加⼀段空白,就可以用QSpacerItem 来表示
核心属性(在构造函数设置即可)
| 属性 |
说明 |
| width |
宽度 |
| height |
高度 |
| hData |
水平方向的sizePolicy :QSizePolicy::Ignored :忽略控件的尺寸,不对布局产生影响;QSizePolicy::Minimum:控件的最小尺寸为固定值,布局时不会超过该值;QSizePolicy::Maximum :控件的最大尺寸为固定值,布局时不会小于该值;QSizePolicy::Preferred :控件的理想尺寸为固定值,布局时会尽量接近该值;QSizePolicy::Expanding :控件的尺寸可以根据空间调整,尽可能占据更多空间;QSizePolicy::Shrinking:控件的尺寸可以根据空间调整,尽可能缩小以适应空间 |
| vData |
垂直方向的sizePolicy(选项同上) |
创建一组左右排列的按钮
- 在界面上创建一个QHBoxLayout ,并添加两个按钮
cpp
复制代码
QHBoxLayout* layout = new QHBoxLayout();
this->setLayout(layout);
QPushButton* button1 = new QPushButton("按钮1");
QPushButton* button2 = new QPushButton("按钮2");
layout->addWidget(button1);
layout->addWidget(button2);
}
- 上面两个水平排列的按钮之间距离很近,使用QSpacerItem使两个按钮之间存在空白
layout->addSpacerItem(spacer)代码位置会影响添加空白的位置,现在我们需要在两个按钮之间,因此在两个按钮之间添加到layout上,如果是在button1前面添加,则要在button1添加到layout管理布局器之前添加
cpp
复制代码
//创建spacer,使两个按钮之间存在空白
QSpacerItem* spacer = new QSpacerItem(200, 20);
layout->addWidget(button1);
layout->addSpacerItem(spacer);
layout->addWidget(button2);
- 在Qt Designer中,也可以直接给界面上添加spacer
