目录
[1:Group Box](#1:Group Box)
[2:Tab Widget](#2:Tab Widget)
一:多控件元素
Qt中提供的多元素控件有:
- QListWidget.
- QListView.
- QTableWidget.
- QTableView.
- QTreeWidget.
- QTreeView.

xxWidget与xxView之间的区别
- xxView是更底层的实现,xxWidget是基于xxView封装而来的.
- 此处的xxView是MVC结构的一种典型实现,M指的是model(数据),V指的是view(视图即界面),C指的是controller(控制器,即数据与视图之间的业务流程).
- 此处的xxView只是负责实现了视图,不负责数据如何存储表示,更不负责数据和视图之间的交互,因此如果使用xxView就需要我们自己实现model和controller的部分.
博主再以QTableWidget和QTableView为例讲讲二者的区别
- QTableView 是基于 MVC 设计的控件. QTableView自身不持有数据. 使用QTableView 的时候需要用户创建⼀个 Model 对象 (比如QStandardModel ), 并且把 Model 和QTableView 关联起来. 后续修改 Model 中的数据就会影响 QTableView 的显示; 修改
QTableView 的显示也会影响到 Model 中的数据(双向绑定).
- QTableWidget是QTableView的子类,对Model进行了封装.不需要用户手动创建Model对象,直接就可以QTableWidget中添加数据了.

1:ListWidget
使用QListWidget 能够显示⼀个纵向的列表. 形如:

每个选项都可以被选中.
核心属性
|-------------------|---------------|
| 属性 | 说明 |
| currentRow | 当前被选中的是第几行. |
| count | ⼀共有多少行. |
| sortingEnabled | 是否允许排序. |
| isWrapping | 是否允许换行. |
| itemAlignment | 元素的对齐⽅式. |
| selectRectVisible | 被选中的元素矩形是否可见. |
| spacing | 元素之间的间隔. |
核心方法
|---------------------------------------------------------------------------------------------|---------------------------------------------|
| ⽅法 | 说明 |
| addItem(const QString & label) addItem(QListWidgetItem * item) | 列表中添加元素. |
| currentItem() | 返回QListWidgetItem * 表示当前选中的元素. |
| setCurrentItem(QListWidgetItem * item) | 设置选中哪个元素. |
| setCurrentRow(int row) | 设置选中第几行的元素. |
| insertItem(const QString& label, int row) insertItem(QListWidgetItem * item, int row) | 在指定的位置插入元素 |
| item(int row) | 返回 QListWidgetItem* 表示第 row 行的元素 |
| takeItem(int row) | 删除指定行的元素, 返回QListWidgetItem* 表示是哪个元素被删 除了. |
核心信号
|----------------------------------------------------------------------|----------------------------------|
| 方法 | 说明 |
| currentItemChanged(QListWidgetItem* current, QListWidgetItem* old) | 选中不同元素时会触发,参数是当前选中的元素之和和之前选中的元素. |
| currentRowChanged(int) | 选中不同元素时会触发. 参数是当前选中元素的行数. |
| itemClicked(QListWidgetItem* item) | 点击某个元素时触发 |
| itemDoubleClicked(QListWidgetItem* item) | 双击某个元素时触发 |
| itemEntered(QListWidgetItem * item) | 鼠标进入元素时触发 |
在上述介绍中, 涉及到⼀个关键的类, QListWidgetItem . 这个类表示QListWidget 中的⼀个元素. 核心方法如下, 本质上就是⼀个 "文本+图标"构成的.
|------------------|----------|
| 方法 | 说明 |
| setFont | 设置字体 |
| setIcon | 设置图标 |
| setHidden | 设置隐藏 |
| setSizeHint | 设置尺寸 |
| setSelected | 设置是否选中 |
| setText | 设置文本 |
| setTextAlignment | 设置文本对齐方式 |
1.1:代码示例
- 在界⾯上创建⼀个 ListView , 右键 => 变形为 => ListWidget , 再创建⼀个 lineEdit 和 两个按钮.
注意: ListWidget 是 ListView 的子类, 功能比ListView 更丰富. 咱们使用ListWidget 即可.

编写 widget.cpp, 在构造函数中添加初始元素,这里需要给widget.h前面加上#include <QListWidgetItem>
widget.h
cpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QListWidgetItem>
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
void on_pushButton_clicked();
void on_pushButton_2_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
widget.cpp
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->listWidget->addItem("C++");
ui->listWidget->addItem("C");
ui->listWidget->addItem("Java");
ui->listWidget->addItem("Python");
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
{
if(current != NULL && previous != NULL)
qDebug() << "当前选中: " << current->text() << " 之前选中: "<<previous->text();
}
void Widget::on_pushButton_clicked()
{
const QString & text = ui->lineEdit->text();
if(text.isEmpty())
return;
ui->listWidget->addItem(text);
}
void Widget::on_pushButton_2_clicked()
{
//当前被选中的是第几行.
int row = ui->listWidget->currentRow();
ui->listWidget->takeItem(row);
}
执行程序, 观察效果. 可以新增元素, 选中元素, 删除元素.



2:TbaleWidget
使用QTableWidget表示 ⼀个表格控件. ⼀个表格中包含若干行, 每⼀行又包含若干列. 表格中的每个单元格, 是⼀个 QTableWidgetItem 对象.
QTableWidget核心方法.
|-----------------------------------------------------|--------------------------------|
| 方法 | 说明 |
| item(int row, int column) | 根据行数列数获取指定的 QTableWidgetItem* |
| setItem(int row,int column,QTableWidget*) | 根据行数列数设置表格中的元素 |
| currentItem() | 返回被选中的元素QTableWidgetItem* |
| currentRow() | 返回被选中元素是第几行 |
| currentColumn() | 返回被选中元素是第几列 |
| row(QTableWidgetItem* ) | 获取指定 item 是第几行 |
| column(QTableWidgetItem* ) | 获取指定 item 是第几列 |
| rowCount() | 获取行数 |
| columnCount | 获取列数 |
| insertRow(int row) | 在第row行处插入新行 |
| insertColumn(int column) | 在第column列插入新列 |
| removeRow(int row) | 删除第row行 |
| removeColumn(int column) | 删除第column列 |
| setHorizontalHeaderItem(int column, QTableWidget*) | 设置指定列的表头 |
| setVerticalHeaderItem(int row, QTableWidget*) | 设置指定行的表头 |
QTableWidgetItem 核心 信号.
|---------------------------------------------------------------------------|------------|
| 信号 | 说明 |
| cellClicked(int row,int column) | 点击单元格时触发 |
| cellDoubleClicked(int row, int column) | 双击单元格时触发 |
| cellEntered(int row, int column) | 鼠标进入单元格时触发 |
| currentCellChanged(int row,int column,int previousRow,int previousColumn) | 选中不同单元格时触发 |
QTableWidgetItem 核心方 法.
|-----------------------------|-----------|
| 方法 | 说明 |
| row() | 获取当前是第几行. |
| column() | 获取当前是第几列. |
| setText(const QString&) | 设置⽂本 |
| setTextAlignment(int) | 设置⽂本对⻬ |
| setIcon(const QIcon&) | 设置图标 |
| setSelected(bool) | 设置被选中 |
| setSizeHints(const QSize&) | 设置尺寸 |
| setFont(const QFont&) | 设置字体 |
2.1:代码示例
- 在界面上创建 QTableWidget 和 三个按钮, ⼀个输入框.
- 注意: QTableWidget 是 QTableView 的子类, 功能比QTableView 更丰富. 咱们使用
QTableWidget 即可.

widget.cpp
cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建3行
ui->tableWidget->insertRow(0);
ui->tableWidget->insertRow(1);
ui->tableWidget->insertRow(2);
//创建3列
ui->tableWidget->insertColumn(0);
ui->tableWidget->insertColumn(1);
ui->tableWidget->insertColumn(2);
//设置列名
ui->tableWidget->setHorizontalHeaderItem(0,new QTableWidgetItem("学号"));
ui->tableWidget->setHorizontalHeaderItem(1,new QTableWidgetItem("姓名"));
ui->tableWidget->setHorizontalHeaderItem(2,new QTableWidgetItem("年龄"));
//设置初始数据
ui->tableWidget->setItem(0,0,new QTableWidgetItem("1001"));
ui->tableWidget->setItem(0,1,new QTableWidgetItem("张三"));
ui->tableWidget->setItem(0,2,new QTableWidgetItem("20"));
ui->tableWidget->setItem(1,0,new QTableWidgetItem("1002"));
ui->tableWidget->setItem(1,1,new QTableWidgetItem("李四"));
ui->tableWidget->setItem(1,2,new QTableWidgetItem("18"));
ui->tableWidget->setItem(2,0,new QTableWidgetItem("1003"));
ui->tableWidget->setItem(2,1,new QTableWidgetItem("王五"));
ui->tableWidget->setItem(2,2,new QTableWidgetItem("28"));
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_AddRow_clicked()
{
//获取行号
int RowCount = ui->tableWidget->rowCount();
//插入行
ui->tableWidget->insertRow(RowCount);
}
void Widget::on_pushButton_DeleteRow_clicked()
{
int CurrentRow = ui->tableWidget->currentRow();
ui->tableWidget->removeRow(CurrentRow);
}
void Widget::on_pushButton_AddColumn_clicked()
{
//获取到列数
int colCount = ui->tableWidget->columnCount();
//插入新列
ui->tableWidget->insertColumn(colCount);
//设置列名
const QString & name = ui->lineEdit->text();
ui->tableWidget->setHorizontalHeaderItem(colCount,new QTableWidgetItem(name));
}
void Widget::on_pushButton_DeleteColumn_clicked()
{
int CurrentColumn = ui->tableWidget->currentColumn();
ui->tableWidget->removeColumn(CurrentColumn);
}
执行程序, 即可完成表格的基本操作.

默认情况下, 单元格中的内容直接就是可编辑的. 如果不想让用户编辑, 可以设置 ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
3:TreeWidget
使用QTreeWidget表示 ⼀个树形控件. 李米娜的每个元素, 都是⼀个 QTreeWidgetItem , 每个
QTreeWidgetItem 可以包含多个文本和图标, 每个文本/图标为⼀个 列. 可以给 QTreeWidget 设置顶层节点(顶层节点可以有多个), 然后再给顶层节点添加子节点, 从而构成树形结构.
QTreeWidget核心方法
|----------------------------------------------|-------------------------------------------|
| 方法 | 说明 |
| clear | 清空所有⼦节点 |
| addTopLevelItem(QTreeWidgetItem * Item) | 新增顶层节点 |
| topLevelItem(int index) | 获取指定下标的顶层节点. |
| indexOfTopLevelItem(QTreeWidgetItem * item) | 查询指定节点是顶层节点中的下标 |
| takeTopLevelItem(int index) | 删除指定的顶层节点. 返回 QTreeWidgetItem*表示被删除 的元素. |
| currentItem | 获取到当前选中的节点, 返回 QTreeWidgetItem* |
| setCurrentItem(QTreeWidgetItem* item) | 选中指定节点 |
| setExpanded(bool) | 展开/关闭节点 |
| setHeaderLabel(const QString & text) | 设置 TreeWidget 的 header 名称. |
QTreeWidget核心信号
|-----------------------------------------------------------------------|----------|
| 信号 | 说明 |
| currentItemChanged(QTreeWidgetItem * current,QTreeWidgetItem * old) | 切换选中时触发 |
| itemClicked(QTreeWidgetItem * item,int col) | 点击元素时触发 |
| itemDoubleClicked(QTreeWidgetItem * item,int col) | 双击元素时触发 |
| itemEntered(QTreeWidgetItem * item,int col) | 鼠标进入时触发 |
| itemExpanded(QTreeWidgetItem * item) | 元素被展开时触发 |
| ItemCollpasend(QTreeWidgetItem * item) | 元素被折叠时触发 |
QTreeWidgetItem核心属性
|---------------|--------|
| 属性 | 说明 |
| text | 持有的文本 |
| textAlignment | ⽂本对齐⽅式 |
| icon | 持有的图表 |
| font | ⽂本字体 |
| hidden | 是否隐藏 |
| disabled | 是否禁用 |
| expand | 是否展开 |
| sizeHint | 尺寸大小 |
| selected | 是否选中 |
QTreeWidgetItem核心方法
|--------------------------------------|----------------------------------|
| 方法 | 说明 |
| addChild(QTreeWidgetItem* child) | 新增⼦节点 |
| childCount() | 子节点的个数 |
| child(int index) | 获取指定下标的⼦节点. 返回 QTreeWidgetItem* |
| takeChild(int index) | 删除对应下标的⼦节点 |
| removeChild(QTreeWidgetItem* child) | 删除对应的⼦节点 |
| parent() | 获取该元素的父节点 |
3.1:代码示例
- 在界面上创建⼀个 TreeView , 右键 => 变形为 => TreeWidget , 再创建⼀个 lineEdit 和 两个按钮.
- 注意: TreeWidget是TreeView 的子类, 功能比TreeView更丰富. 咱们使用TreeWidget****即可.

widget.cpp
cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->treeWidget->setHeaderLabel("属性系别");
QTreeWidgetItem * ItemFire = new QTreeWidgetItem();
ItemFire->setText(0,"火系");
//新增顶层节点
ui->treeWidget->addTopLevelItem(ItemFire);
QTreeWidgetItem * ItemIce = new QTreeWidgetItem();
ItemIce->setText(0,"冰系");
//新增顶层节点
ui->treeWidget->addTopLevelItem(ItemIce);
QTreeWidgetItem * ItemWater = new QTreeWidgetItem();
ItemWater->setText(0,"水系");
ui->treeWidget->addTopLevelItem(ItemWater);
//新增顶层节点
QTreeWidgetItem * ItemThunder = new QTreeWidgetItem();
ItemThunder->setText(0,"雷系");
ui->treeWidget->addTopLevelItem(ItemThunder);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_InsertTopLevelItem_clicked()
{
//获取输入框的内容
const QString & text = ui->lineEdit->text();
if(text.isEmpty())
return;
//添加到顶层节点
QTreeWidgetItem * item = new QTreeWidgetItem();
item->setText(0,text);
ui->treeWidget->addTopLevelItem(item);
}
void Widget::on_pushButton_AddItem_clicked()
{
//获取输入框的内容
const QString & text = ui->lineEdit->text();
if(text.isEmpty())
return;
//获取当前选中的节点
QTreeWidgetItem * CurrentItem = ui->treeWidget->currentItem();
if(CurrentItem == NULL)
return;
//构造新的Item
QTreeWidgetItem * NewItem = new QTreeWidgetItem();
NewItem->setText(0,text);
//添加Item到选中节点
CurrentItem->addChild(NewItem);
//展开父节点
CurrentItem->setExpanded(true);
}
void Widget::on_pushButton_DeleteItem_clicked()
{
//获取当前选中的节点
QTreeWidgetItem * CurrentItem = ui->treeWidget->currentItem();
if(CurrentItem == NULL)
return;
//获取选中节点的父节点
QTreeWidgetItem * Parent = CurrentItem->parent();
//如果当前的父亲节点为空,说明是顶层节点
if(Parent == NULL)
{
//获取顶层节点下标
int index = ui->treeWidget->indexOfTopLevelItem(CurrentItem);
ui->treeWidget->takeTopLevelItem(index);
}
else
//非顶层节点
Parent->removeChild(CurrentItem);
}

二:容器类控件
1:Group Box
使用Group Box实现⼀个带有标题的分组框. 可以把其他的控件放到里面作为⼀组. 这样看起来能更好看一点.
注意, 不要把 QGroupBox 和 QButtonGroup 混淆. (之前在介绍 QRadionButton 的时候提
到了 QButtonGroup ).
核心属性
|-----------|-------------------------------------|
| 属性 | 说明 |
| title | 分组框的标题. |
| alignment | 分组框内部内容的对齐方式. |
| flat | 是否是"扁平"模式. |
| checkable | 是否可选择. 设为true,则在title前方会多出一个可勾选的部分. |
| checked | 描述分组的选择状态(前提是checkable为true) |
分组框只是⼀个用来"美化界面" 这样的组件, 并不涉及到用户交互和业务逻辑. 属于 "锦上添
花" .
代码示例1
在界面上创建三个分组框, 并且在分组框内部创建下拉框和微调框.
wiget.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->addItem("麦辣鸡腿堡");
ui->comboBox_2->addItem("薯条");
ui->comboBox_2->addItem("麦辣鸡翅");
ui->comboBox_2->addItem("鸡米花");
ui->comboBox_3->addItem("可乐");
ui->comboBox_3->addItem("雪碧");
ui->comboBox_3->addItem("七喜");
}
Widget::~Widget()
{
delete ui;
}
PS:在复制粘贴控件的时候, ⼀定要先选中对应的父控件, 再粘贴.
运行程序, 观察效果
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 , 可以添加标签页或者删除标签页.
编写 widget.cpp, 进行初始化, 给标签页中放个简单的label注意新创建的 label 的父元素, 是 ui->tab 和 ui->tab_2 . Qt 中使用父子关系决定该控件 "在哪里".
使用 count() 获取到标签页的个数.
使用addTab 新增标签页.
使用removeTab 删除标签页.
使用currentIndex 获取到当前标签页的下标.
使用setCurrentIndex 切换当前标签页.
widget.cpp
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QLabel>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//新创建的Label的父元素是ui->tab和ui->tab_2.Qt中使用父子关系决定该控件"在哪里"
QLabel* Label = new QLabel(ui->tab);
Label->setText("标签页1");
Label->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_Add_clicked()
{
//获取当前有几个标签页
int count = ui->tabWidget->count();
//创建新的标签页
QWidget* Qw = new QWidget();
ui->tabWidget->addTab(Qw,QString("Tab ") + QString::number(count + 1));
//给widget中添加Label
QLabel * Label = new QLabel(Qw);
Label->setText(QString("标签页") + QString::number(count + 1));
Label->resize(100,50);
//切换当前页的下标
ui->tabWidget->setCurrentIndex(count);
}
void Widget::on_pushButton_Remove_clicked()
{
//获取当前页下标
int Index = ui->tabWidget->currentIndex();
ui->tabWidget->removeTab(Index);
}
void Widget::on_tabWidget_currentChanged(int index)
{
qDebug() << "当前选中标签页为:> "<< index + 1;
}
运行程序, 观察效果
- 点击新建标签页, 可以创建出新的标签.
- 点击删除当前标签页, 可以删除标签.
- 切换标签页时, 可以看到 qDebug 打印出的标签页编号.
三:布局管理器
之前使用Qt 在界⾯上创建的控件, 都是通过 "绝对定位" 的方式来设定的.
也就是每个控件所在的位置, 都需要计算坐标, 最终通过 setGeometry 或者 move方式 摆放过去. 这种设定方式其实并不方便. 尤其是界⾯如果内容⽐较多, 不好计算. 而且⼀个窗口大小往往是可以调整的, 按照绝对定位的⽅式, 也无法自适应窗⼝大小. 因此 Qt 引入 "布局管理器" (Layout) 机制, 来解决上述问题.
1:垂直布局
使用QVBoxLayout表示 垂直的布局管理器. V 是 vertical 的缩写.
核心属性
|--------------------|------------|
| 属性 | 说明 |
| layoutLeftMargin | 左侧边距. |
| layoutRightMargin | 右侧边距. |
| layoutTopMargin | 上方边距. |
| layoutBottomMargin | 下方边距. |
| layoutSpacing | 相邻元素之间的间距. |
Layout 只是用于界⾯布局, 并没有提供信号.
代码示例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 * Button1 = new QPushButton("按钮1");
QPushButton * Button2 = new QPushButton("按钮2");
QPushButton * Button3 = new QPushButton("按钮3");
// 创建布局管理器, 并且把按钮添加进去
// 如果创建的时候指定⽗元素为 this, 则后⾯不需要 setLayout ⽅法了
QVBoxLayout * layout = new QVBoxLayout();
layout->addWidget(Button1);
layout->addWidget(Button2);
layout->addWidget(Button3);
this->setLayout(layout);
}
Widget::~Widget()
{
delete ui;
}
运行程序, 可以看到此时界⾯上的按钮就存在于布局管理器中. 随着窗口尺寸变化而发生改变.
此时三个按钮的尺寸和位置, 都是自动计算出来的.

通过上述代码的方式, 只能给这个 widget 设定⼀个布局管理器. 实际上也可以通过 Qt Design 在⼀个窗口中创建多个布局管理器.
代码示例2
在界面上创建两个 QVBoxLayout , 每个 QVBoxLayout 各放三个按钮.

运行程序, 可以看到这些按钮已经自动排列好. 只不过当前这些按钮的位置不能随着窗口变化自动变化.

- 通过 Qt Designer 创建的布局管理器, 其实是先创建了⼀个 widget, 设置过 geometry 属性的. 再把这个 layout 设置到这个 widget 中.
- 实际上, ⼀个 widget 只能包含⼀个 layout.
- 打开ui文件的原始 xml, 可以看到其中的端倪.
- 这种情况下 layout并非是窗口widget 的布局管理器, 因此不会随着窗口大小改变.
XML
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>100</x>
<y>70</y>
<width>151</width>
<height>401</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_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>
<widget class="QWidget" name="verticalLayoutWidget_2">
<property name="geometry">
<rect>
<x>450</x>
<y>70</y>
<width>160</width>
<height>391</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="pushButton_6">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_5">
<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>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
2:水平布局
使用QHBoxLayout表示 垂直的布局管理器. H 是 horizontal 的缩写.
核心属性(和 QVBoxLayout 属性是⼀致的).
|--------------------|-----------|
| 属性 | 说明 |
| layoutLeftMargin | 左侧边距 |
| layoutRightMargin | 右侧边距 |
| layoutTopMargin | 上方边距 |
| layoutBottomMargin | 下方边距 |
| layoutSpacing | 相邻元素之间的间距 |
代码示例1
编写代码, 创建布局管理器和三个按钮. 并且把按钮添加到布局管理器中.
widget.cpp
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QHBoxLayout>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建三个按钮
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);
//设置layout到widget上
this->setLayout(layout);
}
Widget::~Widget()
{
delete ui;
}
运行程序, 可以看到此时界⾯上的按钮就存在于布局管理器中. 随着窗口尺寸变化而发生改变.

代码示例2
使用addLayout给 layout 中添加子layout.
widget.cpp
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QVBoxLayout>
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//建立顶层layout
QVBoxLayout * LayoutParent = new QVBoxLayout();
this->setLayout(LayoutParent);
//添加两个按钮进去
QPushButton * Button1 = new QPushButton("按钮1");
QPushButton * Button2 = new QPushButton("按钮2");
LayoutParent->addWidget(Button1);
LayoutParent->addWidget(Button2);
//创建子layout
QHBoxLayout * LayoutChild = new QHBoxLayout();
QPushButton * Button3 = new QPushButton("按钮3");
QPushButton * Button4 = new QPushButton("按钮4");
LayoutChild->addWidget(Button3);
LayoutChild->addWidget(Button4);
//把这个子layout添加到父layout中
LayoutParent->addLayout(LayoutChild);
}
Widget::~Widget()
{
delete ui;
}
执行程序, 观察结果.

3:网格布局
Qt中还提供了QGridLayout用来实现网格布局的效果,可以达到M*N的这种网格的效果.
核心属性
- 整体和 QVBoxLayout 以及 QHBoxLayout 相似. 但是设置 spacing 的时候是按照垂直水平两个方向来设置的.
|-------------------------|----------------|
| 属性 | 说明 |
| layoutLeftMargin | 左侧边距. |
| layoutRightMargin | 右侧边距. |
| layoutTopMargin | 上方边距. |
| layoutBottomMargin | 下方边距. |
| layoutHorizontalSpacing | 相邻元素之间水平方向的间距. |
| layoutVerticalSpacing | 相邻元素之间垂直方向的间距. |
| layoutRowStretch | 行方向的拉伸系数. |
| layoutColumnStretch | 列方向的拉伸系数. |
代码示例1
代码中创建 QGridLayout 和 4 个按钮.
- 使用addWidget 添加控件到布局管理器中. 但是添加的同时会指定两个坐标. 表示放在第几行, 第几列.
widget.cpp
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);
QPushButton * Button1 = new QPushButton("按钮1");
QPushButton * Button2 = new QPushButton("按钮1");
QPushButton * Button3 = new QPushButton("按钮1");
QPushButton * Button4 = new QPushButton("按钮1");
//创建网格布局管理器,并且添加元素
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到窗口中
this->setLayout(layout);
}
Widget::~Widget()
{
delete ui;
}
执行代码, 观察效果. 可以看到当前的这几个按钮是按照 2行2列的方式排列的.

如果调整行列坐标为下列代码.
cpp
//创建网格布局管理器,并且添加元素
QGridLayout * layout = new QGridLayout();
layout->addWidget(Button1,0,0);
layout->addWidget(Button2,0,1);
layout->addWidget(Button3,0,2);
layout->addWidget(Button4,0,3);
执行代码, 可以看到这几个按钮都在同一行了. 相当于 QHBoxLayout.

如果调整行列坐标为下列代码
cpp
//创建网格布局管理器,并且添加元素
QGridLayout * layout = new QGridLayout();
//相当于QVBoxLayout
layout->addWidget(Button1,1,0);
layout->addWidget(Button2,2,0);
layout->addWidget(Button3,3,0);
layout->addWidget(Button4,4,0);
执行代码, 可以看到这几个按钮都在同⼀列了. 相当于 QVBoxLayout.

任意调整行列, 即可看到不同的效果.
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);
QPushButton * Button1 = new QPushButton("按钮1");
QPushButton * Button2 = new QPushButton("按钮1");
QPushButton * Button3 = new QPushButton("按钮1");
QPushButton * Button4 = new QPushButton("按钮1");
layout->addWidget(Button1,0,0);
layout->addWidget(Button2,1,1);
layout->addWidget(Button3,2,2);
layout->addWidget(Button4,3,3);
//设置layout到窗口中
this->setLayout(layout);
}
Widget::~Widget()
{
delete ui;
}

编写代码形如
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);
QPushButton * Button1 = new QPushButton("按钮1");
QPushButton * Button2 = new QPushButton("按钮1");
QPushButton * Button3 = new QPushButton("按钮1");
QPushButton * Button4 = new QPushButton("按钮1");
//创建网格布局管理器,并且添加元素
QGridLayout * layout = new QGridLayout();
layout->addWidget(Button1,0,0);
layout->addWidget(Button2,1,0);
layout->addWidget(Button3,2,0);
layout->addWidget(Button4,10,0);
//设置layout到窗口中
this->setLayout(layout);
}
Widget::~Widget()
{
delete ui;
}
- 此处也要注意, 设置行和列的时候, 如果设置的是⼀个很大的值, 但是这个值和上⼀个值之间并没有其他的元素, 那么并不会在中间腾出额外的空间.
- 虽然把Button4设置在第10行, 但是由于 3-9行没有元素. 因此 Button4仍然会紧挨在 Button3下方. 看起来和上面的 0 1 2 3 的情况是相同的.

代码示例2
widget.cpp
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);
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);
//按照1:1:2的方式来设置宽度
Layout->setColumnStretch(0,1);
Layout->setColumnStretch(1,1);
Layout->setColumnStretch(2,2);
this->setLayout(Layout);
}
Widget::~Widget()
{
delete ui;
}

如果要将每一个列都设置拉伸系数的话,那么可以修改成下面的代码.
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);
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);
//按照1:1:2的方式来设置宽度
Layout->setColumnStretch(0,1);
Layout->setColumnStretch(1,1);
Layout->setColumnStretch(2,2);
this->setLayout(Layout);
}
Widget::~Widget()
{
delete ui;
}

另外, QGridLayout 也提供了 setRowStretch 设置⾏之间的拉伸系数. 上述案例中, 直接设置 setRowStretch 效果不明显, 因为每个按钮的高度是固定的. 需要把按钮的垂直方向sizePolicy 属性设置为 QSizePolicy::Expanding尽可能填充满布局管理器, 才能看到效果.
代码示例3
使用setSizePolicy 设置按钮的尺寸策略.可选的值如下:
- QSizePolicy:Ingored:忽略控件的尺寸,不对布局产生影响.
- QSizePolicy:Minium:控件的最小尺寸为固定值,布局时不会超过该值.
- QSizePolicy::Maximum:控件的最大尺寸为固定值,布局时不会小于该值.
- QSizePolicy:Preferred:控件的理想尺寸为固定值,布局时会尽量接近该值.
- QSizePolicy:Expanding:控件的尺寸可以根据空间调整,尽可能占据更多空间.
- QSizePolicy::Shrinking:控件的尺寸可以根据空间调整,尽可能缩小以适应空间。
widget.cpp
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);
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");
//设置按钮的sizePolicy,此时按钮的水平方向和垂直方向都会尽量舒展开,QSizePolicy::Expanding:控件的尺寸可以根据空间调整,尽可能占据更多空间
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);
//创建网格布局管理器,并且添加元素
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);
Layout->setRowStretch(0,1);
Layout->setRowStretch(1,1);
Layout->setRowStretch(2,2);
this->setLayout(Layout);
}
Widget::~Widget()
{
delete ui;
}

总的来说, 使用QGridLayout 能够代替很多 QHBoxLayout 和 QVBoxLayout 嵌套的场景. 毕 竟嵌套的代码写起来是比较麻烦的.
另外不要忘了, QGridLayout里面也能嵌套 QHBoxLayout 和 QVBoxLayout以及QHBoxLayout 和 QVBoxLayout李米娜也能嵌套 QGridLayout .灵活使用上述布局管理器, 就可以实现出任意的复杂界面.
4:表单布局
- 除了上述的布局管理器之外, Qt 还提供了 QFormLayout , 属于是 QGridLayout 的特殊情况, 专门用于实现两列表单的布局.
- 这种表单布局多用于让用户填写信息的场景. 左侧列为提示, 右侧列为输入框.
widget.cpp
- 使用addRow方法来添加一行. 每行包含两个控件. 第⼀个控件固定是 QLabel/文本. 第⼆个控件则可以是任意控件.
- 如果把第⼀个参数填写为 NULL, 则什么都不显示.
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QFormLayout * Layout = new QFormLayout();
this->setLayout(Layout);
//创建三个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 * Button = new QPushButton("Submit");
//把上述元素添加到Layout中
Layout->addRow(Label1,LineEdit1);
Layout->addRow(Label2,LineEdit2);
Layout->addRow(Label3,LineEdit3);
//如果把第⼀个参数填写为 NULL, 则什么都不显示
Layout->addRow(NULL,Button);
}
Widget::~Widget()
{
delete ui;
}

5:Spacer
使用布局管理器的时候, 可能需要在控件之间, 添加⼀段空白. 就可以使用 QSpacerItem 来表示.
|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 属性 | 说明 |
| width | 宽度 |
| height | 高度 |
| hData | 水平方向的 sizePolicy * QSizePolicy::Ignored : 忽略控件的尺寸,不对布局产⽣影响。 * QSizePolicy::Minimum : 控件的最小尺寸为固定值,布局时不会超过该值。 * QSizePolicy::Maximum : 控件的最大尺寸为固定值,布局时不会小于该值。 * QSizePolicy::Preferred : 控件的理想尺寸为固定值,布局时会尽量接近该值。 * QSizePolicy::Expanding : 控件的尺寸可以根据空间调整,尽可能占据更多空间。 * QSizePolicy::Shrinking : 控件的尺寸可以根据空间调整,尽可能缩小以适应空间。 |
| vData | 垂直方向的 sizePolicy,选项同上. |
上述属性在构造函数设置即可.
widget.cpp
创建⼀组左右排列的按钮.在界⾯上创建⼀个 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->setLayout(Layout);
QPushButton * Button1 = new QPushButton("按钮1");
QPushButton * Button2 = new QPushButton("按钮2");
// 创建 Spacer
QSpacerItem* spacer = new QSpacerItem(200, 20);
Layout->addWidget(Button1);
// 在两个 widget 中间添加空⽩
Layout->addSpacerItem(spacer);
Layout->addWidget(Button2);
}
Widget::~Widget()
{
delete ui;
}
运行程序, 观察代码效果. 可以看到两个按钮之间已经存在了间隔了.调整 QSpacerItem 不同的尺寸, 即可看到不同的间距.


