05-布局容器与模型视图.md 80 KB


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

pdf_ref: "《【正点原子】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"

布局容器与模型视图

💡 关联知识:[[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() 绝对定位,界面一旦缩放就会错位。本篇的布局管理器正好解决这个问题;而模型/视图部分则解决“大量结构化数据如何高效显示”的问题。


0. 全章总览

0.1 布局类继承关系

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 一组布局后的部件,可分布显示

0.2 容器与模型/视图索引

分组 控件 一句话作用 例程
布局 QBoxLayoutQHBoxLayout/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

0.3 模型/视图与基于项的区别

  • QListView / QTreeView / QTableView / QColumnView 基于 Model,需要自己建模(如 QStringListModelQStandardItemModel),数据与显示分离,减少数据冗余、程序效率高。
  • QListWidget / QTreeWidget / QTableWidget 基于 Item,是“已经封装好内部模型的视图”,直接 addItemsetItem 即可,操作方便,适合中小数据量。

Qt 提供的常用现成模型:

模型 用途
QStringListModel 存储简单的 QString 列表
QStandardItemModel 管理复杂的树型结构数据项,每项可包含任意数据
QDirModel 提供本地文件系统的文件与目录信息
QSqlQueryModel / QSqlTableModel / QSqlRelationTableModel 访问数据库

1. 布局管理

1.1 QBoxLayout

控件简介

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 像素。


1.2 QGridLayout

控件简介

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,因此按钮大小并不均等。


1.3 QFormLayout

控件简介

QFormLayout 继承 QLayout,以两列形式管理输入部件及其标签:左列是标签,右列是“字段”小部件(如 QLineEditQSpinBox)。适合行列较少的表单;多行多列应改用 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 内出现两行:左侧“用户名:”“密码 :”,右侧对应的单行输入框,行列间距与外框留白已设置。


2. 空间间隔

2.1 QSpacerItem

控件简介

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 之后是水平间隔,把按钮挤向左侧。


3. 容器

容器类用于分组、容纳其他窗口部件。下面依次讲解 QGroupBoxQScrollAreaQToolBoxQTabWidgetQStackedWidgetQWidgetQMdiAreaQDockWidget

3.1 QGroupBox

控件简介

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 示例”组框内规则地垂直排列三个单选框。


3.2 QScrollArea

控件简介

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),因此出现滚动条,可拖动查看图片其余部分。


3.3 QToolBox

控件简介

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


3.4 QTabWidget

控件简介

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) 实现关闭页面。

运行效果说明

点击不同页签切换到对应页面,页面中央显示“标签一/二/三”。页签带图标。


3.5 QStackedWidget

控件简介

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()
{
}

运行效果说明

点击左侧列表的“窗口一/二/三”,右侧堆叠页随之切换显示对应标签。这是同一份代码里“信号 → 槽”跨对象直连的典型用法。


3.6 QWidget

控件简介

QWidget 类是所有用户界面对象的基类(如 QLabel 继承 QFrameQFrame 又继承 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 中已经作为容器使用:hWidgetvWidget 都是 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 本身没有可见外观,它的作用是承载布局与其他控件;把布局设置给容器后,子控件会随容器尺寸变化自动排布。


3.7 QMdiArea

控件简介

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 会释放资源。


3.8 QDockWidget

控件简介

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()
{
}

运行效果说明

停靠窗口出现在主窗口顶部,带有标题“停靠窗口”,内部是一个显示“这是一个测试”的文本编辑框;拖动它可移动并重新停靠。


4. 项目视图组(基于模型)

基于模型的视图需要先建立 Model,再设置给视图。下面依次讲解 QListViewQTreeViewQTableViewQColumnViewQUndoView

4.1 QListView

控件简介

QListView 继承 QAbstractItemView,被 QListWidgetQUndoView 继承。它以简单的非分层列表或图标集合形式显示存储在模型中的项,使用 Qt 的模型/视图体系结构,比旧的 QListBoxQIconView 更灵活。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()
{
}

运行效果说明

三个班级以图标模式显示。双击项可修改内容,也可删除。


4.2 QTreeView

控件简介

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 个二级标题;点击数字前的展开箭头可展开二级标题。


4.3 QTableView

控件简介

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 列的成绩表,列头为“语文、数学、英语”,行列均等高/等宽铺满窗口。


4.4 QColumnView

控件简介

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()
{
}

运行效果说明

第一列显示“广东省”,点它后第二列出现“茂名市、中山市”,点城市后第三列出现对应城镇。


4.5 QUndoView

控件简介

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.hcommand.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.hcommand.cpp,用于重写 QUndoCommand


5. 项目控件组(基于项)

基于项的控件是“自带内部模型的视图”,名字与视图组控件相似(如 QListWidget 继承 QListView)。一般处理大数据时用基于模型的视图,中小数据用基于项的控件更省事。

5.1 QListWidget

控件简介

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 文件;选择一个加入左侧列表。可自行扩展删除项、删除全部等按钮。


5.2 QTreeWidget

控件简介

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()
{
}

运行效果说明

勾选“同事”则“关羽、刘备、张飞”全部勾选;取消勾选则全部取消;只勾选部分子节点时,“同事”显示为半选状态。这正是群发信息选择联系人的交互。


5.3 QTableWidget

控件简介

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 表格,表头“姓名/性别”,内容“小明/男”“小红/女”居中对齐。双击单元格可修改内容,也可删除内容。


6. 总结

6.1 布局选择速查

需求 首选
一行/一列排列 QHBoxLayout / QVBoxLayout
多行多列、需要行列比例 QGridLayout
少量“标签 + 输入框”表单 QFormLayout
布局中留空白/顶开 QSpacerItemaddStretch()
页面切换(页签) QTabWidget
页面切换(列表联动) QListWidget + QStackedWidget
折叠面板 QToolBox
多窗口工作区 QMdiArea
可拖动工具面板 QDockWidget
大内容滚动查看 QScrollArea

6.2 基于模型 vs 基于项

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 即可

7. 构建运行步骤

  1. 新建项目:Qt Creator → New File or ProjectApplication (Qt)Qt Widgets Application;基类选 QMainWindow
    • 布局、容器、模型视图类例程(29~46):不勾选 “Generate form”,用纯代码。
    • 例 27/28 才需要 .ui;本例程组中 27 之前已讲过,29~47 均不勾选。
  2. 替换源码:把 mainwindow.hmainwindow.cpp 替换为本节代码;main.cpp 保持默认。
  3. QUndoView 例程:额外创建 command.hcommand.cpp,并在 .proSOURCES/HEADERS 中加入相应文件。
  4. 资源文件QToolBoxQTabWidgetQScrollArea 例程):按上一篇 1.3 节步骤添加 .qrc 与图片。
  5. 检查 .pro:默认内容如下,确保 QT += widgets 存在:

    QT       += core gui
    greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
    CONFIG += c++11
    SOURCES += main.cpp mainwindow.cpp
    HEADERS += mainwindow.h
    
  6. 编译运行Ctrl+R

  7. 交叉编译到开发板:参照《I.MX6U嵌入式Qt开发指南》第十四章配置交叉编译环境,把可执行文件与 Qt 库、字体一起部署到板子运行。


8. 面试题

Q1:布局管理器都继承自哪个类?它继承 QObject 还是 QWidget

所有基本布局管理类(QBoxLayoutQGridLayoutQFormLayoutQStackedLayout)都继承自 QLayout,而 QLayout 继承自 QObject不是 QWidget。因此布局本身不是部件,但可以通过 addLayout() 嵌套组合成复杂布局。

Q2:QGridLayoutQBoxLayout 设置拉伸因子的方式有何不同?

QBoxLayoutsetStretch(int index, int stretch) 为某个子部件设置伸缩因子;QGridLayout 因为二维,需要分别用 setRowStretch(int row, int stretch)setColumnStretch(int col, int stretch) 设置行、列比例系数。

Q3:QSpacerItem 是什么?在布局里还有什么更简便的方法产生间隔?

QSpacerItem 继承 QLayoutItem,在布局中提供空白间隔,只能在布局里使用,对应 Designer 里的水平/垂直 Spacer。更简便的方法是调用布局的 addStretch(int) 直接添加伸缩量,或用 layout->setSpacing() 设置统一间距。

Q4:QTabWidgetQStackedWidget 都能做多页面,二者有什么区别?

QTabWidget 自带页签,用户直接点击页签切换,是“页签 + 堆叠”的封装;QStackedWidget 只提供堆叠容器,不提供切换入口,通常需要额外搭配 QListWidget,用 currentRowChanged(int) 连接 setCurrentIndex(int) 来实现页面切换(如例 37)。

Q5:QListViewQListWidget 的本质区别是什么?大项目里怎么选?

QListView 基于 Model,需要自己建模(QStringListModelQStandardItemModel 等)来保存数据,数据与显示分离、冗余少、效率高;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