模型/视图教程

每个用户界面开发人员都应该了解模型视图编程,本教程的目的就是向您简单介绍这一主题。

表格、列表和树状部件是图形用户界面中经常使用的组件。这些部件访问数据有两种不同的方式。传统的方法是在 widget 中包含用于存储数据的内部容器。这种方法非常直观,但在许多非复杂的应用程序中,它会导致数据同步问题。第二种方法是模型/视图编程,在这种方法中,部件不维护内部数据容器。它们通过标准化接口访问外部数据,从而避免了数据重复。这种方法初看起来似乎很复杂,但一旦仔细研究,不仅很容易掌握,而且模型/视图编程的许多优点也会变得更加清晰。

在此过程中,我们将学习 Qt 提供的一些基本技术,例如

  • 标准部件与模型/视图部件的区别
  • 窗体和模型之间的适配器
  • 开发一个简单的模型/视图应用程序
  • 预定义模型
  • 中间主题,如
    • 树形视图
    • 选择
    • 委托
    • 使用模型测试进行调试

您还将了解使用模型/视图编程是否能更轻松地编写新的应用程序,或者传统的 widget 是否也能很好地工作。

本教程包含示例代码,供您编辑并集成到您的项目中。本教程的源代码位于 Qt 的examples/widgets/tutorials/modelview目录中。

更多详细信息,请参阅参考文档

1.简介

模型/视图(Model/View)是一种用于在处理数据集的 widget 中将数据与视图分离的技术。标准的 Widget 并不是为分离数据和视图而设计的,这也是 Qt Designer 有两种不同类型的 Widget 的原因。这两种类型的 widget 外观相同,但它们与数据的交互方式不同。

标准部件使用的数据是部件的一部分。

视图类对外部数据(模型)进行操作

1.1 标准部件

让我们仔细看看标准表格部件。表格部件是由用户可以更改的数据元素组成的二维数组。通过读写表格 Widget 提供的数据元素,可以将表格 Widget 集成到程序流程中。这种方法非常直观,在许多应用程序中都非常有用,但使用标准表格部件显示和编辑数据库表格可能会出现问题。必须协调数据的两个副本:一个在 widget 外部,一个在 widget 内部。开发人员负责同步这两个版本。除此之外,表现形式和数据之间的紧密耦合也增加了编写单元测试的难度。

1.2 模型/视图的拯救

模型/视图的出现提供了一种使用更通用架构的解决方案。模型/视图消除了标准部件可能出现的数据一致性问题。由于一个模型可以传递给多个视图,因此模型/视图还能让使用同一数据的多个视图变得更容易。最重要的区别在于,模型/视图部件不在表格单元格后面存储数据。事实上,它们直接根据数据进行操作。由于视图类不知道数据的结构,因此需要提供一个封装器,使数据符合QAbstractItemModel 接口。视图使用该接口读取和写入数据。任何实现QAbstractItemModel 的类实例都可以称为模型。一旦视图接收到指向模型的指针,它就会读取和显示模型的内容,并成为模型的编辑器。

1.3 模型/视图小工具概述

下面是模型/视图部件及其相应标准部件的概述。

小工具标准部件
(基于项目的便利类
模型/视图视图类
(用于外部数据
QListWidgetQListView
QTableWidgetQTableView
QTreeWidgetQTreeView
QColumnView 将树显示为列表的层次结构
QComboBox 既可作为视图类使用,也可作为传统 Widget 使用

1.4 在表单和模型之间使用适配器

表单和模型之间的适配器可以派上用场。

我们可以直接从表格本身编辑存储在表格中的数据,但编辑文本字段中的数据要舒服得多。对于只对一个值(QLineEdit,QCheckBox...)而不是数据集进行操作的小工具,没有直接的模型/视图对应来分离数据和视图,因此我们需要一个适配器来连接表单和数据源。

QDataWidgetMapper 我们需要一个适配器,以便将表单与数据源连接起来。

适配器的另一个例子是QCompleter 。Qt 有QCompleter ,用于在 Qt Widgets 中提供自动完成功能,如QComboBox 和下图所示的QLineEditQCompleter 使用模型作为其数据源。

2.一个简单的模型/视图应用程序

如果您想开发一个模型/视图应用程序,应该从哪里开始呢?我们建议您从一个简单的示例开始,然后逐步扩展。这样可以更容易地理解体系结构。事实证明,对于许多开发人员来说,在调用集成开发环境之前试图详细了解模型/视图架构不太方便。从一个有演示数据的简单模型/视图应用程序开始会容易得多。试试看吧!只需将下面示例中的数据替换为您自己的数据即可。

下面是 7 个非常简单和独立的应用程序,展示了模型/视图编程的不同侧面。源代码可在examples/widgets/tutorials/modelview 目录中找到。

2.1 只读表

我们从一个使用QTableView 显示数据的应用程序开始。稍后我们将添加编辑功能。

(文件源:examples/widgets/tutorials/modelview/1_readonly/main.cpp)

// main.cpp
#include <QApplication>
#include <QTableView>
#include "mymodel.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTableView tableView;
    MyModel myModel;
    tableView.setModel(&myModel);
    tableView.show();
    return a.exec();
}

我们有常用的main()函数:

下面是有趣的部分:我们创建一个 MyModel 实例,并使用tableView.setModel(&myModel); 将它的指针传递给tableViewtableView 将调用它收到的指针的方法来找出两件事:

  • 应显示多少行和列。
  • 每个单元格应打印哪些内容。

模型需要一些代码对此做出响应。

我们有一个表格数据集,因此让我们从QAbstractTableModel 开始,因为它比更通用的QAbstractItemModel 更容易使用。

(文件来源:examples/widgets/tutorials/modelview/1_readonly/mymodel.h)。

// mymodel.h
#include <QAbstractTableModel>

class MyModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    explicit MyModel(QObject *parent = nullptr);

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};

QAbstractTableModel 需要实现三个抽象方法。

(文件源代码:examples/widgets/tutorials/modelview/1_readonly/mymodel.cpp)

// mymodel.cpp
#include "mymodel.h"

MyModel::MyModel(QObject *parent)
    : QAbstractTableModel(parent)
{
}

int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
   return 2;
}

int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
    return 3;
}

QVariant MyModel::data(const QModelIndex &index, int role) const
{
    if (role == Qt::DisplayRole)
       return QString("Row%1, Column%2")
                   .arg(index.row() + 1)
                   .arg(index.column() +1);

    return QVariant();
}

行数和列数由MyModel::rowCount() 和MyModel::columnCount() 提供。当视图需要知道单元格的文本内容时,会调用MyModel::data() 方法。行和列信息由参数index 指定,角色设置为Qt::DisplayRole 。下一节将介绍其他角色。在我们的示例中,应显示的数据已经生成。在实际应用中,MyModel 会有一个名为MyData 的成员,它是所有读写操作的目标。

这个小例子展示了模型的被动性。模型不知道何时会被使用,也不知道需要哪些数据。它只是在视图每次请求时提供数据。

当模型的数据需要更改时会发生什么?视图如何意识到数据已经更改并需要再次读取?模型必须发出一个信号,指出哪些单元格范围发生了变化。这将在第 2.3 节中演示。

2.2 利用角色扩展只读示例

除了控制视图显示哪些文本外,模型还控制文本的外观。当我们稍微改变一下模型,就会得到如下结果:

事实上,除了设置字体、背景颜色、对齐方式和复选框所需的data() 方法外,其他都不需要更改。下面是产生上图所示结果的data() 方法。不同的是,这次我们使用参数 int role,根据其值返回不同的信息。

(文件源:examples/widgets/tutorials/modelview/2_formatting/mymodel.cpp)

// mymodel.cppQVariantMyModel::data(constQModelIndex&index, introle)const{introw=index.row();intcol=index.column();// 在调用此方法时生成日志信息    qDebug() << QString("row %1, col%2, role %3")
           .arg(row).arg(col).arg(role);switch(role) {caseQt::DisplayRole:if(row== 0 &&col== 1)returnQString("<--left");if(row== 1 &&col== 1)returnQString("right-->");returnQString("Row%1, Column%2").arg(row+ 1).arg(col+1);caseQt::FontRole:if(row== 0 &&col== 0) {// 仅为单元格(0,0)更改字体           QFontboldFont; boldFont.setBold(true);returnboldFont; }break;caseQt::BackgroundRole:if(row== 1 &&col== 2)// 仅为单元格(1,2)更改背景 返回QBrush(Qt::red);break;caseQt::TextAlignmentRole:if(row== 1 &&col== 1)// 仅更改单元格(1,1)的文本对齐方式 return int(Qt::AlignRight| ::AlignVCenter Qt::AlignVCenter);break;caseQt::CheckStateRole:if(row== 1 &&col== 0)// 为单元格(1,0)添加复选框 returnQt::Checked;break; }返回QVariant(); }

每个格式化属性都将通过单独调用data() 方法从模型中请求。role 参数用于让模型知道请求的是哪个属性:

有关Qt::ItemDataRole 枚举功能的更多信息,请参阅 Qt XML 名称空间文档。

现在,我们需要确定使用分离模型对应用程序性能的影响,因此让我们跟踪视图调用data() 方法的频率。为了跟踪视图调用模型的频率,我们在data() 方法中加入了调试语句,该语句会记录到错误输出流中。在我们的小例子中,data() 将被调用 42 次。每次将光标悬停在字段上时,data() 将再次被调用,每个单元格将被调用 7 次。这就是为什么必须确保在调用data() 时数据可用,并缓存昂贵的查找操作。

2.3 表单元格内的时钟

我们仍然有一个只读表,但这次的内容每秒都在变化,因为我们显示的是当前时间。

(文件源:examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)

QVariant MyModel::data(const QModelIndex &index, int role) const
{
    int row = index.row();
    int col = index.column();

    if (role == Qt::DisplayRole && row == 0 && col == 0)
        return QTime::currentTime().toString();

    return QVariant();
}

要让时钟滴答作响,还缺少一些东西。我们需要每秒告诉视图时间已经改变,需要再次读取。为此,我们需要一个计时器。在构造函数中,我们将其间隔设为 1 秒,并连接其超时信号。

(文件源:examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)

MyModel::MyModel(QObject *parent)
    : QAbstractTableModel(parent)
    , timer(new QTimer(this))
{
    timer->setInterval(1000);
    connect(timer, &QTimer::timeout , this, &MyModel::timerHit);
    timer->start();
}

下面是相应的槽:

(文件源:examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)

void MyModel::timerHit()
{
    // we identify the top left cell
    QModelIndex topLeft = createIndex(0,0);
    // emit a signal to make the view reread identified data
    emit dataChanged(topLeft, topLeft, {Qt::DisplayRole});
}

我们通过发出dataChanged() 信号,要求视图再次读取左上角单元格中的数据。请注意,我们没有将dataChanged() 信号明确连接到视图。这是在调用setModel() 时自动发生的。

2.4 为列和行设置标题

可以通过视图方法隐藏页眉:tableView->verticalHeader()->hide();

不过,页眉内容是通过模型设置的,因此我们要重新实现headerData() 方法:

(文件源:examples/widgets/tutorials/modelview/4_headers/mymodel.cpp)

QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
        switch (section) {
        case 0:
            return QString("first");
        case 1:
            return QString("second");
        case 2:
            return QString("third");
        }
    }
    return QVariant();
}

请注意,方法headerData() 也有一个参数角色,其含义与MyModel::data() 中的相同。

2.5 最小编辑示例

在本示例中,我们将创建一个应用程序,通过重复输入表格单元格中的值,自动填充窗口标题内容。为了方便访问窗口标题,我们将QTableView 放在QMainWindow 中。

模型决定编辑功能是否可用。我们只需修改模型,即可启用可用的编辑功能。这可以通过重新实现以下虚拟方法来实现:setData() 和flags() 。

(文件来源:examples/widgets/tutorials/modelview/5_edit/mymodel.h)

// mymodel.h
#include <QAbstractTableModel>
#include <QString>

const int COLS= 3;
const int ROWS= 2;

class MyModel : public QAbstractTableModel
{
    Q_OBJECT
public:
    MyModel(QObject *parent = nullptr);
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
    Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
    QString m_gridData[ROWS][COLS];  //holds text entered into QTableView
signals:
    void editCompleted(const QString &);
};

我们使用the 二维数组QString m_gridData 来存储数据。这使得m_gridData 成为MyModel 的核心。MyModel 的其余部分就像一个封装器,并使m_gridData 适应QAbstractItemModel 接口。我们还引入了editCompleted() 信号,通过它可以将修改后的文本传送到窗口标题。

(文件源:examples/widgets/tutorials/modelview/5_edit/mymodel.cpp)

bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role == Qt::EditRole) {
        if (!checkIndex(index))
            return false;
        //save value from editor to member m_gridData
        m_gridData[index.row()][index.column()] = value.toString();
        //for presentation purposes only: build and emit a joined string
        QString result;
        for (int row = 0; row < ROWS; row++) {
            for (int col= 0; col < COLS; col++)
                result += m_gridData[row][col] + ' ';
        }
        emit editCompleted(result);
        return true;
    }
    return false;
}

setData每次用户编辑单元格时,都会调用 () 。index 参数将告诉我们哪个字段已被编辑,而value 将提供编辑过程的结果。由于我们的单元格只包含文本,因此角色将始终设置为Qt::EditRole 。如果存在复选框,且用户权限设置为允许选择复选框,则调用时也会将角色设置为Qt::CheckStateRole

(文件源:examples/widgets/tutorials/modelview/5_edit/mymodel.cpp)

Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
    return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}

通过flags() 可以调整单元格的各种属性。

返回Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled 即可向编辑器显示单元格可以被选中。

如果编辑一个单元格修改的数据多于该单元格中的数据,则模型必须发出dataChanged() 信号,以便读取被修改的数据。

3.中间主题

3.1 树状视图

您可以将上面的示例转换为带有树视图的应用程序。只需将QTableView 替换为QTreeView ,即可生成一个读/写树视图。无需对模型进行任何更改。树视图不会有任何层次结构,因为模型本身没有任何层次结构。

QListView,QTableViewQTreeView 都使用一个模型抽象,它是一个合并的列表、表格和树。这使得从同一个模型中使用几种不同类型的视图类成为可能。

这就是我们的示例模型目前的样子:

我们想呈现一棵真实的树。我们在上面的示例中封装了我们的数据,以便制作一个模型。这次我们使用QStandardItemModel ,它是分层数据的容器,也实现了QAbstractItemModel 。要显示一棵树,QStandardItemModel ,必须用QStandardItems 填充,它们能够容纳项目的所有标准属性,如文本、字体、复选框或刷子。

(文件来源:examples/widgets/tutorials/modelview/6_treeview/mainwindow.cpp)

// modelview.cpp
#include "mainwindow.h"

#include <QTreeView>
#include <QStandardItemModel>
#include <QStandardItem>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , treeView(new QTreeView(this))
    , standardModel(new QStandardItemModel(this))
{
    setCentralWidget(treeView);

    QList<QStandardItem *> preparedRow = prepareRow("first", "second", "third");
    QStandardItem *item = standardModel->invisibleRootItem();
    // adding a row to the invisible root item produces a root element
    item->appendRow(preparedRow);

    QList<QStandardItem *> secondRow = prepareRow("111", "222", "333");
    // adding a row to an item starts a subtree
    preparedRow.first()->appendRow(secondRow);

    treeView->setModel(standardModel);
    treeView->expandAll();
}

QList<QStandardItem *> MainWindow::prepareRow(const QString &first,
                                              const QString &second,
                                              const QString &third) const
{
    return {new QStandardItem(first),
            new QStandardItem(second),
            new QStandardItem(third)};
}

我们只需实例化QStandardItemModel 并在构造函数中添加几个QStandardItems 。这样我们就可以创建一个分层数据结构,因为QStandardItem 可以容纳其他QStandardItems 。节点可以在视图中折叠和展开。

3.2 使用选择

我们希望访问选定项的内容,以便将其与层次结构级别一起输出到窗口标题中。

因此,让我们创建几个项目:

(文件源:examples/widgets/tutorials/modelview/7_selections/mainwindow.cpp)

#include "mainwindow.h"

#include <QTreeView>
#include <QStandardItemModel>
#include <QItemSelectionModel>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , treeView(new QTreeView(this))
    , standardModel(new QStandardItemModel(this))
{
    setCentralWidget(treeView);
    auto *rootNode = standardModel->invisibleRootItem();

    // defining a couple of items
    auto *americaItem = new QStandardItem("America");
    auto *mexicoItem =  new QStandardItem("Canada");
    auto *usaItem =     new QStandardItem("USA");
    auto *bostonItem =  new QStandardItem("Boston");
    auto *europeItem =  new QStandardItem("Europe");
    auto *italyItem =   new QStandardItem("Italy");
    auto *romeItem =    new QStandardItem("Rome");
    auto *veronaItem =  new QStandardItem("Verona");

    // building up the hierarchy
    rootNode->    appendRow(americaItem);
    rootNode->    appendRow(europeItem);
    americaItem-> appendRow(mexicoItem);
    americaItem-> appendRow(usaItem);
    usaItem->     appendRow(bostonItem);
    europeItem->  appendRow(italyItem);
    italyItem->   appendRow(romeItem);
    italyItem->   appendRow(veronaItem);

    // register the model
    treeView->setModel(standardModel);
    treeView->expandAll();

    // selection changes shall trigger a slot
    QItemSelectionModel *selectionModel = treeView->selectionModel();
    connect(selectionModel, &QItemSelectionModel::selectionChanged,
            this, &MainWindow::selectionChangedSlot);
}

视图是在一个单独的选择模型中管理选择的,可以使用selectionModel() 方法检索该模型。我们检索选择模型是为了将槽连接到其selectionChanged() 信号。

(文件源:examples/widgets/tutorials/modelview/7_selections/mainwindow.cpp)

void MainWindow::selectionChangedSlot(const QItemSelection & /*newSelection*/, const QItemSelection & /*oldSelection*/)
{
    // get the text of the selected item
    const QModelIndex index = treeView->selectionModel()->currentIndex();
    QString selectedText = index.data(Qt::DisplayRole).toString();
    // find out the hierarchy level of the selected item
    int hierarchyLevel = 1;
    QModelIndex seekRoot = index;
    while (seekRoot.parent().isValid()) {
        seekRoot = seekRoot.parent();
        hierarchyLevel++;
    }
    QString showString = QString("%1, Level %2").arg(selectedText)
                         .arg(hierarchyLevel);
    setWindowTitle(showString);
}

我们通过调用treeView->selectionModel()->currentIndex() 获得与选择对应的模型索引,并通过使用模型索引获得字段的字符串。然后,我们只需计算项的hierarchyLevel 。顶层项没有父项,因此parent() 方法将返回默认构建的QModelIndex() 。因此,我们使用parent() 方法迭代到顶层,同时计算迭代过程中执行的步骤。

选择模型(如上图所示)可以检索,但也可以通过QAbstractItemView::setSelectionModel 设置。因此,3 个视图类可以同步选择,因为只使用一个选择模型实例。要在 3 个视图之间共享一个选择模型,请使用selectionModel() 并使用setSelectionModel() 将结果分配给第二个和第三个视图类。

3.3 预定义模型

使用模型/视图的典型方法是封装特定数据,使其可用于视图类。不过,Qt 也为常见的底层数据结构提供了预定义模型。如果可用的数据结构之一适合您的应用程序,那么预定义模型就是一个不错的选择。

QStringListModel存储字符串列表
QStandardItemModel存储任意分层项
QFileSystemModel封装本地文件系统
QSqlQueryModel封装 SQL 结果集
QSqlTableModel封装 SQL 表
QSqlRelationalTableModel封装带有外键的 SQL 表
QSortFilterProxyModel排序和/或过滤另一个模型

3.4 委托

在迄今为止的所有示例中,数据都是以文本或复选框的形式显示在单元格中,并以文本或复选框的形式进行编辑。提供这些显示和编辑服务的组件称为委托。由于视图使用的是默认委托,因此我们才刚刚开始使用委托。但想象一下,我们想要一个不同的编辑器(如滑块或下拉列表),或者想象一下,我们想要以图形的形式展示数据。让我们来看一个名为 "星级委托"的示例,在这个示例中,星级用于显示评分:

该视图有一个setItemDelegate() 方法,用于替换默认委托并安装自定义委托。可以通过创建一个继承自QStyledItemDelegate 的类来编写新的委托。为了编写一个显示星星且没有输入功能的委托,我们只需覆盖 2 个方法。

class StarDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    StarDelegate(QWidget *parent = nullptr);
    void paint(QPainter *painter, const QStyleOptionViewItem &option,
               const QModelIndex &index) const;
    QSize sizeHint(const QStyleOptionViewItem &option,
                   const QModelIndex &index) const;
};

paint() 根据底层数据的内容绘制星星。数据可通过调用index.data() 查找。委托的sizeHint() 方法用于获取每个星星的尺寸,因此单元格将提供足够的高度和宽度来容纳星星。

如果想在视图类的网格中以自定义图形表示法显示数据,编写自定义委托是正确的选择。如果想离开网格,就不能使用自定义委托,而应使用自定义视图类。

Qt 文档中有关委托的其他参考资料:

3.5 使用 ModelTest 调试

模型的被动性质为程序员提供了新的挑战。模型中的不一致可能导致应用程序崩溃。由于模型会被视图中的许多调用击中,因此很难找出是哪个调用导致应用程序崩溃,以及是哪个操作引入了问题。

Qt Labs 提供了名为ModelTest 的软件,它可以在程序运行时检查模型。每次更改模型时,ModelTest 都会扫描模型并用断言报告错误。这对树状模型尤为重要,因为它们的分层性质可能会造成许多细微的不一致。

与视图类不同,ModelTest 使用超出范围的索引来测试模型。这意味着您的应用程序在使用 ModelTest 时可能会崩溃,即使在不使用 ModelTest 的情况下也能完美运行。因此,在使用 ModelTest 时,您还需要处理所有超出范围的索引。

4.其他信息的良好来源

4.1 书籍

模型/视图编程在 Qt 文档和几本好书中都有广泛介绍。

  1. C++ GUI Programming with Qt 4/ Jasmin Blanchette, Mark Summerfield,Prentice Hall,第 2 版,ISBN 0-13-235416-0。也有德文版:C++ GUI Programmierung mit Qt 4: Die offizielle Einführung,Addison-Wesley, ISBN 3-827327-29-6
  2. The Book of Qt4, The Art of Building Qt Applications/ Daniel Molkentin,Open Source Press, ISBN 1-59327-147-6。译自Qt 4, Einführung in die Applikationsentwicklung开源出版社,ISBN 3-937514-12-0。
  3. Qt 开发基础/ Johan Thelin,Apress,ISBN 1-59059-831-8。
  4. Advanced Qt Programming/ Mark Summerfield,Prentice Hall,ISBN 0-321-63590-6。本书共 150 多页,涵盖了模型/视图编程。

下面列出了上述前三本书中包含的示例程序概览。其中有些程序是开发类似应用程序的很好模板。

示例名称使用的视图类使用的模型涉及方面
团队领导QListviewQStringListModel第 1 册,第 10 章,图 10.6
颜色名称QListViewQSortFilterProxyModel 应用于QStringListModel第 1 册,第 10 章,图 10.8
货币QTableView自定义模型基于QAbstractTableModel只读第 1 册,第 10 章,图 10.10
城市QTableView基于以下内容的自定义模型QAbstractTableModel读取/写入第 1 册,第 10 章,图 10.12
布尔分析器QTreeView自定义模型基于QAbstractItemModel只读第 1 册,第 10 章,图 10.14
轨迹编辑器QTableWidget提供自定义编辑器的自定义委托第 1 册,第 10 章,图 10.15
地址簿QListView QTableView QTreeView自定义模型基于QAbstractTableModel读/写图书 2,第 8.4 章
带排序功能的地址簿QSortfilterProxyModel介绍排序和过滤功能第 2 版,第 8.5 章
带复选框的地址簿在模型/视图中引入复选框图书 2, 第 8.6 章
带有转置网格的地址簿基于自定义代理模型QAbstractProxyModel引入自定义模型图书 2, 第 8.7 章
带拖放功能的通讯簿介绍拖放支持图书 2, 第 8.8 章
使用自定义编辑器的通讯录介绍自定义代表第 2 版,第 8.9 章
视图QListView QTableView QTreeViewQStandardItemModel只读第 3 册,第 5 章,图 5-3
条形图委托QTableView自定义委托,用于基于QAbstractItemDelegate第 3 册,第 5 章,图 5-5
编辑委托QTableView用于编辑的自定义委托,基于QAbstractItemDelegate第 3 册,第 5 章,图 5-6
单个项目视图自定义视图,基于QAbstractItemView自定义视图第 3 册,第 5 章,图 5-7
列表模型QTableView基于自定义模型QAbstractTableModel只读第 3 册,第 5 章,图 5-8
树模型QTreeView自定义模型,基于QAbstractItemModel只读第 3 册,第 5 章,图 5-10
编辑整数QListView自定义模型基于QAbstractListModel读/写第 3 册,第 5 章,清单 5-37,图 5-11
排序QTableViewQSortFilterProxyModel 应用于QStringListModel演示排序第 3 册第 5 章,图 5-12

4.2 Qt 文档

Qt 5.0 提供了 19 个模型/视图示例。这些示例可在 "项目视图示例"页面中找到。

示例名称使用的视图类使用的模型涉及方面
地址簿QTableViewQAbstractTableModel QSortFilterProxyModel使用QSortFilterProxyModel 从一个数据池中生成不同的子集
基本排序/过滤模型QTreeViewQStandardItemModel QSortFilterProxyModel
图表自定义视图QStandardItemModel设计与选择模型合作的自定义视图
颜色编辑器工厂QTableWidget使用新的自定义编辑器增强标准委托,以选择颜色
组合部件映射器QDataWidgetMapper 映射 、 和QLineEdit QTextEdit QComboBoxQStandardItemModel展示了QComboBox 如何充当视图类
自定义排序/过滤模型QTreeViewQStandardItemModel QSortFilterProxyModel用于高级排序和过滤的子类QSortFilterProxyModel
目录视图QTreeViewQFileSystemModel演示如何将模型分配给视图的小示例
可编辑树形模型QTreeView自定义树模型处理树的综合示例,演示如何使用底层自定义模型编辑单元格和树结构
获取更多QListView自定义列表模型动态变化的模型
冻结列QTableViewQStandardItemModel
访谈多个自定义项目模型多个视图
像素器QTableView自定义表格模型自定义委托的实现
拼图QListView自定义列表模型具有拖放功能的模型/视图
简单 DOM 模型QTreeView自定义树模型自定义树模型的只读示例
简单树模型QTreeView自定义树模型自定义树模型的只读示例
简单小部件映射器QDataWidgetMapper 映射 、 和QLineEdit QTextEdit QSpinBoxQStandardItemModelQDataWidgetMapper 基本用法
电子表格QTableView自定义委托
星形委托QTableWidget全面的自定义委托示例。

另有模型/视图技术参考文档

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.