Qt扫盲-Qt Model/View 理论总结 [上篇]

一、概述

Qt 包含了一组 item view类,它们使用 model / view 架构来管理数据之间的关系以及呈现给用户的方式。该体系结构引入的功能开发人员提供了更大的灵活性来自定义表现形式,而且这个框架还提供了一个标准的model 接口,以允许广泛的数据源与现有 item 目 View 一起使用。

在本文中,我们简要介绍了 model / view 编程概念,概述了所涉及的概念,并描述了 item 目View系统的体系结构。对架构中的每个组件都进行了解释,并给出了如何使用所提供的类的示例。这里参考的资料就是Qt的官方资料。

1.model / view 架构

Model-View-Controller,(Model-View-Controller, MVC)是一种源自Smalltalk的设计模式,经常用于构建用户界面。

在《设计模式》一书中,Gamma等人写道:
MVC由三种对象组成。model 是应用程序对象,View是它的屏幕显示,Controller, 定义了用户界面对用户输入的反应方式。

在使用MVC之前,用户界面设计倾向于将这些对象放在一起。MVC将它们解耦以提高灵活性和重用性。

如果 View 和 Controller 对象被组合,结果就是 model / view 架构。这仍然将数据的存储方式和呈现给用户的方式分开,但基于相同的原则提供了一个更简单的框架。

这种分离使得可以在多个不同的View中显示相同的数据,并实现新的View类型,而无需更改底层数据结构

其实就是说,我们的业务如果能脱离界面的话,就最好分离开,而不是糅合,就行 Linux 并不依靠界面运行,但是 Windows系统其实就把系统和界面糅合了一些。

为了灵活地处理用户输入,我们引入了delegate 的概念。

在这个框架中使用delegate 的好处是,它允许自定义数据 item 的渲染和编辑方式。
在这里插入图片描述

该 model 与数据源进行通信,为架构中的其他组件提供接口。通信的性质取决于数据源的类型,以及model 的实现方式。

View从 model 中获取model 索引;这些是对数据 item 的引用。通过向model 提供model 索引,View可以从数据源检索数据 item 。

在标准View中,delegate 渲染数据 item 。当一个 item 目被编辑时,delegate 使用 model 的索引直接与 model 通信。

上面的话就看出来了, model 和 delegate 、view 通信的话,都是delegate 、view 用的 model 的索引。

一般来说, model / view 类可以分为上面描述的三组:model 、view 和 delegate 。

每个组件都由提供公共接口的抽象类定义,在某些情况下,还提供功能的默认实现。

抽象类旨在被子类化,以便提供其他组件所期望的全套功能;同时也允许编写专用组件。

model 、View和delegate 使用 信号(signal)和槽(slot) 相互通信:

  • 来自 model 的信号通知 View 关于数据源所持有的数据的更改。
  • 来自 View 的信号提供了关于用户与正在显示的 item 目交互的信息。
  • 来自 delegate 的信号在编辑过程中用于告诉model 和View关于编辑器的状态。

2. Model

所有 item model 都基于 QAbstractItemModel 类。这个类定义了一个接口,View和delegate 使用该接口访问数据。数据本身并不一定要存储在model 中;它可以保存在由单独的类、文件、数据库或其他应用程序组件提供的数据结构或存储库中。 这就是分离数据。model 目的有点像做了一个中间层来联系和界面的关系,这个要理解理解。

model 类一节将介绍model 的基本概念。

QAbstractItemModel 提供了一个数据接口,它足够灵活,可以处理以表、列表和树的形式表示数据的 View。

然而,在为 列表 和类似 表格 的 数据结构 实现新 model 时,QAbstractListModel 和 QAbstractTableModel 类是更好的起点,因为它们提供了通用函数的适当默认实现。这些类都可以子类化,以提供支持特定类型列表和表的model 。, QAbstractTableModel 我用的比较多,

Qt提供了一些现成的 model 来处理数据 item :

  • QStringListModel 用于存储一个简单的QString元素列表。
  • QStandardItemModel 管理更复杂的 item 目树结构,每个 item 目可以包含任意数据。
  • QFileSystemModel 提供了关于本地文件系统中的文件和目录的信息。
  • QSqlQueryModel、qsqlltablemodel 和 QSqlRelationalTableModel 按 model / view 约定访问数据库(经常用)。

如果这些标准model 不能满足实际的需求,我们就需要子类化QAbstractListModel, QAbstractListModel或QAbstractTableModel来创建自己的自定义model 。

3. View

Qt 完整的提供了对不同类型的 View:

  • QListView显示 item 目列表
  • QTableView显示表中来自model 的数据
  • QTreeView显示分层列表中的数据model item 。

这些类都基于QAbstractItemView抽象基类。虽然这些类是现成的实现,但它们也可以子类化以提供自定义View。
可用的View将在View类一节中介绍。

4. Delegate

QAbstractItemDelegate 是 model / view 框架中代理的抽象基类。

默认的delegate 实现由QStyledItemDelegate提供,它被Qt的标准View用作默认delegate 。

然而,QStyledItemDelegate 和 QItemDelegate 是 绘图 的独立替代方案,并为View中的 item 目提供编辑器。(这个编辑器的话我们就可以用自己的自定义控件了,或者Qt的像 QLineEdit、QSpinBox等控件)

它们之间的区别在于,QStyledItemDelegate 使用当前样式来绘制它的 item 。

因此,在实现自定义delegate 或使用 Qt样式表 时,我们建议使用QStyledItemDelegate作为基类。

5. 排序

在 model / view 架构中有两种排序方法;选择哪种方法取决于你的基础 model 。

如果你的model 是可排序的,就是如果它重新实现了QAbstractItemModel::sort()函数,QTableView和QTreeView都提供了一个API,允许你以编程方式对model 数据进行排序。

此外,我们还可以启用交互式排序(即允许用户通过单击View的标题对数据进行排序),通过分别将QHeaderView::sortIndicatorChanged() 信号连接到 QTableView::sortByColumn() 槽函数 或 QTreeView::sortByColumn() 槽函数。

另一种方法是,如果你的 model 没有所需的接口,或者你想使用 List View 来显示数据,则在 View 中显示数据之前,使用 proxy model 来转换model 的结构。这在 proxy model 一节中有详细介绍。

6. 快捷类

为了让依赖于Qt基于item的item view和table类的应用程序受益,许多便捷的类都派生自标准View类。

它们不打算被子类化。他们的目的就是为了去被使用的。

这些类的包括 QListWidget、QTreeWidget和QTableWidget。这些类不如View类灵活,不能与任意 model 一起使用。

Qt建议我们使用model/view方法来处理itemView中的数据,除非你非常需要一组基于item的类。确实我用了 model view 的编程方式,两个字形容:真香!

如果你想利用 model / view 方法提供的特性,同时仍然使用基于 item 的接口,可以考虑使用View类,例如 QListView、QTableView 和 QTreeView 与 QStandardItemModel。这个就是说,我们 view 还是可以用基于 mode/view 的控件,只是我们用 QStandardItemModel 来表示 view 里面每一个item数据,这样的灵活性就不那么高啦,但是Qt还是提供了的。

二、使用model/view

接下来的几节解释如何在Qt中使用 model/view 模式。每一节都包含一个示例,然后还有一节展示如何创建新组件。

1. Qt包含两种 model

Qt提供的两个标准 model 是QStandardItemModel 和 QFileSystemModel。QStandardItemModel 一个多用途 model ,可用于表示列表、表和树 view 所需的各种不同的数据结构。这个 model 还保存了数据 item 。QFileSystemModel是一个维护目录内容信息的 model 。因此,它本身不保存任何数据 item ,只是表示本地文件系统上的文件和目录。

QFileSystemModel提供了一个现成的 model 来进行实验,可以很容易地配置以使用现有数据。使用这个 model ,我们可以展示如何为现成的 view 设置 model ,并探索如何使用 model 索引操作数据。

2. 在现有 model 中使用 view

QListView和QTreeView类是最适合与QFileSystemModel一起使用的 view 。下面给出的示例在树 view 中显示目录的内容,与列表 view 中的相同信息相邻。这两个 view 共享用户的选择,因此选中的 item 目在两个 view 中都被突出显示。

共用的一个model的嘛。
在这里插入图片描述

我们设置了一个QFileSystemModel,以便它可以使用,并创建一些 view 来显示目录的内容。这展示了使用 model 的最简单方法。 model 的构造和使用是在一个main()函数中完成的:

int main(int argc, char *argv[])
{
    
    
	QApplication app(argc, argv);
	QSplitter *splitter = new QSplitter;

	QFileSystemModel *model = new QFileSystemModel;
	model->setRootPath(QDir::currentPath());

该 model 被设置为使用来自某个文件系统的数据。调用setRootPath()告诉model要将文件系统中的哪个驱动器路径暴露给 view 。

创建两个 view ,以便以两种不同的方式检查 model 中的 item :

QTreeView *tree = new QTreeView(splitter);
tree->setModel(model);
tree->setRootIndex(model->index(QDir::currentPath()));

QListView *list = new QListView(splitter);
list->setModel(model);
list->setRootIndex(model->index(QDir::currentPath()));

view 的构造方式与其他部件相同。
要想在 view 中显示 model 中的 item 目,只需调用它的setModel()函数,将目录 model 作为参数即可

我们在每个 view 上调用setRootIndex()函数,从文件系统 model 中为当前目录传入一个合适的 model 索引,从而过滤 model 提供的数据。

这里使用的index()函数是QFileSystemModel唯一的。我们给它提供一个目录,它会返回一个 model 索引。 model 索引在 model 类中讨论。

函数的其余部分只是显示splitter部件中的 view ,并运行应用程序的事件循环:

	splitter->setWindowTitle("Two views onto the same file system model");
	splitter->show();
	return app.exec();
}

在上面的例子中,我们忽略了如何处理元素的选择。在Item view 中处理选择的部分会更详细地介绍这个主题。

三、Model 类

1. 基本概念

在 model / view 体系结构中, model 提供了一个标准接口, view 和 delegate 使用该接口访问数据。

在Qt中,标准接口是由QAbstractItemModel类定义的。无论数据 item 如何存储在任何底层数据结构中,QAbstractItemModel的所有子类都将数据表示为包含条目表的分层结构。

view 使用这种约定来访问 model 中的数据 item ,但是它们向用户显示信息的方式不受限制。
在这里插入图片描述
model 还通过信号和槽机制通知任何附加 view 有关数据更改的信息。

本节描述了一些基本概念,这些概念对于其他组件通过 model 类访问数据 item 的方式至关重要。后面的部分将讨论更高级的概念。

1.model 索引

为了确保数据的表示与访问数据的方式是分开的,引入了 model 索引的概念。可以通过 model 获得的每条信息都由 model 索引表示。 view 和 delegate 使用这些索引请求要显示的数据 item 。

因此,只有 model 需要知道如何获取数据,并且可以相当通用地定义 model 管理的数据类型。 (model 去外面取数据的格式无所谓,但是变化的内容仅限于model内,不会去影响到 view,下次我们扩展其实就很nice啦,改动的就小些啦)

model 索引包含一个指向创建它们的 model 的指针,这可以防止在使用多个 model 时出现混乱。

QAbstractItemModel *model = index.model();

model 索引提供对信息片段的临时引用,并可用于通过 model 检索或修改数据。由于 model 可能会不时地重新组织其内部结构,因此 model 索引可能会失效,不应该存储。如果需要对某条信息进行长期引用,则必须创建持久 model 索引。这提供了对 model 保持最新的信息的引用。

临时 model 索引由QModelIndex类提供,持久 model 索引由QPersistentModelIndex类提供。

要获得与数据 item 对应的 model 索引,必须为 model 指定三个属性:row 号、column 号和父 item 的 model 索引。

下面几节将详细描述和解释这些属性。

2. 行和列

在其最基本的形式中, model 可以作为一个简单的表来访问,其中的 item 根据其行号和列号进行定位。

这并不意味着底层数据块存储在数组结构中行号和列号的使用只是允许组件相互通信的约定

通过向 model 指定 item 目的行号和列号,我们可以检索关于任何给定 item 目的信息,并获得一个表示该 item 目的索引:

QModelIndex index = model->index(row, column, ...);

为简单的单级数据结构(如列表和表)提供接口的 model 不需要提供任何其他信息,但是,正如上面的代码所示,我们需要在获得 model 索引时提供更多信息。

在这里插入图片描述

上图显示了一个基本表 model 的表示,其中每个 item 目通过一对行号和列号定位。通过将相关的行号和列号传递给 model ,我们获得一个引用数据 item 的 model 索引。

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

model 中的顶级 item 总是通过指定 QModelIndex() 作为它们的父 item 来引用。这将在下一节中讨论。

2. item 的父 item

当在表或列表 view 中使用数据时, model 提供的类表接口是理想的;行号和列号系统与 view 显示 item 的方式完全对应。然而,像树 view 这样的结构要求 model 向其中的 item 目公开一个更灵活的接口。因此,每个 item 目也可以是另一个 item 目表的父 item ,就像树 view 中的顶级 item 目可以包含另一个 item 目列表一样。

当请求一个 model item 的索引时,我们必须提供一些关于该 item 的父 item 的信息。在 model 之外,引用 item 目的唯一方法是通过 model 索引,因此还必须给出父 model 索引:

QModelIndex index = model->index(row, column, parent);

在这里插入图片描述
上图 显示了树 model 的表示,其中每个 item 由父 item 、行号和列号引用。
item 目“A”和“C”在 model 中表示为顶层兄弟:

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

A item 有几个子 item 。 item 目“B”的 model 索引由以下代码获得:

QModelIndex indexB = model->index(1, 0, indexA);

3. Item roles

model 中的 item 可以为其他组件执行不同的 role,从而允许为不同的情况提供不同类型的数据。

例如,Qt::DisplayRole用于访问可以在 view 中显示为文本的字符串。通常, item 包含许多不同 role 的数据,标准 role 由Qt::ItemDataRole定义。

我们可以向 model 请求 item 目的数据,方法是将 item 目对应的 model 索引传递给 model ,并指定一个 role 来获得我们想要的数据类型:

 QVariant value = model->data(index, role);
在这里插入图片描述 role 向 model 指示所引用的数据类型。 iew 可以以不同的方式显示 role ,因此为每个 role 提供适当的信息非常重要。创建新 model 一节更详细地介绍了 role 的一些特定用途。

item 目数据的最常见用途是由 Qt::ItemDataRole 中定义的标准 role 覆盖的。通过为每个 role 提供适当的 item 目数据, model 可以向 view 和 delegate 提供提示,说明 item 目应该如何呈现给用户。不同类型的 view 可以根据需要自由地解释或忽略此信息。还可以为特定于应用程序的目的定义其他 role 。

4. 总结

  • model 索引以一种独立于任何底层数据结构的方式,为 model 提供关于 item 目位置的 view 和 delegate 信息。
  • item 通过它们的行号和列号以及它们的父 item 的 model 索引来引用。
  • model 索引是由 model 根据其他组件(如 view 和 delegate )的请求构造的。
  • 如果在使用index()请求索引时为父 item 指定了有效的 model 索引,则返回的索引将引用 model 中该父 item 下面的 item 。获得的索引指向该 item 的子 item 。
  • 如果在使用index()请求索引时为父 item 指定了无效的 model 索引,则返回的索引将指向 model 中的顶级 item 。
  • role 区分与 item 关联的不同类型的数据。

2. 使用model 的 Index

为了演示如何使用 model 索引从 model 中检索数据,我们设置了一个没有 view 的QFileSystemModel,并在一个小部件中显示文件和目录的名称。虽然这不是使用 model 的正常方式,但它展示了 model 在处理 model 索引时使用的约定。
QFileSystemModel的加载是异步的,以最小化系统资源使用。在处理这个 model 时,我们必须考虑到这一点。
我们用以下方式构建文件系统 model :

QFileSystemModel *model = new QFileSystemModel;
connect(model, &QFileSystemModel::directoryLoaded, [model](const QString &directory) {
    
    
QModelIndex parentIndex = model->index(directory);
int numRows = model->rowCount(parentIndex);
});
model->setRootPath(QDir::currentPath);

在这种情况下,我们首先设置一个默认的QFileSystemModel。我们将它连接到一个lambda,使用该 model 提供的index()的特定实现来获取父索引。在lambda表达式中,我们使用rowCount()函数计算 model 的行数。最后,我们设置QFileSystemModel的根路径,让它开始加载数据并触发lambda表达式。
为简单起见,我们只对 model 第一列中的 item 感兴趣。我们依次检查每一行,获取每行中第一个 item 目的 model 索引,并读取存储在 model 中该 item 目的数据。

for (int row = 0; row < numRows; ++row) {
    
    
QModelIndex index = model->index(row, 0, parentIndex);

为了获得 model 索引,我们指定行号、列号(第一列为0),以及我们想要的所有元素的父元素对应的 model 索引。存储在每一 item 中的文本可以通过 model 的data()函数取得。我们指定 model 索引和DisplayRole以字符串形式获取 item 目的数据。

 QString text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.

}

上面的例子演示了从 model 中检索数据的基本原则:

  • 使用rowCount()和columnCount()可以得到 model 的维度。这些函数通常需要指定一个父 model 索引。
    -model 索引用于访问 model 中的 item 。指定 item 目需要行、列和父 model 索引。
  • 要访问 model 中的顶层元素,可以用QModelIndex()指定一个空的 model 索引作为父索引。
  • item 目包含不同 role 的数据。要获取特定 role 的数据,必须向 model 提供 model 索引和 role 。

四、View 类

1. 概念

在 model / view 架构中, view 从 model 中获取数据 item 并将它们呈现给用户。

数据的表示方式不必与 model 提供的数据表示形式相似,而且可能与用于存储数据 item 的底层数据结构完全不同。

通过使用 QAbstractItemModel 提供的标准 model 接口和 QAbstractItemView 提供的标准 view 接口,以及使用通用方式表示数据 item 的 model 索引,实现了内容与表现的分离。

view 通常管理从 model 中获得的数据的总体布局。它们可以自己渲染单个数据 item ,或者使用 delegate 来处理渲染和编辑功能。

除了显示数据, view 还处理 item 目之间的导航,以及 item 目选择的一些方面。

这些 view 还实现了基本的用户界面功能,例如上下文菜单和拖放。 view 可以为 item 目提供默认的编辑功能,也可以与 delegate 一起提供自定义编辑器。

可以在没有 model 的情况下构建 view ,但是必须提供 model 才能显示有用的信息。 view 通过使用可以为每个 view 单独维护或在多个 view 之间共享的选择 item 来跟踪用户选择的 item 目。

有些 view ,如QTableView和QTreeView,显示标题和 item 。这些也由一个 view 类QHeaderView实现。

标题通常访问包含它们的 view 的同一个 model 。它们使用QAbstractItemModel::headerData() 函数从 model 中获取数据,并且通常以标签的形式显示标题信息。新的标题可以从QHeaderView类子类化,为 view 提供更专门的标签。

2. 使用Qt 提供的 view

Qt 提供了三个可用的 view 类,它们以大多数用户熟悉的方式呈现 model 中的数据。QListView 可以将 model 中的 item 目显示为简单的列表,或者以经典图标 view 的形式显示。QTreeView 将 model 中的 item 目显示为列表的层次结构,允许以紧凑的方式表示深度嵌套结构。QTableView 以表格的形式呈现 model 中的 item 目,很像电子表格应用程序的布局。
在这里插入图片描述

上面显示的标准 view 的默认行为应该足以满足大多数应用程序。它们提供基本的编辑功能,并可以进行定制以适应更专业的用户界面的需求。

1. 使用model

我们将创建的字符串列表 model 作为示例 model ,在其中设置一些数据,并构建一个 view 来显示 model 的内容。这些都可以在一个函数中完成:

int main(int argc, char *argv[])
{
    
    
QApplication app(argc, argv);

// Unindented for quoting purposes:
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";

QAbstractItemModel *model = new StringListModel(numbers);

注意,StringListModel 被声明为 QAbstractItemModel。这就说我们可以使用 model 的抽象接口,并确保代码仍然有效,即使我们将字符串列表 model 替换为不同的 model 。以为用的都是 QAbstractItemModel 接口,这就是多态的牛逼啊!

QListView提供的列表 view 足以显示string列表 model 中的 item 目。我们使用下面的代码来构建 view 和建立 model :

QListView *view = new QListView;
view->setModel(model);

view 按正常方式显示:

	view->show();
	return app.exec();
}

view 渲染 model 的内容,通过 model 的接口访问数据。当用户试图编辑 item 时, view 使用默认 delegate 来提供编辑器部件。
在这里插入图片描述

上图显示了QListView如何表示字符串列表 model 中的数据。由于 model 是可编辑的, view 自动允许使用默认 delegate 编辑列表中的每一 item 。

2. 使用 model 的多个 view

为同一个 model 提供多个 view ,只需为每个 view 设置相同的 model 即可。在下面的代码中,我们创建了两个表 view ,每个都使用了我们为这个例子创建的相同的简单表 model :

QTableView *firstTableView = new QTableView;
QTableView *secondTableView = new QTableView;

firstTableView->setModel(model);
secondTableView->setModel(model);

在 model / view 架构中使用信号和槽意味着对 model 的更改可以传播到所有附加的 view ,确保我们始终可以访问相同的数据,无论使用的是哪个 view 。
在这里插入图片描述

上图显示了同一个 model 的两个不同 view ,每个 view 都包含一些选中的 item 目。尽管来自 model 的数据在整个 view 中一致地显示,但每个 view 都维护自己的内部选择 model 。这在某些情况下可能有用,但对于许多应用程序来说,共享选择 model 是可取的。

3. 处理元素的选择

在 view 中处理元素选择的机制由QItemSelectionModel类提供。所有的标准 view 都默认构建自己的选择 model ,并以正常的方式与它们交互。

view 使用的选择 model 可以通过selectionModel()函数获得,而替换选择 model 可以通过setSelectionModel()指定。当我们想为同一个 model 数据提供多个一致的 view 时,控制 view 使用的选择 model 的能力很有用。

一般来说,除非是 model 或 view 的子类化,否则不需要直接操作选择的内容。不过,如果需要的话,选择 model 的接口也是可以访问的,我们会在12.4.3节中讨论如何处理Item view 中的选择。

1. view 间共享选择

虽然 view 类默认提供自己的选择 model 很方便,但当我们在同一个 model 上使用多个 view 时,通常希望 model 的数据和用户的选择在所有 view 中都保持一致。由于 view 类允许替换它们的内部选择 model ,我们可以使用以下代码实现 view 之间的统一选择:

 secondTableView->setSelectionModel(firstTableView->selectionModel());

第二个 view 是第一个 view 的选择 model 。这两个 view 现在都在同一个选择 model 上操作,保持数据和所选 item 目同步。
在这里插入图片描述

在上面的示例中,使用了相同类型的两个 view 来显示相同 model 的数据。然而,如果使用了两种不同类型的 view ,则所选择的 item 目在每个 view 中可能表现得非常不同;例如,表 view 中的连续选择可以表示为树 view 中高亮显示的 item 目的片段集。

五、Delegate 类

1. 概念

与 model - view - controller 模式不同, model / view 设计没有包含一个完全独立的组件来管理与用户的交互。通常, view 负责向用户展示 model 数据,并负责处理用户输入。为了在获取输入的方式上具有一定的灵活性,交互由 delegate 执行。这些组件提供输入功能,还负责在某些 view 中渲染单个 item 目。控制 delegate 的标准接口定义在QAbstractItemDelegate类中。

delegate 希望能够通过实现paint()和sizeHint()函数来渲染它们自己的内容。然而,简单的基于部件的 delegate 可以继承QStyledItemDelegate而不是QAbstractItemDelegate,并利用这些函数的默认实现。

delegate 编辑器可以通过使用小部件来管理编辑过程,也可以通过直接处理事件来实现。第一种方法将在本节后面介绍,它也会在Spin Box delegate 的例子中展示。

Pixelator的例子展示了如何创建一个自定义 delegate 来为tableview执行特殊的渲染。

2. 使用现有 delegate

Qt提供的标准 view 使用QStyledItemDelegate实例来提供编辑功能。delegate接口的默认实现会以标准 view (QListView、QTableView和QTreeView)的通常风格渲染元素。

所有标准 role 都由标准 view 使用的默认 delegate 处理。解释它们的方式在QStyledItemDelegate文档中有描述。

view 使用的 delegate 由itemDelegate()函数返回。setItemDelegate()函数允许你为标准 view 安装一个自定义 delegate ,在为自定义 view 设置 delegate 时,必须使用这个函数。

3. 一个简单的 delegate

这里实现的 delegate 使用QSpinBox来提供编辑功能,主要用于显示整数的 model 。虽然我们为此设置了一个自定义的基于整数的表 model ,但我们可以轻松地使用QStandardItemModel,因为自定义 delegate 控制数据输入。我们构建一个表 view 来显示 model 的内容,这将使用自定义 delegate 进行编辑。
在这里插入图片描述

我们继承了QStyledItemDelegate的代理,因为我们不想编写自定义的显示函数。但是,我们仍然必须提供管理编辑器部件的函数:

class SpinBoxDelegate : public QStyledItemDelegate
{
    
    
	Q_OBJECT
	
public:
	SpinBoxDelegate(QObject *parent = nullptr);
	
	QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
	const QModelIndex &index) const override;
	
	void setEditorData(QWidget *editor, const QModelIndex &index) const override;
	void setModelData(QWidget *editor, QAbstractItemModel *model,
	const QModelIndex &index) const override;
	
	void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
	const QModelIndex &index) const override;
};

注意,构造 delegate 时没有设置编辑器部件。我们只在需要时构建编辑器部件。

1. 提供编辑器

在这个例子中,当表 view 需要提供一个编辑器时,它要求 delegate 提供一个适合于正在修改的 item 的编辑器部件。createEditor()函数提供了 delegate 设置适当部件所需的一切:

QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
 const QStyleOptionViewItem &/* option */,
 const QModelIndex &/* index */) const
{
    
    
	QSpinBox *editor = new QSpinBox(parent);
	editor->setFrame(false);
	editor->setMinimum(0);
	editor->setMaximum(100);

	return editor;
}

请注意,我们不需要保留指向编辑器部件的指针,因为当不再需要它时, view 会负责销毁它。

我们在编辑器上安装了 delegate 的默认事件过滤器,以确保它提供了用户期望的标准编辑快捷方式。

可以向编辑器添加额外的快捷方式,以允许更复杂的行为;这些将在编辑提示一节中讨论。

view 通过调用我们稍后为这些目的定义的函数来确保编辑器的数据和几何信息被正确设置。我们可以根据 view 提供的 model 索引创建不同的编辑器。例如,如果我们有一列整数和一列字符串,我们可以返回QSpinBox或QLineEdit,这取决于正在编辑哪一列。

delegate 必须提供将 model 数据复制到编辑器中的函数。在这个例子中,我们读取了存储在display role 中的数据,并相应地设置了spin box中的值。

void SpinBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    
    
	int value = index.model()->data(index, Qt::EditRole).toInt();
	
	QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
	spinBox->setValue(value);
}

在这个例子中,我们知道编辑器小部件是一个spin box,但我们可以为 model 中的不同类型的数据提供不同的编辑器,在这种情况下,我们需要在访问其成员函数之前将小部件转换为适当的类型。

2. 向 model 提交数据

当用户完成微调框中的值编辑后, view 会调用setModelData()函数,要求 delegate 将编辑后的值存储到 model 中。

void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, 
	 const QModelIndex &index) const
{
    
    
	QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
	spinBox->interpretText();
	int value = spinBox->value();
	
	model->setData(index, value, Qt::EditRole);
}

由于 view 为 delegate 管理编辑器部件,我们只需要使用提供的编辑器内容更新 model 。在本例中,我们确保微调框是最新的,并使用指定的索引用它包含的值更新 model 。

标准的 QStyledItemDelegate 类通过发出 closeEditor() 信号来通知 view 何时完成编辑。 view 确保编辑器部件被关闭和销毁。在这个例子中,我们只提供了简单的编辑功能,所以我们永远不需要发射这个信号。

所有对数据的操作都通过 QAbstractItemModel 提供的接口来执行。这使得 delegate 基本上独立于它所操作的数据类型,但是为了使用某些类型的编辑器部件,必须做一些假设。在这个例子中,我们假设 model 总是包含整数值,但我们仍然可以将此 delegate 用于不同类型的 model ,因为QVariant为意外数据提供了合理的默认值。

3. 更新编辑器的几何形状

delegate 的职责是管理编辑器的几何图形。几何形状必须在编辑器创建时设置,并且当 item 目的大小或在 view 中的位置发生变化时设置。幸运的是,该 view 在 view 选 item 对象中提供了所有必要的几何信息。

void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
 		const QStyleOptionViewItem &option,
		const QModelIndex &/* index */) const
{
    
    
	editor->setGeometry(option.rect);
}

在这个例子中,我们只使用 item 目矩形中的view选 item 提供的几何信息。呈现具有多个元素的 item 的 delegate 不会直接使用 item 矩形。它将使编辑器相对于 item 目中的其他元素进行定位。

4. Editing hints

在编辑之后, delegate 应该向其他组件提供有关编辑过程结果的提示,并提供有助于任何后续编辑操作的提示。这是通过发送带有适当提示的closeEditor()信号来实现的。

这是由默认的QStyledItemDelegate事件过滤器处理的,我们在构建spin box时安装了它。

可以调整旋转框的行为,使其对用户更友好。在QStyledItemDelegate提供的默认事件过滤器中,如果用户点击Return来确认他们在微调框中的选择,则 delegate 将值提交给 model 并关闭微调框。

我们可以通过在微调框上安装自己的事件过滤器来改变这种行为,并根据需要提供编辑提示;例如,我们可以发送带有EditNextItem提示的closeEditor(),以自动开始编辑 view 中的下一 item 。

另一种不需要使用事件过滤器的方法是提供我们自己的编辑器部件,为了方便,可能会子类化QSpinBox。这种替代方法将使我们能够更多地控制编辑器部件的行为,但代价是编写额外的代码。如果需要自定义标准Qt编辑器部件的行为,在 delegate 中安装事件过滤器通常更容易。

delegate 不必发出这些提示,但是那些不发出提示的 delegate 与应用程序的集成程度较低,而且与那些发出提示以支持常见编辑操作的 delegate 相比,它们的可用性较差。

猜你喜欢

转载自blog.csdn.net/qq_43680827/article/details/132156435
今日推荐