title: 布局容器与模型视图 tags: [
嵌入式Linux,
Qt,
Qt Widgets,
布局管理,
QBoxLayout,
QGridLayout,
QFormLayout,
QSpacerItem,
QGroupBox,
QScrollArea,
QToolBox,
QTabWidget,
QStackedWidget,
QWidget,
QMdiArea,
QDockWidget,
QListView,
QTreeView,
QTableView,
QColumnView,
QUndoView,
QListWidget,
QTreeWidget,
QTableWidget,
模型视图,
I.MX6U,
] created: 2026-09-18 updated: 2026-09-18
💡 关联知识:[[05-Qt应用开发/03-信号与槽]]、[[05-Qt应用开发/04-Qt控件详解]];延伸阅读:[[编程学习项目/使用Qt和C++构造UI界面/7. 布局管理器]]
本篇覆盖《I.MX6U嵌入式Qt开发指南》第七章的后半部分:布局管理(7.5)、空间间隔(7.6)、容器(7.7)、项目视图组(基于模型)(7.8)、项目控件组(基于项)(7.9),共 20 个类。代码取自配套例程 29_qboxlayout ~ 47_qtablewidget,去掉版权后可直接编译。
上一篇使用了 setGeometry() 绝对定位,界面一旦缩放就会错位。本篇的布局管理器正好解决这个问题;而模型/视图部分则解决“大量结构化数据如何高效显示”的问题。
Qt 的基本布局管理类都继承自 QLayout,而 QLayout 继承自 QObject(不是 QWidget)。多个布局可以彼此嵌套,构建复杂界面。
classDiagram
class QObject
class QLayout
class QBoxLayout
class QHBoxLayout
class QVBoxLayout
class QGridLayout
class QFormLayout
class QStackedLayout
class QLayoutItem
class QSpacerItem
class QWidgetItem
QObject <|-- QLayout
QLayout <|-- QBoxLayout
QBoxLayout <|-- QHBoxLayout
QBoxLayout <|-- QVBoxLayout
QLayout <|-- QGridLayout
QLayout <|-- QFormLayout
QLayout <|-- QStackedLayout
QObject <|-- QLayoutItem
QLayoutItem <|-- QSpacerItem
QLayoutItem <|-- QWidgetItem
各布局作用:
| 布局 | 作用 |
|---|---|
QBoxLayout |
水平或垂直排列子部件 |
QGridLayout |
网格(二维)方式管理界面组件 |
QFormLayout |
以两列形式布局输入部件与其标签 |
QStackedLayout |
一组布局后的部件,可分布显示 |
| 分组 | 控件 | 一句话作用 | 例程 |
|---|---|---|---|
| 布局 | QBoxLayout(QHBoxLayout/QVBoxLayout) |
水平/垂直布局 | 29 |
| 布局 | QGridLayout |
网格布局 | 30 |
| 布局 | QFormLayout |
表单布局 | 31 |
| 间隔 | QSpacerItem |
布局中的空白间隔 | 32 |
| 容器 | QGroupBox |
带标题的组框 | 33 |
| 容器 | QScrollArea |
滚动区域 | 34 |
| 容器 | QToolBox |
列状层叠工具箱(抽屉) | 35 |
| 容器 | QTabWidget |
选项卡多页面 | 36 |
| 容器 | QStackedWidget |
堆叠小部件,一次显示一个 | 37 |
| 容器 | QWidget |
所有 UI 对象的基类,可作容器 | 无独立例程 |
| 容器 | QMdiArea |
多文档界面(MDI)区域 | 38 |
| 容器 | QDockWidget |
可停靠窗体部件 | 39 |
| 模型视图 | QListView |
基于模型的列表/图标视图 | 40 |
| 模型视图 | QTreeView |
基于模型的树视图 | 41 |
| 模型视图 | QTableView |
基于模型的表视图 | 42 |
| 模型视图 | QColumnView |
级联列表视图 | 43 |
| 模型视图 | QUndoView |
撤销堆栈视图 | 44 |
| 项目控件 | QListWidget |
基于项的列表 | 45 |
| 项目控件 | QTreeWidget |
基于项的树 | 46 |
| 项目控件 | QTableWidget |
基于项的表 | 47 |
QListView / QTreeView / QTableView / QColumnView 基于 Model,需要自己建模(如 QStringListModel、QStandardItemModel),数据与显示分离,减少数据冗余、程序效率高。QListWidget / QTreeWidget / QTableWidget 基于 Item,是“已经封装好内部模型的视图”,直接 addItem、setItem 即可,操作方便,适合中小数据量。Qt 提供的常用现成模型:
| 模型 | 用途 |
|---|---|
QStringListModel |
存储简单的 QString 列表 |
QStandardItemModel |
管理复杂的树型结构数据项,每项可包含任意数据 |
QDirModel |
提供本地文件系统的文件与目录信息 |
QSqlQueryModel / QSqlTableModel / QSqlRelationTableModel |
访问数据库 |
QBoxLayout 继承 QLayout,提供水平或垂直排列子部件。它从父布局或 parentWidget() 获取空间,分成一列框,并让每个被管理的部件填充一个框。
QHBoxLayout(水平)与 QVBoxLayout(垂直)都继承自 QBoxLayout。
| 成员 | 说明 |
|---|---|
QHBoxLayout() / QVBoxLayout() |
构造水平/垂直布局 |
addWidget(QWidget*) |
添加部件 |
addLayout(QLayout*) |
嵌套添加子布局 |
addStretch(int) |
添加伸缩量 |
addSpacerItem(QSpacerItem*) |
添加间隔 |
setSpacing(int) |
设置部件间距 |
setMargin(int) / setContentsMargins(...) |
设置外框留白 |
setDirection(Direction) |
设置排列方向 |
setStretch(int, int) |
设置某部件的伸缩因子 |
例 29_qboxlayout 垂直或水平布局:上半区水平排 3 个按钮,下半区垂直排 3 个按钮。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 声明按钮对象数组 */
QPushButton *pushButton[6];
/* 声明两个widget,用于容纳排布按钮 */
QWidget *hWidget;
QWidget *vWidget;
/* QHBoxLayout与QVBoxLayout对象 */
QHBoxLayout *hLayout;
QVBoxLayout *vLayout;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include <QList>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗口的位置与大小 */
this->setGeometry(0, 0, 800, 480);
/* 实例化与设置位置大小 */
hWidget = new QWidget(this);
hWidget->setGeometry(0, 0, 800, 240);
vWidget = new QWidget(this);
vWidget->setGeometry(0, 240, 800, 240);
hLayout = new QHBoxLayout();
vLayout = new QVBoxLayout();
/* QList<T>是Qt的一种泛型容器类。
* 它以链表方式存储一组值,并能对这组数据进行快速索引
*/
QList <QString>list;
/* 将字符串值插入list */
list<<"one"<<"two"<<"three"<<"four"<<"five"<<"six";
/* 用一个循环实例化6个按钮 */
for(int i = 0; i < 6; i++){
pushButton[i] = new QPushButton();
pushButton[i]->setText(list[i]);
if(i < 3) {
/* 将按钮添加至hLayout中 */
hLayout->addWidget(pushButton[i]);
} else {
/* 将按钮添加至vLayout中 */
vLayout->addWidget(pushButton[i]);
}
}
/* 设置间隔为50 */
hLayout->setSpacing(50);
/* hWidget与vWidget的布局设置为hLayout/vLayout */
hWidget->setLayout(hLayout);
vWidget->setLayout(vLayout);
}
MainWindow::~MainWindow()
{
}
上半区 3 个按钮水平排布,下半区 3 个按钮垂直排布,间距为 50 像素。
QGridLayout 继承 QLayout,以网格(二维)方式管理界面组件,坐标与二维数组类似:
0,0(组件一) 0,1(组件二)
1,0(组件三) 1,1(组件四)
它把可用空间分为行和列,把部件放入正确单元格。与 QBoxLayout 不同的是,网格布局还需要分别设置行和列的比例系数(拉伸因子)。
| 成员 | 说明 |
|---|---|
addWidget(QWidget*, int row, int col) |
把部件放入指定单元格 |
addWidget(QWidget*, row, col, rowSpan, colSpan) |
跨行/跨列 |
setRowStretch(int row, int stretch) |
行比例系数 |
setColumnStretch(int col, int stretch) |
列比例系数 |
setSpacing(int) |
间距 |
setMargin(int) |
外框留白 |
例 30_qgridlayout 网格布局:4 个按钮排成 2×2,并设置行列拉伸因子。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QGridLayout>
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 声明widget窗口部件,用于容纳下面4个pushButton按钮 */
QWidget *gWidget;
/* 声明QGridLayout对象 */
QGridLayout *gridLayout;
/* 声明pushButton按钮数组 */
QPushButton *pushButton[4];
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置位置与大小 */
this->setGeometry(0, 0, 800, 480);
/* 实例化 */
gWidget = new QWidget(this);
/* 设置gWidget居中央 */
this->setCentralWidget(gWidget);
gridLayout = new QGridLayout();
/* QList链表,字符串类型 */
QList <QString> list;
list<<"按钮1"<<"按钮2"<<"按钮3"<<"按钮4";
for (int i = 0; i < 4; i++){
pushButton[i] = new QPushButton();
pushButton[i]->setText(list[i]);
/* 设置最小宽度与高度 */
pushButton[i]->setMinimumSize(100, 30);
/* 自动调整按钮的大小 */
pushButton[i]->setSizePolicy(
QSizePolicy::Expanding,
QSizePolicy::Expanding
);
switch (i) {
case 0:
/* 将pushButton[0]添加至网格的坐标(0,0),下同 */
gridLayout->addWidget(pushButton[i], 0, 0);
break;
case 1:
gridLayout->addWidget(pushButton[i], 0, 1);
break;
case 2:
gridLayout->addWidget(pushButton[i], 1, 0);
break;
case 3:
gridLayout->addWidget(pushButton[i], 1, 1);
break;
default:
break;
}
}
/* 设置第0行与第1行的行比例系数 */
gridLayout->setRowStretch(0, 2);
gridLayout->setRowStretch(1, 3);
/* 设置第0列与第1列的列比例系数 */
gridLayout->setColumnStretch(0, 1);
gridLayout->setColumnStretch(1, 3);
/* 将gridLayout设置到gWidget */
gWidget->setLayout(gridLayout);
}
MainWindow::~MainWindow()
{
}
4 个按钮按 2×2 网格排列;行高比例为 2:3,列宽比例为 1:3,因此按钮大小并不均等。
QFormLayout 继承 QLayout,以两列形式管理输入部件及其标签:左列是标签,右列是“字段”小部件(如 QLineEdit、QSpinBox)。适合行列较少的表单;多行多列应改用 QGridLayout。可用 setRowWrapPolicy(RowWrapPolicy) 设置换行策略。
| 成员 | 说明 |
|---|---|
addRow(const QString &labelText, QWidget *field) |
添加一行:标签文本 + 字段部件 |
addRow(QWidget *label, QWidget *field) |
用部件作标签 |
setSpacing(int) |
水平/垂直间距 |
setMargin(int) |
布局外框宽度 |
setRowWrapPolicy(RowWrapPolicy) |
换行策略 |
setLabelAlignment(Qt::Alignment) |
标签对齐 |
setFormAlignment(Qt::Alignment) |
表单对齐 |
例 31_qformlayout 表单布局:用户名、密码两行输入。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QFormLayout>
#include <QLineEdit>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* widget对象 */
QWidget *fWidget;
/* 用于输入用户名 */
QLineEdit *userLineEdit;
/* 用于输入密码 */
QLineEdit *passwordLineEdit;
/* 声明QFormLayout对象 */
QFormLayout *formLayout;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置位置与大小 */
this->setGeometry(0, 0, 800, 480);
/* 实例化及设置位置与大小,下同 */
fWidget = new QWidget(this);
fWidget->setGeometry(250, 100, 300, 200);
userLineEdit = new QLineEdit();
passwordLineEdit = new QLineEdit();
formLayout = new QFormLayout();
/* 添加行 */
formLayout->addRow("用户名:", userLineEdit);
formLayout->addRow("密码 :", passwordLineEdit);
/* 设置水平垂直间距 */
formLayout->setSpacing(10);
/* 设置布局外框的宽度 */
formLayout->setMargin(20);
/* 将formLayout布局到fWidget */
fWidget->setLayout(formLayout);
}
MainWindow::~MainWindow()
{
}
fWidget 内出现两行:左侧“用户名:”“密码 :”,右侧对应的单行输入框,行列间距与外框留白已设置。
QSpacerItem 继承 QLayoutItem,在布局中提供空白(空间间隔),只能在布局里使用。它对应 Qt Designer 中的 Horizontal Spacer(水平间隔)与 Vertical Spacer(垂直间隔)。
| 成员 | 说明 |
|---|---|
QSpacerItem(int w, int h, QSizePolicy::Policy hPolicy, QSizePolicy::Policy vPolicy) |
构造间隔 |
changeSize(int w, int h, ...) |
改变尺寸策略 |
QLayout::addSpacerItem(QSpacerItem*) |
把间隔加入布局 |
QLayout::addStretch(int) |
直接加伸缩量(更简便) |
例 32_qspaceritem 空间间隔:垂直布局中加垂直间隔,水平布局中加水平间隔。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QPushButton>
#include <QSpacerItem>
#include <QBoxLayout>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 按钮对象数组 */
QPushButton *bt[4];
/* 垂直间隔 */
QSpacerItem *vSpacer;
/* 水平间隔 */
QSpacerItem *hSpacer;
/* 声明一个widget用来存放布局的内容 */
QWidget *widget;
/* 主布局对象 */
QHBoxLayout *mainLayout;
/* 垂直布局对象 */
QVBoxLayout *vBoxLayout;
/* 水平布局对象 */
QHBoxLayout *hBoxLayout;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗体显示位置与大小 */
this->setGeometry(0, 0, 800, 480);
widget = new QWidget(this);
/* 居中widget */
this->setCentralWidget(widget);
/* 实例化对象 */
vSpacer = new QSpacerItem(10, 10,
QSizePolicy::Minimum,
QSizePolicy::Expanding
);
hSpacer = new QSpacerItem(10, 10,
QSizePolicy::Expanding,
QSizePolicy::Minimum
);
vBoxLayout = new QVBoxLayout();
hBoxLayout = new QHBoxLayout();
mainLayout = new QHBoxLayout();
/* 在vBoxLayout添加垂直间隔 */
vBoxLayout->addSpacerItem(vSpacer);
QList <QString>list;
/* 将字符串值插入list */
list<<"按钮1"<<"按钮2"<<"按钮3"<<"按钮4";
/* 用一个循环实例化4个按钮 */
for(int i = 0; i < 4 ; i++){
bt[i] = new QPushButton();
bt[i]->setText(list[i]);
if (i == 0){
/* 按钮1,设置为100*100 */
bt[i]->setFixedSize(100, 100);
/* 在vBoxLayout添加按钮1 */
vBoxLayout->addWidget(bt[i]);
} else {
/* 按钮2~4,设置为60*60 */
bt[i]->setFixedSize(60, 60);
/* 在hBoxLayout添加按钮2~4 */
hBoxLayout->addWidget(bt[i]);
}
}
/* 在hBoxLayout添加水平间隔 */
hBoxLayout->addSpacerItem(hSpacer);
/* 在主布局里添加垂直布局 */
mainLayout->addLayout(vBoxLayout);
/* 在主布局里添加水平布局 */
mainLayout->addLayout(hBoxLayout);
/* 设置部件间距 */
mainLayout->setSpacing(50);
/* 将主布局设置为widget的布局 */
widget->setLayout(mainLayout);
}
MainWindow::~MainWindow()
{
}
垂直布局中,垂直间隔把按钮 1 顶到下方;水平布局中,按钮 2~4 之后是水平间隔,把按钮挤向左侧。
容器类用于分组、容纳其他窗口部件。下面依次讲解 QGroupBox、QScrollArea、QToolBox、QTabWidget、QStackedWidget、QWidget、QMdiArea、QDockWidget。
QGroupBox 继承 QWidget,提供一个带标题的组框框架,通常与一组同类型部件一起使用。组框还提供键盘快捷方式,可把焦点移到组框内的一个子部件。内部一般使用布局(如 QBoxLayout)来布置子部件。
| 成员 | 说明 |
|---|---|
QGroupBox(const QString &title, QWidget *parent) |
构造:标题 + 父对象 |
setTitle(QString) / title() |
标题 |
setCheckable(bool) |
是否带复选框 |
setChecked(bool) |
复选状态 |
setLayout(QLayout*) |
设置内部布局 |
toggled(bool) |
复选状态变化信号 |
例 33_qgroupbox 组框示例:3 个单选框放进一个组框。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QRadioButton>
#include <QGroupBox>
#include <QVBoxLayout>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 声明对象 */
QGroupBox *groupBox;
QVBoxLayout *vBoxLayout;
QRadioButton *radioButton[3];
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include <QList>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗体位置与大小 */
this->setGeometry(0, 0, 800, 480);
/* 以标题为“QGroupBox示例”实例化groupBox对象 */
groupBox = new QGroupBox(tr("QGroupBox示例"), this);
groupBox->setGeometry(300, 100, 300, 200);
vBoxLayout = new QVBoxLayout();
/* 字符串链表 */
QList <QString>list;
list<<"选项一"<<"选项二"<<"选项三";
for(int i = 0; i < 3; i++){
radioButton[i] = new QRadioButton();
radioButton[i]->setText(list[i]);
/* 在vBoxLayout添加radioButton */
vBoxLayout->addWidget(radioButton[i]);
}
/* 添加一个伸缩量1 */
vBoxLayout->addStretch(1);
/* vBoxLayout布局设置为groupBox布局 */
groupBox->setLayout(vBoxLayout);
}
MainWindow::~MainWindow()
{
}
“QGroupBox 示例”组框内规则地垂直排列三个单选框。
QScrollArea 继承 QAbstractScrollArea,用于在框架中显示子部件的内容。当子部件超过框架大小时,视图出现滚动条,从而可以查看子部件的整个区域。
| 成员 | 说明 |
|---|---|
setWidget(QWidget*) |
设置要显示的子部件 |
widget() |
取得子部件 |
setWidgetResizable(bool) |
是否随区域自动调整大小 |
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy) |
水平滚动条策略 |
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy) |
垂直滚动条策略 |
例 34_qscrollarea 滚动视图:把一张大图放进滚动区域。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QScrollArea>
#include <QLabel>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 定义QScrollArea对象 */
QScrollArea *scrollArea;
QLabel *label;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setGeometry(0, 0, 800, 480);
scrollArea = new QScrollArea(this);
/* 设置滚动区域为700*380 */
scrollArea->setGeometry(50, 50, 700, 380);
label = new QLabel();
/* label显示的lantingxu.png图片分辨率为1076*500 */
QImage image(":/images/lantingxu.png");
label->setPixmap(QPixmap::fromImage(image));
scrollArea->setWidget(label);
}
MainWindow::~MainWindow()
{
}
图片(1076×500)大于滚动区域(700×380),因此出现滚动条,可拖动查看图片其余部分。
QToolBox 继承 QFrame,提供一列选项卡小部件项,中文常译为“工具箱”,效果类似抽屉或 QQ 好友面板。当前项显示在当前选项卡下面,每个选项卡在选项卡列中都有一个索引位置,选项卡项是 QWidget。
| 成员 | 说明 |
|---|---|
addItem(QWidget *widget, const QString &text) |
添加页 |
insertItem(int index, QWidget*, QString) |
插入页 |
removeItem(int index) |
移除页 |
count() |
页数 |
setCurrentIndex(int) / currentIndex() |
当前页 |
currentChanged(int) |
当前页变化信号 |
例 35_qtoolbox QQ 好友面板:两组工具按钮分别放进两个组框,再作为两页加入工具箱。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QToolBox>
#include <QGroupBox>
#include <QToolButton>
#include <QVBoxLayout>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 声明对象 */
QToolBox *toolBox;
QGroupBox *groupBox[2];
QVBoxLayout *vBoxLayout[2];
QToolButton *toolButton[6];
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setGeometry(0, 0, 800, 480);
toolBox = new QToolBox(this);
toolBox->setGeometry(300, 50, 200, 250);
/* 设置toolBox的样式,此处设置为30%不透明度的黑色 */
toolBox->setStyleSheet("QToolBox {background-color:rgba(0, 0, 0, 30%);}");
for(int i = 0; i < 2; i++){
vBoxLayout[i] = new QVBoxLayout();
groupBox[i] = new QGroupBox(this);
}
/* 字符串链表 */
QList <QString>strList;
strList<<"李白"<<"王照君"<<"李元芳"<<"程咬金"<<"钟馗"<<"上官婉儿";
/* 字符串图标链表 */
QList <QString>iconsList;
iconsList<<":/icons/libai"<<":/icons/wangzhaojun"
<<":/icons/liyuanfang"<<":/icons/chengyaojin"
<<":/icons/zhongkui"<<":/icons/shangguanwaner";
for(int i = 0; i < 6; i++){
toolButton[i] = new QToolButton();
/* 设置toolButton图标 */
toolButton[i]->setIcon(QIcon(iconsList[i]));
/* 设置toolButton的文本 */
toolButton[i]->setText(strList[i]);
/* 设置toolButton的大小 */
toolButton[i]->setFixedSize(150, 40);
/* 设置toolButton的setToolButtonStyle的样式 */
toolButton[i]->setToolButtonStyle(
Qt::ToolButtonTextBesideIcon
);
if( i < 3 ) {
/* 将toolButton添加到垂直布局 */
vBoxLayout[0]->addWidget(toolButton[i]);
/* 添加一个伸缩量1 */
vBoxLayout[0]->addStretch(1);
} else {
vBoxLayout[1]->addWidget(toolButton[i]);
vBoxLayout[1]->addStretch(1);
}
}
/* 将垂直布局的内容添加到组框groupBox */
groupBox[0]->setLayout(vBoxLayout[0]);
groupBox[1]->setLayout(vBoxLayout[1]);
/* 将组框加入QToolBox里 */
toolBox->addItem(groupBox[0],"我的好友");
toolBox->addItem(groupBox[1],"黑名单");
}
MainWindow::~MainWindow()
{
}
点击“我的好友”显示前三人的头像列表,点击“黑名单”显示后三人的列表。子项除 QGroupBox 外也可以是普通 QWidget。
QTabWidget 继承 QWidget,提供一组选项卡(多页面)小部件,用于分页显示:每一页一个界面,众多界面公用一块区域,节省空间。类似浏览器的多标签页。
| 成员 | 说明 |
|---|---|
addTab(QWidget *page, const QString &label) |
添加页面 |
addTab(QWidget *page, QIcon, QString) |
带图标添加 |
insertTab(int, QWidget*, QIcon, QString) |
插入页面 |
removeTab(int index) |
移除页面 |
setCurrentIndex(int) / currentIndex() |
当前页 |
setTabsClosable(bool) |
是否显示关闭按钮 |
setTabPosition(QTabWidget::TabPosition) |
页签位置 |
currentChanged(int) |
当前页变化信号 |
tabCloseRequested(int) |
页签关闭请求信号 |
例 36_qtabwidget 标题栏多页面切换:3 个页面各放一个标签。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTabWidget>
#include <QHBoxLayout>
#include <QLabel>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 声明对象 */
QWidget *widget;
QTabWidget *tabWidget;
QHBoxLayout *hBoxLayout;
QLabel *label[3];
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setGeometry(0, 0, 800, 480);
widget = new QWidget(this);
/* 居中 */
this->setCentralWidget(widget);
/* 多页面小部件 */
tabWidget = new QTabWidget();
/* 水平布局实例化 */
hBoxLayout = new QHBoxLayout();
QList <QString>strLabelList;
strLabelList<<"标签一"<<"标签二"<<"标签三";
QList <QString>strTabList;
strTabList<<"页面一"<<"页面二"<<"页面三";
QList <QString>iconList;
iconList<<":/icons/icon1"
<<":/icons/icon2"
<<":/icons/icon3";
for (int i = 0; i < 3; i++) {
label[i] = new QLabel();
/* 设置标签文本 */
label[i]->setText(strLabelList[i]);
/* 标签对齐方式(居中) */
label[i]->setAlignment(Qt::AlignCenter);
/* 添加页面 */
tabWidget->addTab(label[i],
QIcon(iconList[i]),
strTabList[i]
);
}
/* 是否添加关闭按钮 */
//tabWidget->setTabsClosable(true);
/* 将tabWidget水平排布 */
hBoxLayout->addWidget(tabWidget);
/* 将布局设置到widget */
widget->setLayout(hBoxLayout);
}
MainWindow::~MainWindow()
{
}
可扩展使用
setTabsClosable(true)在页签后加关闭按钮,再连接tabCloseRequested(int)与removeTab(int)实现关闭页面。
点击不同页签切换到对应页面,页面中央显示“标签一/二/三”。页签带图标。
QStackedWidget 继承 QFrame,提供一个小部件堆栈,一次只能看到一个小部件,类似 QQ 设置面板。它可用于创建类似 QTabWidget 的界面,构建在 QStackedLayout 之上。常与 QListWidget 搭配:左边列表选择,右边堆叠显示不同页面。
| 成员 | 说明 |
|---|---|
addWidget(QWidget*) |
添加页面 |
insertWidget(int, QWidget*) |
插入页面 |
removeWidget(QWidget*) |
移除页面 |
setCurrentIndex(int) / currentIndex() |
当前页(对应槽 setCurrentIndex(int)) |
count() |
页面数 |
currentChanged(int) |
当前页变化信号 |
例 37_qstackedwidget 列表栏多页面切换:左边列表、右边堆叠页,靠信号槽联动。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QStackedWidget>
#include <QHBoxLayout>
#include <QListWidget>
#include <QLabel>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* widget小部件 */
QWidget *widget;
/* 水平布局 */
QHBoxLayout *hBoxLayout;
/* 列表视图 */
QListWidget *listWidget;
/* 堆栈窗口部件 */
QStackedWidget *stackedWidget;
/* 3个标签 */
QLabel *label[3];
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setGeometry(0, 0, 800, 480);
/* widget小部件实例化 */
widget = new QWidget(this);
/* 设置居中 */
this->setCentralWidget(widget);
/* 水平布局实例化 */
hBoxLayout = new QHBoxLayout();
/* 堆栈部件实例化 */
stackedWidget = new QStackedWidget();
/* 列表实例化 */
listWidget = new QListWidget();
QList <QString>strListWidgetList;
strListWidgetList<<"窗口一"<<"窗口二"<<"窗口三";
for (int i = 0; i < 3; i++){
/* listWidget插入项 */
listWidget->insertItem(
i,
strListWidgetList[i]
);
}
QList <QString>strLabelList;
strLabelList<<"标签一"<<"标签二"<<"标签三";
for (int i = 0; i < 3; i++){
label[i] = new QLabel();
/* 设置标签文本 */
label[i]->setText(strLabelList[i]);
/* 标签对齐方式(居中) */
label[i]->setAlignment(Qt::AlignCenter);
/* 添加页面 */
stackedWidget->addWidget(label[i]);
}
/* 设置列表的最大宽度 */
listWidget->setMaximumWidth(200);
/* 添加到水平布局 */
hBoxLayout->addWidget(listWidget);
hBoxLayout->addWidget(stackedWidget);
/* 将widget的布局设置成hBoxLayout */
widget->setLayout(hBoxLayout);
/* 利用listWidget的信号currentRowChanged()与
* 槽setCurrentIndex(),进行信号与槽连接
*/
connect(listWidget, SIGNAL(currentRowChanged(int)),
stackedWidget, SLOT(setCurrentIndex(int)));
}
MainWindow::~MainWindow()
{
}
点击左侧列表的“窗口一/二/三”,右侧堆叠页随之切换显示对应标签。这是同一份代码里“信号 → 槽”跨对象直连的典型用法。
QWidget 类是所有用户界面对象的基类(如 QLabel 继承 QFrame,QFrame 又继承 QWidget)。Widget 是用户界面的基本单元:它从窗口系统接收鼠标、键盘和其他事件,并在屏幕上绘制自己。每个 Widget 都是矩形,按 Z-order 排序。
Z-order 指沿 z 轴(垂直于屏幕)的堆叠顺序。GUI 窗口可能重叠,当两个窗口重叠时,Z 顺序决定谁在上。就像桌面上的一叠纸:每张纸是一个窗口,桌面是屏幕,最上面的窗口 z 值最高。
QWidget 不是抽象类,可以用作其他 Widget 的容器,也容易子类化来创建定制 Widget。它属性方法相当多,初学者通常只用它作容器并接收事件。
| 成员 | 说明 |
|---|---|
setLayout(QLayout*) |
设置布局 |
setGeometry(x, y, w, h) |
位置与大小 |
show() / hide() / close() |
显示/隐藏/关闭 |
setStyleSheet(QString) |
样式表 |
setWindowTitle(QString) |
窗口标题 |
setAttribute(Qt::WidgetAttribute) |
设置属性(如 WA_DeleteOnClose) |
setSizePolicy(QSizePolicy::Policy h, v) |
尺寸策略 |
update() |
请求重绘 |
QWidget 在 1.1 节的例 29 中已经作为容器使用:hWidget、vWidget 都是 QWidget,通过 setLayout() 承载布局。下面再给一个最小片段,说明“容器 + 布局”的标准写法:
/* 新建一个容器部件 */
QWidget *container = new QWidget(this);
/* 新建布局并添加子部件 */
QVBoxLayout *layout = new QVBoxLayout();
layout->addWidget(new QPushButton("按钮一"));
layout->addWidget(new QPushButton("按钮二"));
/* 把布局交给容器,容器再作为主窗口中央部件 */
container->setLayout(layout);
this->setCentralWidget(container);
QWidget 本身没有可见外观,它的作用是承载布局与其他控件;把布局设置给容器后,子控件会随容器尺寸变化自动排布。
QMdiArea 继承 QAbstractScrollArea,提供一个显示 MDI(多文档界面)窗口的区域,功能本质上类似 MDI 窗口管理器。复杂程序常使用 MDI 框架。通过 addSubWindow() 添加 QMdiSubWindow 子窗口。
| 成员 | 说明 |
|---|---|
addSubWindow(QWidget *widget) |
添加子窗口 |
removeSubWindow(QWidget*) |
移除子窗口 |
setActiveSubWindow(QMdiSubWindow*) |
激活子窗口 |
activeSubWindow() |
当前活动子窗口 |
subWindowList() |
子窗口列表 |
cascadeSubWindows() |
级联排列 |
tileSubWindows() |
平铺排列 |
closeAllSubWindows() |
关闭全部子窗口 |
例 38_qmdiarea 父子窗口:每点按钮新建一个子窗口。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QMdiSubWindow>
#include <QMdiArea>
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* Mdi Area区域对象 */
QMdiArea *mdiArea;
/* MdiSubWindow子窗口对象 */
QMdiSubWindow *newMdiSubWindow;
/* 用作点击创建新的窗口 */
QPushButton *pushButton;
private slots:
/* 按钮槽函数 */
void creat_newMdiSubWindow();
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置窗口的显示位置与大小 */
this->setGeometry(0, 0, 800, 480);
pushButton = new QPushButton("新建窗口", this);
pushButton->setGeometry(0, 30, 100, 30);
mdiArea = new QMdiArea(this);
/* 设置MDI Area区域大小 */
mdiArea->setGeometry(100, 30, 700, 430);
/* 连接信号槽 */
connect(pushButton, SIGNAL(clicked()),
this, SLOT(creat_newMdiSubWindow()));
}
void MainWindow::creat_newMdiSubWindow()
{
newMdiSubWindow = new QMdiSubWindow();
newMdiSubWindow->setWindowTitle("新建窗口");
/* 如果窗口设置了Qt::WA_DeleteOnClose这个属性,
* 在窗口接受了关闭事件后,Qt会释放这个窗口所占用的资源
*/
newMdiSubWindow->setAttribute(Qt::WA_DeleteOnClose);
/* 添加子窗口 */
mdiArea->addSubWindow(newMdiSubWindow);
/* 显示窗口,不设置时为不显示 */
newMdiSubWindow->show();
/* 以平铺方式排列所有窗口 */
mdiArea->tileSubWindows();
}
MainWindow::~MainWindow()
{
}
子窗口内容默认为空,可用
newMdiSubWindow->setWidget(QWidget*)放入QLineEdit等部件。另有cascadeSubWindows()以级联方式排列。
每点一次“新建窗口”,MDI 区域里新增一个标题为“新建窗口”的子窗口,并自动平铺排列;关闭子窗口时因 WA_DeleteOnClose 会释放资源。
QDockWidget 继承 QWidget,提供一个可以停靠在 QMainWindow 内、也可作为桌面顶级窗口浮动的部件,也称工具面板或实用程序窗口。停靠窗口可被移动到当前区域、新区域,或由终端用户浮动/取消浮动。API 允许限制其移动、浮动、关闭能力及可放置区域。
初始停靠区域:Qt::BottomDockWidgetArea(底部)、Qt::LeftDockWidgetArea(左)、Qt::RightDockWidgetArea(右)、Qt::TopDockWidgetArea(顶)、Qt::NoDockWidgetArea(不显示)。
| 成员 | 说明 |
|---|---|
QDockWidget(const QString &title, QWidget *parent) |
构造:标题 + 父对象 |
setWidget(QWidget*) |
设置内部部件 |
QMainWindow::addDockWidget(area, dock) |
添加到主窗口指定停靠区 |
setAllowedAreas(Qt::DockWidgetAreas) |
允许停靠的区域 |
setFloating(bool) |
是否浮动 |
setFeatures(Features) |
可移动/可关闭/可浮动等特性 |
visibilityChanged(bool) |
可见性变化信号 |
例 39_qdockwidget 停靠窗口:文本编辑框放入停靠窗口,停靠在主窗口顶部。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QDockWidget>
#include <QTextEdit>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 停靠窗口部件 */
QDockWidget *dockWidget;
/* 文本编辑框 */
QTextEdit *textEdit;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗体的显示的位置与大小 */
this->setGeometry(0, 0, 800, 480);
/* 实例化标题为停靠窗口 */
dockWidget = new QDockWidget("停靠窗口", this);
/* 实例化文本编辑框 */
textEdit = new QTextEdit(dockWidget);
textEdit->setText("这是一个测试");
/* 停靠窗口添加文本编辑框 */
dockWidget->setWidget(textEdit);
/* 放在主窗体的顶部 */
this->addDockWidget(Qt::TopDockWidgetArea, dockWidget);
}
MainWindow::~MainWindow()
{
}
停靠窗口出现在主窗口顶部,带有标题“停靠窗口”,内部是一个显示“这是一个测试”的文本编辑框;拖动它可移动并重新停靠。
基于模型的视图需要先建立 Model,再设置给视图。下面依次讲解 QListView、QTreeView、QTableView、QColumnView、QUndoView。
QListView 继承 QAbstractItemView,被 QListWidget 和 QUndoView 继承。它以简单的非分层列表或图标集合形式显示存储在模型中的项,使用 Qt 的模型/视图体系结构,比旧的 QListBox、QIconView 更灵活。QListView 基于 Model,需要建模,减少数据冗余、效率高。
| 成员 | 说明 |
|---|---|
setModel(QAbstractItemModel*) |
设置模型 |
setViewMode(QListView::ViewMode) |
ListMode / IconMode |
setDragEnabled(bool) |
是否可拖动 |
setSelectionMode(QAbstractItemView::SelectionMode) |
选择模式 |
setModelColumn(int) |
显示模型哪一列 |
clicked(QModelIndex) / doubleClicked(QModelIndex) |
点击信号 |
例 40_qlistview 清单图:用 QStringListModel 提供数据。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QListView>
#include <QStringListModel>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* QListView对象 */
QListView *listView;
/* 字符串模型对象 */
QStringListModel *stringListModel;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗口位置与大小 */
this->setGeometry(0, 0, 800, 480);
/* 实例化 */
listView = new QListView(this);
/* 将listView居中 */
setCentralWidget(listView);
QStringList strList;
strList<<"高三(1)班"<<"高三(2)班"<<"高三(3)班";
/* 实例化,字符串模型 */
stringListModel = new QStringListModel(strList);
/* 向表中插入一段数据 */
listView->setModel(stringListModel);
/* 设置为视图为图标模式 */
listView->setViewMode(QListView::IconMode);
/* 设置为不可拖动 */
listView->setDragEnabled(false);
}
MainWindow::~MainWindow()
{
}
三个班级以图标模式显示。双击项可修改内容,也可删除。
QTreeView 继承 QAbstractItemView,被 QTreeWidget 继承。它提供树视图的默认模型/视图实现,实现模型项的树表示。要显示数据需构造 Model 并设置给 QTreeView,最常用的是 QStandardItemModel。表头内容也由 Model 管理,setHorizontalHeaderLabels() 可设置列数与每列文字。
| 成员 | 说明 |
|---|---|
setModel(QAbstractItemModel*) |
设置模型 |
setHeaderHidden(bool) |
是否隐藏表头 |
expandAll() / collapseAll() |
展开/折叠全部 |
setColumnWidth(int, int) |
列宽 |
QStandardItemModel::appendRow(...) |
追加行 |
QStandardItem::appendRow(...) |
给某项追加子行 |
clicked(QModelIndex) / expanded(QModelIndex) |
信号 |
例 41_qtreeview 仿 Word 标题:一级标题下挂二级标题。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTreeView>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
QTreeView *treeView;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include <QStandardItemModel>
#include <QStandardItem>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置窗口的位置与大小 */
this->setGeometry(0, 0, 800, 480);
/* 实例化QTreeView对象 */
treeView = new QTreeView(this);
/* 居中 */
setCentralWidget(treeView);
/* 构建Model */
QStandardItemModel *sdiModel = new QStandardItemModel(treeView);
sdiModel->setHorizontalHeaderLabels(
QStringList()<<QStringLiteral("标题")
<< QStringLiteral("名称")
);
for(int i = 0; i < 5; i++) {
/* 一级标题 */
QList<QStandardItem*> items1;
QStandardItem* item1 =
new QStandardItem(QString::number(i));
QStandardItem* item2 =
new QStandardItem(QStringLiteral("一级标题"));
/* 添加项一 */
items1.append(item1);
/* 添加项二 */
items1.append(item2);
/* appendRow方法添加到model上 */
sdiModel->appendRow(items1);
for(int j = 0; j < 5; j++) {
/* 在一级标题后面插入二级标题 */
QList<QStandardItem*> items2;
QStandardItem* item3 =
new QStandardItem(QString::number(j));
QStandardItem* item4 =
new QStandardItem(QStringLiteral("二级标题"));
items2.append(item3);
items2.append(item4);
/* 使用appendRow方法添加到item1上 */
item1->appendRow(items2);
}
}
/* 设置Model给treeView */
treeView->setModel(sdiModel);
}
MainWindow::~MainWindow()
{
}
显示两列表头“标题”“名称”,5 个一级标题,每个下面 5 个二级标题;点击数字前的展开箭头可展开二级标题。
QTableView 继承 QAbstractItemView,被 QTableWidget 继承。它提供表视图的默认模型/视图实现,用于显示来自模型的数据。同样需要构造 Model(最常用 QStandardItemModel),表头内容也由 Model 管理。
| 成员 | 说明 |
|---|---|
setModel(QAbstractItemModel*) |
设置模型 |
setShowGrid(bool) |
显示网格线 |
horizontalHeader() / verticalHeader() |
水平/垂直表头 |
QHeaderView::setSectionResizeMode(mode) |
表头缩放模式(Stretch 平均分) |
setSelectionBehavior(...) |
选择行为 |
setEditTriggers(...) |
编辑触发方式 |
QStandardItemModel::setItem(row, col, item) |
设置单元格 |
例 42_qtableview 表格视图:构建一个成绩表。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTableView>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
QTableView *tableView;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include <QStandardItemModel>
#include <QHeaderView>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置窗口的位置与大小 */
this->setGeometry(0, 0, 800, 480);
tableView = new QTableView(this);
setCentralWidget(tableView);
/* 显示网格线 */
tableView->setShowGrid(true);
QStandardItemModel* model = new QStandardItemModel();
QStringList labels =
QObject::tr("语文,数学,英语").simplified().split(",");
/* 设置水平头标签 */
model->setHorizontalHeaderLabels(labels);
/* item */
QStandardItem* item = 0;
/* model插入项内容 */
for(int i = 0; i < 5; i++){
item = new QStandardItem("80");
model->setItem(i, 0, item);
item = new QStandardItem("99");
model->setItem(i, 1, item);
item = new QStandardItem("100");
model->setItem(i, 2, item);
}
/* 将model设置给tableView */
tableView->setModel(model);
/* 平均分列 */
tableView->horizontalHeader()
->setSectionResizeMode(QHeaderView::Stretch);
/* 平均分行 */
tableView->verticalHeader()
->setSectionResizeMode(QHeaderView::Stretch);
}
MainWindow::~MainWindow()
{
}
生成 5 行 3 列的成绩表,列头为“语文、数学、英语”,行列均等高/等宽铺满窗口。
QColumnView 继承 QAbstractItemView,在许多 QListView 中显示一个模型,每个 QListView 对应树中的一个层次结构,有时称为级联列表。它是模型/视图框架的一部分,实现 QAbstractItemView 定义的接口,以显示派生自 QAbstractItemModel 的模型数据。填写收货地址的多级选择就是这种效果。
| 成员 | 说明 |
|---|---|
setModel(QAbstractItemModel*) |
设置模型 |
setColumnWidths(QList<int>) |
各列宽度 |
updatePreviewWidget(QModelIndex) |
更新预览部件 |
setResizeGripsVisible(bool) |
是否显示拖动柄 |
例 43_qcolumnview 收货地址:省 → 市 → 镇 多级 QStandardItem。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QColumnView>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
QColumnView *columnView;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include <QStandardItem>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗体显示位置与大小 */
this->setGeometry(0, 0, 800, 480);
QStandardItemModel *model = new QStandardItemModel;
/* 省份 */
QStandardItem *province = new QStandardItem("广东省");
/* 城市 */
QStandardItem *city1 = new QStandardItem("茂名市");
QStandardItem *city2 = new QStandardItem("中山市");
/* 添加城市到省份下 */
province->appendRow(city1);
province->appendRow(city2);
QStandardItem *town1 = new QStandardItem("电白镇");
QStandardItem *town2 = new QStandardItem("南头镇");
/* 添加城镇到城市下 */
city1->appendRow(town1);
city2->appendRow(town2);
columnView = new QColumnView;
/* 建立model */
model->appendRow(province);
/* 设置model */
columnView->setModel(model);
/* 设置居中 */
setCentralWidget(columnView);
}
MainWindow::~MainWindow()
{
}
第一列显示“广东省”,点它后第二列出现“茂名市、中山市”,点城市后第三列出现对应城镇。
QUndoView 继承 QListView,显示 QUndoStack 的内容——即撤销堆栈上已推入的命令列表。它总是选中最近执行的命令;选择不同命令会调用 QUndoStack::setIndex(),把文档状态向前或向后滚动到该命令。可用 setStack() 显式设置堆栈,或用 setGroup() 设置 QUndoGroup(组的活动堆栈变化时视图自动更新)。效果类似 Photoshop 的历史记录面板。
| 成员 | 说明 |
|---|---|
QUndoView(QUndoStack *stack, QWidget *parent) |
用撤销栈构造 |
setStack(QUndoStack*) / setGroup(QUndoGroup*) |
设置栈/组 |
setEmptyLabel(QString) |
空栈时显示文本 |
QUndoStack::push(QUndoCommand*) |
入栈(会自动调用命令的 redo()) |
QUndoStack::undo() / redo() |
撤销/重做 |
QUndoStack::createUndoAction() / createRedoAction() |
生成撤销/重做动作 |
QUndoStack::indexChanged(int) |
索引变化信号 |
QUndoCommand::redo() / undo() / setText(QString) |
重写/设置命令描述 |
例 44_qundoview 仿 PS 历史记录:自定义 QUndoCommand,实现“加 1”的可撤销操作。需要额外新建 command.h、command.cpp。
command.h:
#ifndef COMMAND_H
#define COMMAND_H
#include <QUndoCommand>
#include <QObject>
class addCommand : public QUndoCommand
{
public:
addCommand(int *value, QUndoCommand* parent = 0);
~addCommand();
/* 重写重做与撤回方法 */
void redo() override;
void undo() override;
private:
/* 新的count */
int *new_count;
/* 旧的count */
int old_count;
};
#endif // COMMAND_H
command.cpp:
#include "command.h"
#include <QDebug>
addCommand::addCommand(int *value, QUndoCommand *parent)
{
/* 使用Q_UNUSED,避免未使用的数据类型 */
Q_UNUSED(parent);
/* undoView显示的操作信息 */
setText("进行了加1操作");
/* value的地址赋值给new_count */
new_count = value;
/* 让构造函数传过来的*new_count的值赋值给old_count */
old_count = *new_count;
}
/* 执行stack push时或者重做操作时会自动调用 */
void addCommand::redo()
{
/* 重新赋值给new_count */
*new_count = old_count;
/* 打印出*new_count的值 */
qDebug()<<"redo:"<<*new_count<<endl;
}
/* 回撤操作时执行 */
void addCommand::undo()
{
/* 回撤操作每次应减一 */
(*new_count)--;
/* 打印出*new_count的值 */
qDebug()<<"undo:"<<*new_count<<endl;
}
addCommand::~addCommand()
{
}
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QUndoView>
#include <QUndoStack>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLabel>
#include "command.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 水平布局 */
QHBoxLayout *hLayout;
/* 垂直布局 */
QVBoxLayout *vLayout;
/* 用于容纳hLayout布局 */
QWidget *mainWidget;
/* 容器QWidget,用于容纳标签与按钮 */
QWidget *widget;
/* 存放 QUndoCommand命令的栈 */
QUndoStack *undoStack;
/* 历史记录面板 */
QUndoView *undoView;
/* 用于显示计算结果 */
QLabel *label;
/* 按钮 */
QPushButton *pushButton;
/* 计算结果 */
int count;
private slots:
void pushButtonClieked();
void showCountValue(int);
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗体显示的位置与大小 */
this->setGeometry(0, 0, 800, 480);
/* 实例一个水平布局,用于左侧按钮区域与右侧历史记录面板 */
hLayout = new QHBoxLayout();
/* 实例一个垂直布局,用于左侧标签与按钮 */
vLayout = new QVBoxLayout();
/* 主Widget,因为MainWindow自带一个布局,
* 我们要新建一个Widget容纳新布局
*/
mainWidget = new QWidget();
/* 用于存放命令行栈 */
undoStack = new QUndoStack(this);
/* 用于容纳左侧标签与按钮布局 */
widget = new QWidget();
/* 历史记录面板实例化 */
undoView = new QUndoView(undoStack);
/* 实例化一个按钮,用于加一操作 */
pushButton = new QPushButton();
/* 标签,用于显示计算结果 */
label = new QLabel();
/* 设置widget的大小 */
widget->setMinimumSize(400, 480);
/* 将两个widget添加到水平布局 */
hLayout->addWidget(widget);
hLayout->addWidget(undoView);
/* 初始化count的值 */
count = 0;
/* 显示初始化计算结果 */
label->setText("计算结果:" + QString::number(count));
label->setAlignment(Qt::AlignCenter);
/* 左侧布局 */
vLayout->addWidget(label);
vLayout->addWidget(pushButton);
/* 左侧布局控件的高度设置 */
label->setMaximumHeight(this->height() / 5);
pushButton->setMaximumHeight(this->height() / 5);
/* 按钮文本设置 */
pushButton->setText("加1");
/* 设置widget的布局为vLayout */
widget->setLayout(vLayout);
/* 将主窗体的布局设置为hLayout */
mainWidget->setLayout(hLayout);
/* 设置mainWidget为主窗体的居中widget */
this->setCentralWidget(mainWidget);
/* 信号槽连接,按钮点击,执行加一操作 */
connect(pushButton, SIGNAL(clicked()), this,
SLOT(pushButtonClieked()));
/* 信号槽连接,历史记录项index发生变化,显示count大小 */
connect(undoStack, SIGNAL(indexChanged(int) ),
this, SLOT(showCountValue(int)));
}
/* 入栈操作会自动调用addCommand的redo */
void MainWindow::pushButtonClieked()
{
/* 变量值加一 */
count++;
/* value指向count的地址 */
int *value = &count;
/* 用重写的addCommand类实例化 */
QUndoCommand *add = new addCommand(value);
/* 入栈 */
undoStack->push(add);
}
void MainWindow::showCountValue(int)
{
/* 标签用于显示计算结果 */
label->setText("计算结果:" + QString::number(count));
}
MainWindow::~MainWindow()
{
}
点击“加 1”按钮,计算结果加 1;用方向键或鼠标选择右侧历史记录项,会执行重做/回撤,输出窗口打印 redo:/undo: 信息,计算结果回到该步数值。
command.h/command.cpp的创建方法:项目右键Add New…→ C++ →C++ Source File,分别命名command.h、command.cpp,用于重写QUndoCommand。
基于项的控件是“自带内部模型的视图”,名字与视图组控件相似(如 QListWidget 继承 QListView)。一般处理大数据时用基于模型的视图,中小数据用基于项的控件更省事。
QListWidget 继承 QListView,提供基于项的列表小部件。它用内部模型管理列表中的每个 QListWidgetItem,直接 addItem 即可添加项目(图标、文字),操作方便。
| 成员 | 说明 |
|---|---|
addItem(const QString&) / addItem(QListWidgetItem*) |
添加项 |
insertItem(int row, const QString&) |
插入项 |
takeItem(int row) |
移除并返回项 |
item(int row) |
取项 |
count() |
项数 |
clear() |
清空 |
currentRowChanged(int) |
当前行变化信号 |
itemClicked(QListWidgetItem*) / itemDoubleClicked(...) |
点击/双击信号 |
例 45_qlistwidget 添加“歌曲”:打开文件对话框,把选中的 mp3 加到列表。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QListWidget>
#include <QPushButton>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* 声明对象 */
QListWidget *listWidget;
QPushButton *pushButton;
private slots:
void pushButtonClicked();
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include "QFileDialog"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗口的显示位置与大小 */
this->setGeometry(0, 0, 800, 480);
listWidget = new QListWidget(this);
/* 设置listWidget的大小 */
listWidget->setGeometry(0, 0, 480, 480);
listWidget->addItem("请单击右边的添加项添加内容");
pushButton = new QPushButton(this);
/* 设置pushButton的位置与大小 */
pushButton->setGeometry(540, 200, 200, 100);
pushButton->setText("添加项");
/* 信号与槽连接 */
connect(pushButton, SIGNAL(clicked()),
this, SLOT(pushButtonClicked()));
}
void MainWindow::pushButtonClicked()
{
/* 调用系统打开文件窗口,设置窗口标题为“添加项”,过滤文件名 */
QString fileName = QFileDialog::getOpenFileName(
this,tr("添加项"),"",
tr("Files(*.mp3)")
);
/* 判断是否选中打开mp3文件 */
if (fileName != NULL)
/* 添加项到列表中 */
listWidget->addItem(fileName);
}
MainWindow::~MainWindow()
{
}
可在项目下用
touch 0.mp3 1.mp3创建两个后缀为 mp3 的文件用于测试(并非真正歌曲)。本例就是音乐播放器“打开歌曲”的部分代码。
点击“添加项”弹出文件对话框,只显示 mp3 文件;选择一个加入左侧列表。可自行扩展删除项、删除全部等按钮。
QTreeWidget 继承 QTreeView,提供使用预定义树模型的树视图。它基于模型/视图体系结构,使用默认模型保存项,每个项都是 QTreeWidgetItem。下例模拟飞信联系人分组,演示树节点与子节点的联动选择。
| 成员 | 说明 |
|---|---|
addTopLevelItem(QTreeWidgetItem*) |
添加顶层项 |
QTreeWidgetItem(QTreeWidget*) / QTreeWidgetItem(QTreeWidgetItem*) |
构造顶层项/子项 |
setText(int col, QString) / text(int) |
设置/读取文本 |
setCheckState(int col, Qt::CheckState) / checkState(int) |
设置/读取勾选状态 |
setFlags(Qt::ItemFlags) |
项属性(可勾选、可用、可选) |
childCount() / child(int) |
子项数量/子项 |
parent() |
父项 |
itemChanged(QTreeWidgetItem*, int) |
项变化信号 |
clear() |
清空 |
例 46_qtreewidget 群发信息:选中顶层节点则子节点全选;子节点部分选中则顶层显示半选。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTreeWidget>
#include <QTreeWidgetItem>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* QTreeWidget对象 */
QTreeWidget *treeWidget;
/* 顶层树节点 */
QTreeWidgetItem *parentItem;
/* 声明三个子节点 */
QTreeWidgetItem *subItem[3];
/* 子节点处理函数 */
void updateParentItem(QTreeWidgetItem*);
private slots:
/* 槽函数 */
void treeItemChanged(QTreeWidgetItem*, int);
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗体大小 */
this->setGeometry(0, 0, 800, 480);
/* 实例化 */
treeWidget = new QTreeWidget(this);
/* 居中 */
setCentralWidget(treeWidget);
/* 清空列表 */
treeWidget->clear();
/* 实例化顶层树节点 */
parentItem = new QTreeWidgetItem(treeWidget);
parentItem->setText(0, "同事");
parentItem->setFlags(
Qt::ItemIsUserCheckable
| Qt::ItemIsEnabled
| Qt::ItemIsSelectable
);
/* 树节点设置为未选中 */
parentItem->setCheckState(0, Qt::Unchecked);
/* 字符串链表 */
QList <QString> strList;
strList<<"关羽"<<"刘备"<<"张飞";
for (int i = 0; i < 3; i++){
/* 实例化子节点 */
subItem[i] = new QTreeWidgetItem(parentItem);
/* 设置子节点的文本,参数0代表第0列 */
subItem[i]->setText(0, strList[i]);
/* 设置子节点的属性为用户可选、项开启、项可选 */
subItem[i]->setFlags(
Qt::ItemIsUserCheckable
| Qt::ItemIsEnabled
| Qt::ItemIsSelectable
);
/* 设置子节点的状态为未选中 */
subItem[i]->setCheckState(0,Qt::Unchecked);
}
/* 信号槽连接 */
connect(treeWidget,SIGNAL(itemChanged(QTreeWidgetItem* , int)),
this, SLOT(treeItemChanged(QTreeWidgetItem* , int)));
}
/* 更新树节点函数 */
void MainWindow::updateParentItem(QTreeWidgetItem *item)
{
/* 获取子节点的父节点(树节点) */
QTreeWidgetItem* parent = item->parent();
if(parent == NULL){
return;
}
/* 初始化选中的数目为0,下面根据selectCount来判断树节点的状态 */
int selectCount = 0;
/* 获取树节点的子节点总数 */
int childCount = parent->childCount();
/* 循环判断子节点的状态 */
for(int i = 0; i < childCount; i ++){
QTreeWidgetItem* childItem =parent->child(i);
/* 判断当前子节点的状态是否为选中状态,如果是,则加一 */
if(childItem->checkState(0) == Qt::Checked) {
selectCount ++;
}
}
/* 根据selectCount来判断树节点的状态 */
if (selectCount <= 0) {
/* 未选中任何子节点:树节点为未选中状态 */
parent->setCheckState(0, Qt::Unchecked);
} else if (selectCount > 0 && selectCount < childCount) {
/* 部分选中时,树节点为半选状态 */
parent->setCheckState(0, Qt::PartiallyChecked);
} else if (selectCount == childCount){
/* 子节点全选时,树节点为选中状态 */
parent->setCheckState(0, Qt::Checked);
}
}
void MainWindow::treeItemChanged(QTreeWidgetItem *item, int)
{
/* 获取子节点总数 */
int count = item->childCount();
/* 若顶层树节点选中 */
if(Qt::Checked == item->checkState(0) ) {
/* 若选中的项是树节点,count会大于0,否则选中的项是子节点 */
if (count > 0) {
for (int i = 0; i < count; i++) {
/* 子节点全选 */
item->child(i)->setCheckState(0, Qt::Checked);
}
} else {
/* 子节点处理 */
updateParentItem(item);
}
/* 若顶层树节点取消选中时 */
} else if (Qt::Unchecked == item->checkState(0)) {
if (count > 0){
for (int i = 0; i < count; i++) {
/* 子节点全不选 */
item->child(i)->setCheckState(0, Qt::Unchecked);
}
} else {
/* 子节点处理 */
updateParentItem(item);
}
}
}
MainWindow::~MainWindow()
{
}
勾选“同事”则“关羽、刘备、张飞”全部勾选;取消勾选则全部取消;只勾选部分子节点时,“同事”显示为半选状态。这正是群发信息选择联系人的交互。
QTableWidget 继承 QTableView,提供带默认模型的基于项的表视图。表中的项由 QTableWidgetItem 提供。它适合直接绘制小表格,双击即可编辑项内容。
| 成员 | 说明 |
|---|---|
setColumnCount(int) / setRowCount(int) |
设置列数/行数 |
setHorizontalHeaderLabels(QStringList) |
设置水平表头 |
setVerticalHeaderLabels(QStringList) |
设置垂直表头 |
setItem(int row, int col, QTableWidgetItem*) |
设置单元格 |
item(int row, int col) |
取单元格 |
QTableWidgetItem(const QString&) |
构造项 |
setTextAlignment(Qt::Alignment) |
文本对齐 |
cellClicked(int, int) / cellChanged(int, int) |
单元格信号 |
例 47_qtablewidget 表格:绘制一个“姓名/性别”两行两列表格。
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTableWidget>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
/* QTableWidget表格 */
QTableWidget *tableWidget;
/* QTableWidgetItem表格数据(项) */
QTableWidgetItem *tableWidgetItem[4];
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
/* 设置主窗体的大小与位置 */
this->setGeometry(0, 0, 800, 480);
/* 实例化 */
tableWidget = new QTableWidget(this);
/* 设置tableWidget表居中 */
setCentralWidget(tableWidget);
/* 设置列数 */
tableWidget->setColumnCount(2);
/* 设置行数 */
tableWidget->setRowCount(2);
/* 使用标签设置水平标题标签 */
tableWidget->setHorizontalHeaderLabels(
QStringList()<<"姓名"<<"性别"
);
/* 字符串类型链表 */
QList <QString> strList;
strList<<"小明"<<"小红"<<"男"<<"女";
for (int i = 0; i < 4; i++) {
/* 实例化 */
tableWidgetItem[i] = new QTableWidgetItem(strList[i]);
/* 设置文本居中对齐 */
tableWidgetItem[i]->setTextAlignment(Qt::AlignCenter);
}
/* 插入数据,表的index就是一个二维数组数据 */
tableWidget->setItem(0, 0, tableWidgetItem[0]);
tableWidget->setItem(1, 0, tableWidgetItem[1]);
tableWidget->setItem(0, 1, tableWidgetItem[2]);
tableWidget->setItem(1, 1, tableWidgetItem[3]);
}
MainWindow::~MainWindow()
{
}
生成 2×2 表格,表头“姓名/性别”,内容“小明/男”“小红/女”居中对齐。双击单元格可修改内容,也可删除内容。
| 需求 | 首选 |
|---|---|
| 一行/一列排列 | QHBoxLayout / QVBoxLayout |
| 多行多列、需要行列比例 | QGridLayout |
| 少量“标签 + 输入框”表单 | QFormLayout |
| 布局中留空白/顶开 | QSpacerItem 或 addStretch() |
| 页面切换(页签) | QTabWidget |
| 页面切换(列表联动) | QListWidget + QStackedWidget |
| 折叠面板 | QToolBox |
| 多窗口工作区 | QMdiArea |
| 可拖动工具面板 | QDockWidget |
| 大内容滚动查看 | QScrollArea |
flowchart LR
subgraph M["基于模型 Model/View"]
M1["QListView<br/>QTreeView<br/>QTableView<br/>QColumnView"]
M2["需自建 Model<br/>QStringListModel / QStandardItemModel"]
M1 --- M2
end
subgraph I["基于项 Item"]
I1["QListWidget<br/>QTreeWidget<br/>QTableWidget"]
I2["自带内部模型<br/>QListWidgetItem 等"]
I1 --- I2
end
M -->|"数据量大、结构复杂"| V["选型"]
I -->|"数据量小、开发快"| V
| 对比项 | 基于模型(View) | 基于项(Widget) |
|---|---|---|
| 代表类 | QListView/QTreeView/QTableView |
QListWidget/QTreeWidget/QTableWidget |
| 数据 | 需自建 Model | 自带内部模型 |
| 数据冗余 | 少 | 相对多 |
| 效率 | 高(适合大数据) | 适合中小数据 |
| 操作难度 | 需了解建模 | addItem/setItem 即可 |
New File or Project → Application (Qt) → Qt Widgets Application;基类选 QMainWindow。
.ui;本例程组中 27 之前已讲过,29~47 均不勾选。mainwindow.h、mainwindow.cpp 替换为本节代码;main.cpp 保持默认。QUndoView 例程:额外创建 command.h、command.cpp,并在 .pro 的 SOURCES/HEADERS 中加入相应文件。QToolBox、QTabWidget、QScrollArea 例程):按上一篇 1.3 节步骤添加 .qrc 与图片。检查 .pro:默认内容如下,确保 QT += widgets 存在:
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
SOURCES += main.cpp mainwindow.cpp
HEADERS += mainwindow.h
编译运行:Ctrl+R。
交叉编译到开发板:参照《I.MX6U嵌入式Qt开发指南》第十四章配置交叉编译环境,把可执行文件与 Qt 库、字体一起部署到板子运行。
QObject 还是 QWidget?所有基本布局管理类(QBoxLayout、QGridLayout、QFormLayout、QStackedLayout)都继承自 QLayout,而 QLayout 继承自 QObject,不是 QWidget。因此布局本身不是部件,但可以通过 addLayout() 嵌套组合成复杂布局。
QGridLayout 与 QBoxLayout 设置拉伸因子的方式有何不同?QBoxLayout 用 setStretch(int index, int stretch) 为某个子部件设置伸缩因子;QGridLayout 因为二维,需要分别用 setRowStretch(int row, int stretch) 和 setColumnStretch(int col, int stretch) 设置行、列比例系数。
QSpacerItem 是什么?在布局里还有什么更简便的方法产生间隔?QSpacerItem 继承 QLayoutItem,在布局中提供空白间隔,只能在布局里使用,对应 Designer 里的水平/垂直 Spacer。更简便的方法是调用布局的 addStretch(int) 直接添加伸缩量,或用 layout->setSpacing() 设置统一间距。
QTabWidget 和 QStackedWidget 都能做多页面,二者有什么区别?QTabWidget 自带页签,用户直接点击页签切换,是“页签 + 堆叠”的封装;QStackedWidget 只提供堆叠容器,不提供切换入口,通常需要额外搭配 QListWidget,用 currentRowChanged(int) 连接 setCurrentIndex(int) 来实现页面切换(如例 37)。
QListView 与 QListWidget 的本质区别是什么?大项目里怎么选?QListView 基于 Model,需要自己建模(QStringListModel、QStandardItemModel 等)来保存数据,数据与显示分离、冗余少、效率高;QListWidget 基于 Item,本质是“已经封装好模型的 QListView”,直接 addItem 即可。处理大数据、需要多种视图共享同一数据源时用基于模型的 QListView;小数据、追求开发效率时用 QListWidget。
内容来源:《【正点原子】I.MX6U嵌入式Qt开发指南V1.2》第七章 Qt控件 7.5 布局管理、7.6 空间间隔、7.7 容器、7.8 项目视图组(基于模型)、7.9 项目控件组(基于项);配套例程 Embedded-Qt-Tutorial/Qt/01/29_qboxlayout ~ 47_qtablewidget。