QT模型视图代理笔记

1.概览

  1. 模型:存放数据项
  2. 视图:显示数据项
  3. 代理:视图模型的交互控制

QT内置模型关系图: 

Qt 5.14Qt控件模型视图编程

Qt 5.14.1参考文档

内容

  • 模型/查看编程的介绍
  • 模型/视图体系结构
  • 使用模型和视图
  • Qt中包含了两个模型
  • 使用具有现有模型的视图
  • 模型类
  • 基本概念
  • 使用模型索引
  • 进一步阅读
  • 视图类
  • 概念
  • 使用现有视图
  • 处理选择的项目
  • 代理类
  • 概念
  • 使用现有代理
  • 一个简单的代理
  • 处理项目视图中的选择
  • 概念
  • 使用选择模型
  • 创建新模型
  • 设计模型
  • 只读示例模型
  • 可编辑的模型
  • 下一步
  • 项目视图方便类
  • List widgets
  • Tree widgets
  • Table widgets
  • Common features
  • 公共特性
  • 在项目视图中使用拖放方式
  • 使用方便视图
  • 使用模型/视图类
  • 代理模型
  • 使用代理模型
  • 自定义代理模型
  • 模型子类别引用
  • 项目数据处理
  • 只读访问
  • 导航和模型索引的创建
  • 拖放支持和MIME类型处理
  • 针对大量数据进行性能优化
  • 模型/视图类
  • 相关示例

Model/View Programming 

模型/视图编程的介绍

Qt包含一组项目视图类,它们使用模型/视图体系结构来管理数据和数据呈现给用户的方式之间的关系。该体系结构引入的功能分离为开发人员提供了更大的灵活性来自定义项的表示,并提供了一个标准模型接口,允许与现有的项视图一起使用。在本文档中,我们简要介绍了模型/视图范式,概述了所涉及的概念,并描述了项目视图系统的体系结构。本文解释了体系结构中的每个组件,并给出了说明如何使用所提供的类的示例。

模型视图编程架构

模型-视图-控制器(MVC)是一种起源于小调对话的设计模式,在构建用户界面时经常使用。在《设计模式中,Gamma等人写道:

MVC由三种对象组成。模型是应用程序对象,视图是它的屏幕表示形式,控制器定义了用户界面对用户输入的反应方式。在MVC之前,用户界面设计倾向于将这些对象组合在一起。MVC将它们解耦,以增加灵活性和重用性。

如果视图和控制器对象组合,则结果就是模型/视图体系结构。这仍然将数据的存储方式与呈现给用户的方式分开,但提供了一个基于相同原则的更简单的框架。这种分离使得可以在几个不同的视图中显示相同的数据,并实现新类型的视图类型,而不更改底层数据结构。为了能够灵活地处理用户输入,我们引入了委托的概念。在这个框架中有一个委托的优点是,它允许定制呈现和编辑数据项的方式。

模型/视图体系结构

该模型与一个数据源进行通信,从而为体系结构中的其他组件提供了一个接口。通信的性质取决于数据源的类型和模型的实现方式。 该视图从模型中获取模型索引;这些都是对数据项的引用。通过向模型提供模型索引,视图可以从数据源中检索数据项。 在标准视图中,委托会呈现数据项。编辑项目时,委托直接使用模型索引与模型通信。

通常,模型/视图类可以分为上面描述的三个组:模型、视图和委托。这些组件都由抽象类定义,这些类提供公共接口,在某些情况下还提供特性的默认实现。抽象类需要进行子类化,以便提供其他组件所期望的完整功能集;这还允许编写专门的组件。

模型、视图和委托使用信号和插槽相互通信:

  • 来自模型的信号通知视图有关由数据源所保存的数据的更改。
  • 视图中的信号提供了关于用户与正在显示的项目的交互的信息。
  • 在编辑过程中,可以使用来自委托的信号来告诉模型和视图关于编辑器的状态。

模型

所有的项目模型都是基于QAbstractItemModel 类。此类定义了一个由视图和委托用于访问数据的接口。数据本身不需要存储在模型中;它可以保存在由单独的类、文件、数据库或其他应用程序组件提供的数据结构或存储库中。

围绕模型的基本概念将在关于模型类的一节中介绍。

QAbstractItemModel提供了一个数据接口,该接口足够灵活,可以处理以表、列表和树的形式表示数据的视图。然而,当为列表和类似表的数据结构实现新模型时,QAbstractListModel 和QAbstractTableModel 类是更好的起点,因为它们提供了公共函数的适当的默认实现。每个这些类都可以被子类化,以提供支持特定类型的列表和表的模型。

子类化模型的过程将在“创建新模型”一节中进行讨论。

Qt提供了一些现成的模型,可以用来处理数据项:

如果这些标准模型不满足您的要求,您可以将QAbstractItemModelQAbstractListModelQAbstractTableModel 进行子类化,以创建您自己的自定义模型

视图

为不同类型的视图提供了完整的实现: QListView显示一列项目,QTableView在一个表中显示来自模型的数据,而QTreeView在一个层次结构列表中显示数据的模型项。这些类中的每个类都是基于QAbstractItemView 抽象基类。虽然这些类是现成的实现,但也可以将它们子类化以提供自定义视图。

可用的视图将在有关视图类的部分中介绍View

代理

QAbstractItemDelegate is the abstract base class for delegates in the model/view framework. The default delegate implementation is provided by QStyledItemDelegate, and this is used as the default delegate by Qt's standard views. However, QStyledItemDelegate and QItemDelegate are independent alternatives to painting and providing editors for items in views. The difference between them is that QStyledItemDelegate uses the current style to paint its items. We therefore recommend using QStyledItemDelegate as the base class when implementing custom delegates or when working with Qt style sheets.

QAbstractItemDelegate 是模型/视图框架中的委托的抽象基类。默认的委托实现是由QStyledItemDelegate提供,它被Qt的标准视图用作默认的委托。然而 QStyledItemDelegate 和 QItemDelegate是在视图中绘制项目和为项目提供编辑器的独立替代方案。它们之间的区别是, QStyledItemDelegate使用当前样式来绘制其项目。因此,我们建议在实现自定义委托或使用Qt样式表时,使用QStyledItemDelegate作为基类。

委托在Delegate Classes. 一节中描述。

分类

在模型/视图体系结构中有两种进行排序的方法;选择哪种方法取决于您的底层模型。

如果您的模型是可排序的,即,如果它重新实现了QAbstractItemModel::sort() 函数QTableView和QTreeView都提供了一个API,允许您以编程方式对模型数据进行排序。此外,您还可以启用交互式排序(即,允许用户通过单击视图的标头来对数据进行排序)通过连接 QHeaderView::sortIndicatorChanged() 信号到QTableView::sortByColumn()槽或 QTreeView::sortByColumn() 槽,各自的槽。

如果您的模型没有所需的接口,或者您希望使用列表视图来显示数据,另一种方法是在在视图中显示数据之前使用代理模型来转换模型的结构。这将在“代理模型”一节中详细介绍。

比较方便的类

为了方便依赖于Qt的基于项的项视图和表类的应用程序,有许多方便类。它们并不打算被子类化的。

这些类的例子包括QListWidget,QTreeWidget和QTableWidget。

这些类不如视图类灵活,不能与任意模型使用。我们建议您使用模型/视图方法来处理项视图中的数据,除非您强烈需要一个基于项的类集。

如果您希望在使用基于项的接口的同时充分使用模型/视图方法提供的特性,请考虑使用视图类,如QListViewQTableViewQStandardItemModel.。

使用模型和视图

以下部分解释了如何在Qt中使用模型/视图模式。每个部分都包含一个示例,后面是一个显示如何创建新组件的部分。

Qt中包含了两个模型

Qt提供的两个标准模型分别是QStandardItemModel 和QFileSystemModelQStandardItemModel 是一个多用途的模型,可用于表示列表、表和树视图所需的各种不同的数据结构。这个模型还包含了数据项。QFileSystemModel 是一种维护有关目录内容的信息的模型。因此,它本身并不包含任何数据项,而只是表示本地归档系统上的文件和目录。

QFileSystemModel 提供了一个现成的实验模型,并且可以轻松地配置为使用现有的数据。使用这个模型,我们可以展示如何设置用于现成视图的模型,并探索如何使用模型索引来操作数据。

使用具有现有模型的视图

QListView和QTreeView类是最适合使用QFileSystemModel 的视图。下面的示例显示了树视图中一个目录的内容,旁边是列表视图中的相同信息。视图共享用户的选择,以便在两个视图中高亮显示。

 我们设置了一个QFileSystemModel ,以便它可以随时使用,并创建了一些视图来显示目录的内容。这显示了使用模型的最简单的方法。模型的构建和使用是在一个main函数中执行的:

  int main(int argc, char *argv[])

  {

      QApplication app(argc, argv);

      QSplitter *splitter = new QSplitter;

      QFileSystemModel *model = new QFileSystemModel;

      model->setRootPath(QDir::currentPath());

该模型被设置为使用来自某个文件系统的数据。对setRootPath()的调用告诉模型要向视图公开文件系统上的哪个驱动器。

我们创建了两个视图,以便我们可以以两种不同的方式检查模型中保存的项:

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()));

这些视图的构造方式与其他小部件相同。设置一个视图以显示模型中的项,只需要用目录模型作为参数来调用其setModel()函数。我们通过在每个视图上调用setRootIndex()函数来过滤模型提供的数据,并从当前目录的文件系统模型中传递一个合适的模型索引。

本例中使用的index()函数对于QFileSystemModel是唯一的;我们给它提供一个目录,它返回一个模型索引。模型索引在模型类中进行了讨论。

该函数的其余部分只是splitter widget中的视图,并运行应用程序的事件循环:

splitter->setWindowTitle("Two views onto the same file system model");

      splitter->show();

      return app.exec();

  }

在上面的例子中,我们忽略了如何处理项的选择。这个主题在“处理项视图中的选择”一节中有更详细的介绍。

模型类

在检查如何处理选择之前,您可能会发现检查模型/视图框架中使用的概念是很有用的。

基本概念

在模型/视图体系结构中,模型提供了一个标准接口,视图和委托使用该接口访问数据。在Qt中,标准接口是由QAbstractItemModel类定义的。无论数据项如何存储在任何底层数据结构中,QAbstractItemModel的所有子类都将数据表示为包含项表的层次结构。视图使用这种约定来访问模型中的数据项,但在向用户显示此信息的方式上不受限制。

模型还通过信号和槽机制将数据的更改通知任何附加的视图。

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

模型索引

为了确保数据的表示与访问数据的方式是分开的,引入了模型索引的概念。可以通过模型获得的每条信息都由模型索引表示。视图和委托使用这些索引来请求要显示的数据项。

因此,只有模型需要知道如何获取数据,并且模型管理的数据类型可以相当一般地定义。模型索引包含一个指向创建它们的模型的指针,这在处理多个模型时防止了混乱。

QAbstractItemModel *model = index.model();

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

要获得与数据项对应的模型索引,必须向模型指定三个属性:行号、列号和父项的模型索引。以下部分将详细描述和解释这些属性。

行和列

在最基本的形式中,可以将模型作为一个简单的表进行访问,其中的项按行号和列号进行定位。这并不意味着底层数据块存储在数组结构中;使用行号和列号只是允许组件彼此通信的一种约定。通过向模型指定项目的行号和列号,我们可以检索关于任何给定项目的信息,并且我们接收到一个表示项目的索引:

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

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

行和列

该图显示了一个基本表模型的表示形式,其中每个项由一对行号和列号定位。我们通过向模型传递相关的行号和列号来获得引用数据项的模型索引。

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

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

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

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

父项

当在表或列表视图中使用数据时,模型提供的项目数据的类似表格的接口是理想的;行号和列号系统完全映射到视图显示项的方式。然而,树视图等结构要求模型向其中的项公开更灵活的接口。因此,每个项也可以是另一个项表的父表,这与树视图中的顶级项可以包含另一个项列表的方式非常相似。

当为模型项请求索引时,我们必须提供关于项的父项的一些信息。在模型之外,引用一个项的唯一方法是通过模型索引,因此必须同时给出父模型索引:

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

父类、行和列

该图显示了树模型的表示形式,其中每个项由父项、行号和列号引用。

项目“A”和“C”在模型中表示为顶层兄弟:

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

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

项目A有很多孩子。项目“B”的模型索引由以下代码获得:

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

项目角色/数据元素显示方式

模型中的项可以为其他组件执行各种角色,允许为不同的情况提供不同类型的数据。例如,Qt::DisplayRole用于访问可以在视图中显示为文本的字符串。通常,项包含许多不同角色的数据,标准角色由Qt::ItemDataRole定义。

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

  QVariant value = model->data(index, role);

项目角色

角色向模型指明引用的数据类型。视图可以以不同的方式显示角色,因此为每个角色提供适当的信息非常重要。

创建新模型一节更详细地介绍了角色的一些特定用法。

项目数据的大多数常见用途都包含在Qt::ItemDataRole中定义的标准角色中。通过为每个角色提供适当的项目数据,模型可以向视图和委托提供关于项目应该如何呈现给用户的提示。不同类型的视图可以根据需要自由解释或忽略此信息。还可以为特定于应用程序的目的定义其他角色。

摘要

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

使用模型索引

为了演示如何使用模型索引从模型中检索数据,我们设置了一个没有视图的QFileSystemModel,并在一个小部件中显示文件和目录的名称。尽管这没有显示使用模型的正常方式,但它演示了模型在处理模型索引时使用的约定。

QFileSystemModel 加载是异步的,以最小化系统资源的使用。我们在处理这个模型时必须考虑到这一点。

我们以以下方式构建了一个文件系统模型:

      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上,在其中,我们将使用该模型提供的index()的特定实现来获得一个父索引。在lambda中,我们使用 rowCount()函数来计算模型中的行数。最后,我们设置了QFileSystemModel的根路径,以便它开始加载数据并触发lambda。

为简单起见,我们只对模型的第一列中的项感兴趣。我们依次检查每一行,为每行中的第一项获得一个模型索引,并读取在模型中为该项存储的数据。

      for (int row = 0; row < numRows; ++row) {

          QModelIndex index = model->index(row, 0, parentIndex);

为了获得模型索引,我们需要指定行号、列号(第一列为0),并为我们想要的所有项的父项指定适当的模型索引。使用模型的data()函数检索存储在每个项中的文本。我们指定模型索引和显示角色,以字符串的形式获取项的数据。

QString text = model->data(index, Qt::DisplayRole).toString();

          // Display the text in a widget.

      }

上面的例子演示了用于从模型中检索数据的基本原则:

  • 可以使用rowCount()columnCount()找到模型的维度。这些函数通常需要指定一个父模型索引。
  • 模型索引用于访问模型中的项。在指定项目时,需要使用行、列和父模型索引。
  • 要访问模型中的顶级项,请使用QModelIndex()指定一个空模型索引为父索引。
  • 项目包含针对不同角色的数据。要获取特定角色的数据,必须同时向模型提供模型索引和角色。

进一步阅读

可以通过QAbstractItemModel提供的标准接口来创建新的模型。在“创建新模型”部分中,我们将通过创建一个用来保存字符串列表的方便的现成模型来演示这一点。

视图类

概念

在模型/视图体系结构中,视图从模型中获取数据项,并将其呈现给用户。数据的呈现方式不需要类似于模型提供的数据的表示,而且可能与用于存储数据项的底层数据结构完全不同。

内容和表示方式的分离是通过使用QAbstractItemModel中提供的标准模型接口来实现的,由QAbstractItemView提供的标准视图接口,以及使用以一般方式表示数据项的模型索引来实现的。视图通常会管理从模型中获得的数据的总体布局。它们可以自己呈现数据的各个项,或者使用委托来处理呈现和编辑特性。

除了显示数据之外,视图还可以处理项目之间的导航,以及项目选择的某些方面。这些视图还实现了基本的用户界面特性,如上下文菜单和拖放功能。视图可以为项目提供默认的编辑工具,也可以与委托一起提供自定义编辑器。

视图可以在没有模型的情况下构建,但必须提供模型才能显示有用的信息。视图跟踪用户通过使用选择所选择的项目,这些选择可以为每个视图单独维护,或在多个视图之间共享。

一些视图,如QTableView和QTreeView,显示标题和项。这些也是由视图类QHeaderView实现的。头文件通常访问与包含它们的视图相同的模型。它们使用QAbstractItemModel::headerData()函数从模型中检索数据,并且通常以标签的形式显示头信息。新的标题可以从QHeaderView类的子类化,以为视图提供更专门的标签。

使用现有视图

提供了三个现成的视图类,它们以大多数用户都熟悉的方式呈现来自模型的数据。QListView可以将一个模型中的项目显示为一个简单的列表,或以一个经典的图标视图的形式显示。QTreeView将模型中的项目显示为列表的层次结构,允许以紧凑的方式表示深度嵌套的结构。QTableView以表格的形式呈现来自模型中的项目,很像电子表格应用程序的布局。

对于大多数应用程序来说,上述标准视图的默认行为应该就足够了。它们提供了基本的编辑设施,并可以进行定制,以适应更专业的用户界面的需要。

使用模型

我们将我们创建的字符串列表模型作为一个示例模型,用一些数据来设置它,并构造一个视图来显示模型的内容。这一切都可以在一个功能中执行:

  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。这允许我们使用模型的抽象接口,并确保代码仍然可以工作,即使我们用不同的模型替换字符串列表模型。

QListView提供的列表视图足以显示字符串列表模型中的项目。我们构建了这个视图,并使用以下代码行设置了这个模型:

  QListView *view = new QListView;

  view->setModel(model);

该视图以正常方式显示:

      view->show();

      return app.exec();

  }

该视图呈现模型的内容,并通过模型的接口访问数据。当用户尝试编辑项目时,视图将使用默认委托来提供编辑器小部件。

上图显示了QListView如何表示字符串列表模型中的数据。由于模型是可编辑的,因此视图自动允许使用默认委托编辑列表中的每个项目。

Using multiple views of a model

使用一个模型的多个视图

在同一个模型上提供多个视图只是需要为每个视图设置相同的模型。在下面的代码中,我们创建了两个表视图,每个视图都使用我们为本例创建的相同的简单表模型:

      QTableView *firstTableView = new QTableView;

      QTableView *secondTableView = new QTableView;

      firstTableView->setModel(model);

      secondTableView->setModel(model);

在模型/视图体系结构中使用信号和插槽意味着对模型的更改可以传播到所有附加的视图,从而确保无论使用什么视图,我们都可以始终访问相同的数据。

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

项选择的处理

处理视图中项目选择的机制由QItemSelectionModel 提供。默认情况下,所有的标准视图都构建自己的选择模型,并以正常的方式与它们交互。一个视图所使用的选择模型可以通过selectionModel()函数获得,并且通过设置setSelectionModel()可以指定一个替代的选择模型。当我们希望为同一模型数据提供多个一致的视图时,控制视图所使用的选择模型的能力是很有用的。

通常,除非您要继承一个模型或视图,否则您不需要直接操作选择的内容。但是,如果需要,可以访问到选择模型的接口,这在处理项视图中的选择中进行了探讨。

视图间共用选择

尽管视图类在默认情况下提供它们自己的选择模型是很方便的,但当我们在同一个模型上使用多个视图时,通常希望模型的数据和用户的选择在所有视图中一致地显示。因为视图类允许替换它们的内部选择模型,我们可以通过以下行实现视图之间的统一选择:

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

第二个视图提供了第一个视图的选择模型。现在,这两个视图都在相同的选择模型上运行,从而保持数据和所选项目的同步。

在上面显示的示例中,使用了相同类型的两个视图来显示相同模型的数据。但是,如果使用了两种不同类型的视图,则所选项目在每个视图中的表示可能非常不同;例如,表视图中的连续选择可以表示为树视图中高亮显示的项目片段集。

代理类

概念

与模型-视图-控制器模式不同,模型/视图设计不包含一个完全独立的组件来管理与用户的交互。通常,视图负责向用户表示模型数据,并处理用户的输入。为了在获得此输入的方式上有一些灵活性,将由委托执行交互。这些组件提供了输入功能,并负责在某些视图中呈现单个项目。控制委托的标准接口是在QAbstractItemDelegate 中定义的。

委托们可以通过实现paint()和sizeHint()函数来自己呈现他们的内容。但是,简单的基于小部件的委托可以进行子类化QStyledItemDelegate ,而不是QAbstractItemDelegate,并利用这些函数的默认实现。

Editors for delegates can be implemented either by using widgets to manage the editing process or by handling events directly. The first approach is covered later in this section, and it is also shown in the Spin Box Delegate example.

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

Pixelator示例展示了如何创建一个为表视图执行专门渲染的自定义委托。

使用现有代理

Qt提供的标准视图使用QStyledItemDelegate 的实例来提供编辑工具。这个委托接口的默认实现以每个标准视图的常规样式呈现项:QListView、QTableView和QTreeView。

所有standard roles都由标准视图使用的默认委托处理。QStyledItemDelegate文档中描述了解释它们的方法。

视图所使用的委托由itemDelegate()函数返回。setItemDelegate()函数允许您为标准视图安装自定义委托,并且在为自定义视图设置委托时必须使用此函数。

一个简单的代理

这里实现的委托使用QSpinBox提供编辑功能,主要用于显示整数的模型。尽管我们为此目的设置了一个基于整数的自定义表模型,但是我们可以很容易地使用QStandardItemModel来代替,因为自定义委托控制数据条目。我们构造一个表视图来显示模型的内容,这将使用自定义委托进行编辑。

我们对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;

  };

请注意,在构建委托时,没有设置任何编辑器小部件。我们只在需要时构建一个编辑器小部件。

提供编辑器

在本例中,当表视图需要提供编辑器时,它要求委托提供适合于被修改项的编辑器小部件。createEditor()函数提供了委托能够设置合适的小部件所需的一切:

  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;

  }

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

我们在编辑器上安装委托的默认事件过滤器,以确保它提供用户期望的标准编辑快捷方式。可以在编辑器中添加额外的快捷方式,以允许更复杂的行为;这些将在编辑提示一节中讨论。

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

委托必须提供将模型数据复制到编辑器的函数。在本例中,我们读取存储在display角色中的数据,并相应地设置旋转框中的值。

  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);

  }

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

向模型提交数据

当用户完成了对旋转框中的值的编辑后,视图会要求委托通过调用setModelData()函数来将编辑后的值存储在模型中。

  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);

  }

由于视图管理委托的编辑器小部件,所以我们只需要使用所提供的编辑器的内容来更新模型。在这种情况下,我们确保spinBox是最新的,并使用指定的索引使用其包含的值更新模型。

标准的QStyledItemDelegate 通过发出closeEditor()信号来通知视图。视图确保关闭并销毁编辑器小部件。在本例中,我们只提供简单的编辑工具,所以我们不需要发出这个信号。

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

Updating the editor's geometry

更新编辑器的几何图形

委托负责管理编辑器的几何图形。在创建编辑器时,以及在视图中更改项目的大小或位置时,必须设置几何图形。幸运的是,视图在视图选项对象中提供了所有必要的几何信息。

  void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,

  const QStyleOptionViewItem &option,

                        const QModelIndex &/*index */) const

  {

      editor->setGeometry(option.rect);

  }

In this case, we just use the geometry information provided by the view option in the item rectangle. A delegate that renders items with several elements would not use the item rectangle directly. It would position the editor in relation to the other elements in the item. 

在本例中,我们只使用项目矩形中的视图选项提供的几何信息。呈现带有多个元素的项的委托不会直接使用项矩形。它将根据项目中的其他元素对编辑器进行定位。

编辑提示

在编辑之后,委托应该向其他组件提供关于编辑过程结果的提示,并提供将帮助任何后续编辑操作的提示。这是通过发出带有适当提示的closeEditor()信号来实现的。这是由默认的QStyledItemDelegate事件过滤器来处理的,它是我们在构造spinBox 时安装的。

spinBox 的行为可以调整,使其更用户友好。在QStyledItemDelegate提供的默认事件过滤器中,如果用户点击Return以确认他们在旋转框中的选择,则委托将值提交给模型并关闭旋转框。我们可以通过在旋转框上安装我们自己的事件过滤器来改变这种行为,并提供适合我们需要的编辑提示;例如,我们可以发出closeEditor()和EditNextItem提示,以自动开始编辑视图中的下一项。

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

委托不一定要发出这些提示,但那些不发出这些提示的委托与应用程序的集成程度会较低,而且与发出提示以支持公共编辑操作的委托相比,它们的可用性也会较差。

视图项的选择处理

概述

项目视图类中使用的选择模型提供了基于模型/视图体系结构设施的选择的一般描述。尽管用于操作选择的标准类对于所提供的项目视图已经足够了,但是选择模型允许您创建专门的选择模型,以满足您自己的项目模型和视图的需求。

有关在视图中选择的项的信息存储在QItemSelectionModel类。这将为单个模型中的项维护模型索引,并且独立于任何视图。由于在一个模型上可以有多个视图,因此可以在不同的视图之间共享选择,从而允许应用程序以一致的方式显示多个视图。

选择由选择范围组成。通过仅记录所选项目的每个范围的开始和结束模型索引,这些方法有效地维护了关于大型项目选择的信息。项目的不连续选择是通过使用多个选择范围来描述选择来构造的。

选择应用于选择模型所持有的模型索引集合。应用项目的最新选择称为当前选择。即使在应用了该选择之后,也可以通过使用某些类型的选择命令来修改该选择的效果。这些将在本节后面讨论。

当前项目和所选项目

在视图中,总是有一个当前项和一个选中项——两个独立的状态。一个项可以是当前项,同时被选中。视图负责确保总是有一个当前项,例如,键盘导航需要一个当前项。

下表突出显示了当前项目和选定项目之间的差异。

Current Item

Selected Items

当前只能有一个项目。

可以有多个选定的项目。

当前项目将通过键导航或鼠标按钮点击进行更改。

项目的选择状态会被设置或取消设置,这取决于几种预定义的模式,例如,单个选择、多重选择等。当用户与项目交互时的-。

如果按下编辑键F2或双击该项目(如果启用了编辑),则将编辑当前项目。

当前项目可以与锚点一起使用,以指定应选择或取消选择的范围(或两者的组合)。

当前项目由焦点矩形表示。

选定的项目用选择矩形表示。

在操作选择时,将QItemSelectionModel视为项模型中所有项的选择状态的记录通常是有帮助的。一旦设置了选择模型,就可以选择、取消选择项目集合,或者切换它们的选择状态,而不需要知道哪些项目已经被选中。可以在任何时候检索所有选中项的索引,并且可以通过信号和槽机制将选择模型的更改通知给其他组件。

使用选择模型

标准视图类提供了可以在大多数应用程序中使用的默认选择模型。使用视图的selectionModel() 功能可以获得属于一个视图的选择模型,并使用setSelectionModel()在多个视图之间共享,因此通常不需要构建新的选择模型。

通过指定一个模型和一对模型索引来创建一个QItemSelection。这使用索引引用给定模型中的项,并将它们解释为所选项目块中的左上角和右下角的项目。要将选择应用于模型中的项目,需要将选择提交给选择模型;这可以通过多种方式来实现,每一种方式都对选择模型中已经存在的选择有不同的影响。

选择项目

为了演示选择的一些主要特性,我们构建了一个总共有32个项目的自定义表模型的实例,并对其数据打开一个表视图:

      TableModel *model = new TableModel(8, 4, &app);

      QTableView *table = new QTableView(0);

      table->setModel(model);

      QItemSelectionModel *selectionModel = table->selectionModel();

此时将检索表视图的默认选择模型,以供以后使用。我们不修改模型中的任何项,而是选择了视图将显示在表的左上角的几个项。为此,我们需要检索与所选区域中左上角和右下角的项对应的模型索引:

      QModelIndex topLeft;

      QModelIndex bottomRight;

      topLeft = model->index(0, 0, QModelIndex());

      bottomRight = model->index(5, 2, QModelIndex());

要在模型中选择这些项目,并在表视图中查看相应的更改,我们需要构造一个选择对象,然后将其应用于选择模型:

      QItemSelection selection(topLeft, bottomRight);

      selectionModel->select(selection, QItemSelectionModel::Select);

使用由选择标志组合定义的命令将选择应用到选择模型。在这种情况下,使用的标志导致选择对象中记录的项被包括在选择模型中,而不管它们以前的状态如何。视图将显示所产生的选择。

可以使用由选择标志定义的各种操作来修改项的选择。这些操作产生的选择可能具有复杂的结构,但它由选择模型有效地表示。当我们检查如何更新选择时,将描述使用不同的选择标志来操作所选项。

读取选择状态

可以使用selectedIndexes()函数读取选择模型中存储的模型索引。这返回一个未排序的模型索引列表,只要我们知道它们为哪个模型,我们就可以遍历它们:

      const QModelIndexList indexes = selectionModel->selectedIndexes();

      for (const QModelIndex &index : indexes) {

          QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());

          model->setData(index, text);

      }

上面的代码使用一个基于范围的为循环来遍历和修改与选择模型返回的索引对应的项。

选择模型会发出信号来指示选择过程中的变化。这些信息会通知其他组件关于对整体选择和项模型中当前关注的项的更改。我们可以将选择中的selectionChange()信号连接到一个插槽中,并在选择改变时检查模型中被选择或取消选择的项目。该插槽具有两个QItemSelection 对象:一个包含与新选择的项目对应的索引列表;另一个包含与新取消选择的项目对应的索引。

在下面的代码中,我们提供了一个插槽,接收selectionChanged()信号,用字符串填充选定的项目,并清除取消选择的项目的内容。

  void MainWindow::updateSelection(const QItemSelection &selected,

      const QItemSelection &deselected)

  {

      QModelIndexList items = selected.indexes();

      for (const QModelIndex &index : qAsConst(items)) {

          QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());

          model->setData(index, text);

      }

      items = deselected.indexes();

      for (const QModelIndex &index : qAsConst(items)) {

          model->setData(index, QString());

  }

我们可以通过将currentChanged()信号连接到一个用两个模型索引调用的插槽来跟踪当前关注的项目。这些分别对应于先前关注的项目和当前关注的项目。

在下面的代码中,我们提供了一个接收currentChanged() 信号,并使用提供的信息更新QMainWindow的状态栏:

  void MainWindow::changeCurrent(const QModelIndex ¤t,

      const QModelIndex &previous)

  {

      statusBar()->showMessage(

          tr("Moved from (%1,%2) to (%3,%4)")

              .arg(previous.row()).arg(previous.column())

              .arg(current.row()).arg(current.column()));

  }

用户对这些信号的监控选择很简单,但我们也可以直接更新选择模型。

更新选择

选择命令由选择标志的组合提供,这些选择标志由QItemSelectionModel::SelectionFlag定义。每个选择标志都告诉选择模型在调用select()函数时如何更新所选项的内部记录。最常用的标志是Select标志,它指示选择模型将指定的项记录为已选中。Toggle标志导致选择模型反转指定项的状态,选择给定的任何取消选择的项,并取消选择当前选中的任何项。取消选择标志取消选择所有指定的项。

通过创建选择项并将它们应用于选择模型,可以更新选择模型中的单个项目。在下面的代码中,我们将对上面所示的表模型应用对项目的第二次选择,使用切换命令来反转给定的项目的选择状态。

      QItemSelection toggleSelection;

      topLeft = model->index(2, 1, QModelIndex());

      bottomRight = model->index(7, 3, QModelIndex());

      toggleSelection.select(topLeft, bottomRight);

      selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);

此操作的结果显示在表格视图中,提供了一种方便的方式来可视化我们所取得的成果:

默认情况下,选择命令只操作模型索引指定的单个项。但是,用于描述选择命令的标志可以与其他标志结合使用,以更改整个行和列。例如,如果只使用一个索引调用select(),但使用select和Rows的组合命令,则会选择包含引用的项的整个行。下面的代码演示了行和列标志的用法:

      QItemSelection columnSelection;

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

      bottomRight = model->index(0, 2, QModelIndex());

      columnSelection.select(topLeft, bottomRight);

      selectionModel->select(columnSelection,

          QItemSelectionModel::Select | QItemSelectionModel::Columns);

      QItemSelection rowSelection;

      topLeft = model->index(0, 0, QModelIndex());

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

      rowSelection.select(topLeft, bottomRight);

      selectionModel->select(rowSelection,

          QItemSelectionModel::Select | QItemSelectionModel::Rows);

虽然只向选择模型提供了四个索引,但使用“列”和“行”选择标志意味着选择了两列和两行。下图显示了这两个选择的结果:

在示例模型上执行的命令都涉及到在模型中积累项目的选择。也可以清除选定内容,或者用新的选定内容替换当前选定内容。

 

若要用新选择替换当前选择,请将其他选择标志与current标志结合。使用此标志的命令指示选择模型将其当前的模型索引集合替换为select()调用中指定的索引集合。若要在开始添加新选项之前清除所有选项,请将其他选项标志与clear标志结合起来。这具有重置选择模型的模型索引集合的效果。

选择模型中的所有项目

为了选择模型中的所有项,有必要为模型的每一层创建一个选择,该选择涵盖了该层中的所有项。我们通过检索与具有给定父索引的左上和右下项对应的索引来实现这一点:

      QModelIndex topLeft = model->index(0, 0, parent);

      QModelIndex bottomRight = model->index(model->rowCount(parent)-1,

          model->columnCount(parent)-1, parent);

利用这些索引和模型构建了一个选择模型。然后在选择模型中选择相应的项目:

      QItemSelection selection(topLeft, bottomRight);

      selectionModel->select(selection, QItemSelectionModel::Select);

这需要对模型中的所有级别都进行执行。对于顶级项目,我们将以通常的方式定义父级索引:

      QModelIndex parent = QModelIndex();

对于层次模型,hasChildren()函数用于确定任何给定的项是否是另一个级别项的父项。

创建新模型

模型/视图组件之间的功能分离允许创建可以利用现有视图的模型。这种方法允许我们使用标准的图形用户界面组件(如QListView、QTableView和QTreeView)来显示来自各种来源的数据。

QAbstractItemModel 提供了一个足够灵活的接口,以支持以分层结构排列信息的数据源,允许以某种方式插入、删除、修改或排序的数据。它还提供了支持的拖放操作。

QAbstractListModel 和QAbstractTableModel 类提供了从接口到更简单的非层次数据结构的支持,并且更容易作为简单列表和表模型的起点使用。

在本节中,我们将创建一个简单的只读模型来探索模型/视图体系结构的基本原则。在本节的后面,我们将调整这个简单的模型,以便用户可以修改项。

有关更复杂模型的示例,请参阅Simple Tree Model示例。

更详细地描述了QAbstractItemModel 子类的要求,在 Model Subclassing Reference子类化参考文档中有更详细的描述。

设计模型

在为现有数据结构创建新模型时,必须考虑应该使用哪种类型的模型来提供到数据的接口。如果数据结构可以表示为项的列表或表,则可以将QAbstractListModel 或QAbstractTableModel 作为子类,因为这些类为许多函数提供了合适的默认实现。

但是,如果底层数据结构只能用层次树结构表示,则需要对QAbstractItemModel进行子类。在简单树模型示例中采用了此方法。

在本节中,我们将实现一个基于字符串列表的简单模型,因此QAbstractListModel 提供了要构建的理想基类。

无论底层数据结构采用何种形式,在专门的模型中补充标准的QAbstractItemModel API。这使得用数据填充模型变得更容易,但仍然允许其他通用模型/视图组件使用标准API与之交互。下面描述的模型仅为此目的提供了一个自定义构造函数。

只读示例模型

这里实现的模型是一个基于标准的QStringListModel 类的简单的、无层次的、只读的数据模型。它有一个QStringList作为它的内部数据源,并且只实现了创建一个功能模型所需的内容。为了使实现更容易,我们对QAbstractListModel 进行子类,因为它为列表模型定义了合理的默认行为,并且公开了一个QAbstractItemModel 类更简单的接口。

在实现模型时,重要的是要记住QAbstractItemModel 本身不存储任何数据,它只是提供了一个视图用于访问数据的接口。对于一个最小的只读模型,只需要实现一些函数,因为对于大多数接口都有默认的实现。类声明如下:

  class StringListModel : public QAbstractListModel

  {

      Q_OBJECT

  public:

      StringListModel(const QStringList &strings, QObject *parent = nullptr)

          : QAbstractListModel(parent), stringList(strings) {}

      int rowCount(const QModelIndex &parent = QModelIndex()) const override;

      QVariant data(const QModelIndex &index, int role) const override;

      QVariant headerData(int section, Qt::Orientation orientation,

                          int role = Qt::DisplayRole) const override;

  private:

      QStringList stringList;

  };

除了模型的构造函数外,我们只需要实现两个函数: rowCount()返回模型中的行数,data()返回与指定模型索引对应的数据项。

行为良好的模型还实现了头headerData(),使树和表视图能够在头中显示。

请注意,这是一个非分层模型,所以我们不必担心父子关系。如果我们的模型是分层的,那么我们还必须实现index()parent()函数

字符串列表内部存储在stringList 私有成员变量中。

Dimensions of the model

模型的尺寸

我们希望模型中的行数与字符串列表中的字符串数相同。我们为此,实现了rowCount()函数:

  int StringListModel::rowCount(const QModelIndex &parent) const

  {

      return stringList.count();

  }

由于模型是非分层的,我们可以安全地忽略与父项对应的模型索引。默认情况下,从QAbstractListModel 派生的模型只包含一列,因此我们不需要重新实现columnCount()函数。

模型头和数据

对于视图中的项,我们希望返回字符串列表中的字符串。data()函数负责返回与索引参数对应的数据项:

  QVariant StringListModel::data(const QModelIndex &index, int role) const

  {

      if (!index.isValid())

          return QVariant();

      if (index.row() >= stringList.size())

          return QVariant();

      if (role == Qt::DisplayRole)

          return stringList.at(index.row());

      else

          return QVariant();

  }

当提供的模型索引有效,行号在字符串列表中的项范围内,并且请求的角色是我们支持的角色时,我们只返回一个有效的QVariant。

一些视图,如QTreeView和QTableView,能够与项目数据一起显示标题。如果我们的模型显示在带有标题的视图中,我们希望标题显示行号和列号。我们可以通过子类化headerData()函数来提供关于头的信息:

  QVariant StringListModel::headerData(int section, Qt::Orientation orientation,

                                       int role) const

  {

      if (role != Qt::DisplayRole)

          return QVariant();

      if (orientation == Qt::Horizontal)

          return QStringLiteral("Column %1").arg(section);

      else

          return QStringLiteral("Row %1").arg(section);

  }

同样,只有当角色是我们支持的角色时,我们才返回一个有效的QVarirant。在决定要返回的确切数据时,也会考虑到报头的方向。

视图可以配置为隐藏它们。尽管如此,建议您实现headerData()函数,以提供有关模型提供的数据的相关信息。

一个项可以有多个角色,根据指定的角色给出不同的数据。模型中的项只有一个角色DisplayRole,因此无论指定的角色是什么,我们都返回项的数据。但是,我们可以在其他角色中重用为DisplayRole提供的数据,例如视图可以使用ToolTipRole来显示工具提示中有关项的信息。

可编辑的模型

只读模型显示了如何向用户提供简单的选择,但是,对于许多应用程序来说,一个可编辑的列表模型更有用。我们可以通过修改我们为只读实现的data()函数和实现两个额外的函数:flags() 来使setData()可编辑。以下函数声明将被添加到类定义中:

      Qt::ItemFlags flags(const QModelIndex &index) const override;

      bool setData(const QModelIndex &index, const QVariant &value,

                   int role = Qt::EditRole) override;

 

使模型可编辑

委托在创建编辑器之前检查项目是否可编辑。模型必须让委托知道其项是可编辑的。我们通过为模型中的每个项返回正确的标志来做到这一点;在这种情况下,我们启用所有项,并使它们都可选择和可编辑:

  Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const

  {

      if (!index.isValid())

          return Qt::ItemIsEnabled;

      return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;

  }

请注意,我们不需要知道委托是如何执行实际的编辑过程。我们只需要为委托提供一种在模型中设置数据的方法。这是通过setData()功能来实现的

  bool StringListModel::setData(const QModelIndex &index,

                                const QVariant &value, int role)

  {

      if (index.isValid() && role == Qt::EditRole) {

          stringList.replace(index.row(), value.toString());

          emit dataChanged(index, index, {role});

          return true;

      }

      return false;

  }

在此模型中,与模型索引对应的字符串列表中的项被提供的值所替换。但是,在修改字符串列表之前,必须确保索引是有效的,项的类型是正确的,并且角色受到支持。按照惯例,我们坚持该角色是EditRole,因为这是标准项委托使用的角色。然而,对于布尔值,你可以使用Qt::CheckStateRole并设置Qt::ItemIsUserCheckable标志;然后使用复选框编辑值。该模型中的底层数据对于所有角色都是相同的,因此这个细节使得将模型与标准组件集成起来更加容易。

当设置了数据后,模型必须让视图知道某些数据已经发生了变化。这是通过发射dataChanged() 信号来实现的。由于只有一条数据发生了变化,在信号中指定的项的范围被限制为一个模型索引。

还需要更改data()函数来添加Qt::EditRole测试:

  QVariant StringListModel::data(const QModelIndex &index, int role) const

  {

      if (!index.isValid())

          return QVariant();

      if (index.row() >= stringList.size())

          return QVariant();

      if (role == Qt::DisplayRole || role == Qt::EditRole)

          return stringList.at(index.row());

      else

          return QVariant();

  }

 

插入和删除行

可以更改模型中的行数和列数。在字符串列表模型中,只有更改行数才有意义,所以我们只重新实现用于插入和删除行的函数。这些内容已在类定义中进行了声明:

      bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;

      bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;

由于此模型中的行对应于列表中的字符串,因此innertRows()函数在指定位置之前将一些空字符串插入到字符串列表中。插入的字符串数相当于指定的行数。

父索引通常用于确定应该在模型中添加行的位置。在这种情况下,我们只有一个顶级的字符串列表,所以我们只需在该列表中插入空的字符串。

  bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)

  {

      beginInsertRows(QModelIndex(), position, position+rows-1);

      for (int row = 0; row < rows; ++row) {

          stringList.insert(position, "");

      }

      endInsertRows();

      return true;

  }

模型首先调用beginInsertRows()函数,通知其他组件行数即将更改。该函数指定要插入的第一个和最后一个新行的行号,以及它们的父项的模型索引。更改字符串列表后,它调用endInsertRows() 来完成操作,并通知其他组件模型的维度已经更改,返回true以表示成功。

从模型中删除行的函数也很容易编写。要从模型中删除的行将由给定的位置和行数来指定。我们忽略父索引以简化实现,只需从字符串列表中删除相应的项。

  bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)

  {

      beginRemoveRows(QModelIndex(), position, position+rows-1);

      for (int row = 0; row < rows; ++row) {

          stringList.removeAt(position);

      }

      endRemoveRows();

      return true;

  }

在删除任何底层数据之前,总是先调用beginRemoveRows()函数,并指定要删除的第一行和最后一行。这允许其他组件在数据不可用之前访问该数据。删除行后,模型发出 endRemoveRows()以完成操作,并让其他组件知道模型的尺寸已更改。

下一步

我们可以使用QListView类以垂直列表的形式显示该模型或任何其他模型提供的数据。对于字符串列表模型,此视图还提供了一个默认的编辑器,以便可以操作这些项。我们将检查视图类中的标准视图类所提供的可能性。

模型子类参考文档更详细地讨论了QAbstractItemModel 子类的需求,并提供了必须实现的虚拟函数的指南,以启用不同类型的模型中的各种特性。

项目视图方便类

基于项的小部件的名称反映了它们的用途: QListWidget提供了一个项的列表,QTreeWidget显示了一个多层次的树状结构,而QTableWidget提供了一个单元格项的表。每个类都继承了QAbstractItemView类的行为,该类实现了项目选择和标头管理的公共行为。

列表小部件

单个级别的项目列表通常使用QListWidget和一些QListWidgetItems来显示。一个列表小部件的构造方式与任何其他小部件相同:

      QListWidget *listWidget = new QListWidget(this);

在构建列表项时,可以直接添加到列表小部件中:

      new QListWidgetItem(tr("Sycamore"), listWidget);

      new QListWidgetItem(tr("Chestnut"), listWidget);

      new QListWidgetItem(tr("Mahogany"), listWidget);

它们也可以在没有父列表小部件的情况下构建,并在稍后的时候添加到列表中:

      QListWidgetItem *newItem = new QListWidgetItem;

      newItem->setText(itemText);

      listWidget->insertItem(row, newItem);

列表中的每个项目都可以显示文本标签和图标。可以更改用于呈现文本的颜色和字体,以提供项目的自定义外观。工具提示、状态提示和“这是什么?”帮助都很容易配置,以确保列表正确地集成到应用程序中。

      newItem->setToolTip(toolTipText);

      newItem->setStatusTip(toolTipText);

      newItem->setWhatsThis(whatsThisText);

默认情况下,列表中的项目将按其创建的顺序显示。项目列表可以根据Qt::SortOrder中给出的标准进行排序,以生成一个按正向或反向字母顺序排序的项目列表:

      listWidget->sortItems(Qt::AscendingOrder);

      listWidget->sortItems(Qt::DescendingOrder);

 

树的小部件

项的树或层次列表由QTreeWidget 和QTreeWidgetItem 类提供。树小部件中的每个项都可以有自己的子项,并且可以显示许多信息列。树状小部件的创建就像任何其他小部件一样:

      QTreeWidget *treeWidget = new QTreeWidget(this);

在将项目添加到树小部件之前,必须设置列数。例如,我们可以定义两列,并创建一个头来在每个列的顶部提供标签:

      treeWidget->setColumnCount(2);

      QStringList headers;

      headers << tr("Subject") << tr("Default");

      treeWidget->setHeaderLabels(headers);

为每个部分设置标签的最简单的方法是提供一个字符串列表。对于更复杂的头,您可以构造一个树项,按需要装饰它,并将其用作树小部件的头。

树小部件中的顶级项是用树小部件作为其父小部件来构建的。它们可以以任意顺序插入,也可以通过在构造每个项时指定前一项来确保它们以特定顺序列出:

      QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);

      cities->setText(0, tr("Cities"));

      QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);

      osloItem->setText(0, tr("Oslo"));

      osloItem->setText(1, tr("Yes"));

      QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);

树状结构小部件处理顶级项目的方式与树内更深处的其他项目略有不同。您可以通过调用树小部件的takeTopLevelItem()函数来从树的顶层删除项,但是通过调用它们的父项的takeChild()函数来删除来自较低层次的项。项目通过insertTopLevelItem()函数插入到树的顶层。在树的较低级别,使用父项的insertChild()函数。

在树中的顶层和下层之间移动项目很容易。我们只需要检查这些项目是否为顶级项目,并且这些信息将由每个项目的parent()函数提供。例如,我们可以删除树小部件中的当前项,而不管它的位置如何:

      QTreeWidgetItem *parent = currentItem->parent();

      int index;

      if (parent) {

          index = parent->indexOfChild(treeWidget->currentItem());

          delete parent->takeChild(index);

      } else {

          index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem());

          delete treeWidget->takeTopLevelItem(index);

      }

在树小部件的其他位置插入项目将遵循相同的模式:

      QTreeWidgetItem *parent = currentItem->parent();

      QTreeWidgetItem *newItem;

      if (parent)

          newItem = new QTreeWidgetItem(parent, treeWidget->currentItem());

      else

          newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());

 

表小部件

与电子表格应用程序中类似的项目表是用QTableWidget和QTableWidgetItem构建的。这些提供了一个滚动表小部件,其中有标题和要使用的项目。

可以使用一组行和列来创建表,也可以根据需要将这些行和列添加到无大小的表中。

      QTableWidget *tableWidget;

      tableWidget = new QTableWidget(12, 3, this);

项目在添加到表的要求位置之前,将在表外构造:

      QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(

          pow(row, column+1)));

      tableWidget->setItem(row, column, newItem);

水平和垂直的标题,添加到表格中:

      QTableWidgetItem *valuesHeaderItem = new QTableWidgetItem(tr("Values"));

      tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);

请注意,表中的行和列从零开头.

公共特性

每个方便类都有许多基于项的特性,这些特性可以通过每个类中的相同接口获得。我们将在下面的部分中介绍这些,并为不同小部件的一些示例。查看每个小部件的模型/视图类列表,了解关于使用所使用的每个函数的更多细节。

隐藏的项目

有时能够隐藏项目视图小部件中隐藏项目而不是删除它们是有用的。上面所有小部件的项都可以隐藏,稍后会再次显示。您可以通过调用“isItemHidden()”函数来确定是否隐藏了一个项目,并且可以使用设置“setItemHidden()”来隐藏项目。

由于此操作是基于项的,所以所有三个方便类都可以使用相同的函数。

选择

项目的选择方式将由小部件的选择模式来控制(QAbstractItemView::SelectionMode).此属性控制用户是否可以选择一个或多个项,以及在多项选择中,选择是否必须是一个连续的项范围。选择模式对上述所有小部件的工作方式都相同。

Single item selections: 单个项选择:当用户需要从一个小部件中选择单个项时,默认的SingleSelection 是最合适的。在此模式下,当前项目和选定项目均相同

Multi-item selections: 多项选择:在这种模式下,用户可以切换小部件中任何项的选择状态,而不改变现有的选择,就像可以独立切换非排他性复选框的方式一样。

Extended selections:扩展选择:通常需要选择许多相邻项目的小部件,如在电子表格中发现的小部件,需要使用ExtendedSelection 模式。在这种模式下,可以通过鼠标和键盘来选择小部件中连续的项目范围。如果使用修改器键,也可以创建复杂的选择,其中涉及许多与小部件中的其他选定项不相邻的项。 如果用户不使用修改键选择项目,则清除现有选择。

使用selectedItems()函数读取小部件中选定的项,并提供可遍历的相关项的列表。例如,我们可以通过以下代码在所选项的列表中找到所有数值的和:

      const QList<QTableWidgetItem *> selected = tableWidget->selectedItems();

      int number = 0;

      double total = 0;

      for (QTableWidgetItem *item : selected) {

          bool ok;

          double value = item->text().toDouble(&ok);

          if (ok && !item->text().isEmpty()) {

              total += value;

              number++;

          }

      }

请注意,对于单个选择模式,当前项目将在选择模式中。在多选择和扩展选择模式中,当前项目可能不在选择范围内,这取决于用户形成选择的方式。

搜索

能够在项视图小部件中找到项通常是很有用的,无论是作为开发人员还是作为向用户呈现的服务。所有三个项目视图方便类都提供了一个通用的findItems()函数,使其尽可能一致和简单。

根据从Qt::MatchFlags中选择的值所指定的条件,通过它们所包含的文本来搜索项目。我们可以通过findItems()函数获得匹配项的列表:

      const QList<QTreeWidgetItem *> found = treeWidget->findItems(

          itemText, Qt::MatchWildcard);

      for (QTreeWidgetItem *item : found) {

          item->setSelected(true);

          // Show the item->text(0) for each item.

      }

如果上述代码包含搜索字符串中给出的文本,则会选择树小部件中的项。这个模式也可以在列表和表小部件中使用。

在项目视图中使用拖放方式

模型/视图框架完全支持Qt的拖放基础架构。列表、表和树中的项可以在视图中拖动,数据可以作为mime编码的数据导入和导出。

标准视图自动支持内部拖放,在其中移动项目以更改其显示的顺序。默认情况下,对这些视图不启用拖放,因为它们被配置为最简单、最常见的用途。要允许四处拖动项目,需要启用视图的某些属性,而且项目本身也必须允许发生拖动。

仅允许从视图导出项且不允许包含数据的模型的要求比完全启用的拖放模型的要求要少。

有关在新模型中启用拖放支持的更多信息,还请参见模型子类化参考资料。

使用方便视图

QListWidgiget、QTableWidget和QTreeWidget中使用的每种项类型都默认配置为使用不同的标志集。例如,每个QListWidgetItem 或QTreeWidgetItem 初始启用,可选的,并可以用作拖放操作的源;QTableWidgetItem 也可以编辑并用作拖放操作的目标。

尽管所有标准项都设置了一个或两个要进行拖放的标志,但您通常需要在视图本身中设置各种属性,以利用对拖放的内置支持:

  • 要启用项目拖动,请将视图的“dragEnabled ”属性设置为true。
  • 要允许用户在视图中删除内部或外部项目,请将视图的viewport()'s acceptDrops属性设置为true。
  • 要向用户显示当前被拖放的项目将放在哪里的位置,请设置视图的drop指示器属性。这为用户提供了不断更新有关视图中项目放置的信息。

例如,我们可以使用以下代码行在列表小部件中启用拖放:

  QListWidget *listWidget = new QListWidget(this);

  listWidget->setSelectionMode(QAbstractItemView::SingleSelection);

  listWidget->setDragEnabled(true);

  listWidget->viewport()->setAcceptDrops(true);

  listWidget->setDropIndicatorShown(true);

结果是一个列表小部件,它允许在视图中复制项目,甚至允许用户在包含相同类型数据的视图之间拖动项目。在这两种情况下,这些项目都会被复制,而不是被移动。

要使用户能够在视图中四处移动项目,我们必须设置列表小部件的dragDropMode:

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

使用模型/视图类

设置拖放视图遵循与方便视图相同的模式。例如,QListWiew可以以与QListWidget相同的方式设置:

  QListView *listView = new QListView(this);

  listView->setSelectionMode(QAbstractItemView::ExtendedSelection);

  listView->setDragEnabled(true);

  listView->setAcceptDrops(true);

  listView->setDropIndicatorShown(true);

由于对视图所显示的数据的访问是由模型控制,因此所使用的模型还必须提供对拖放操作的支持。模型所支持的操作可以通过重新实现 QAbstractItemModel::supportedDropActions()函数来指定。例如,将使用以下代码启用复制和移动操作:

  Qt::DropActions DragDropListModel::supportedDropActions() const

  {

      return Qt::CopyAction | Qt::MoveAction;

  }

尽管可以给出Qt::DropActions 的任何值组合,但需要编写模型来支持它们。例如,要允许Qt::MoveAction与列表模型正确使用,模型必须提供QAbstractItemModel::removeRows()的实现,可以直接或通过从其基类继承实现。

启用项目的拖放功能

模型通过重新实现QAbstractItemModel::flags() 函数来指示哪些项可以被拖动,哪些项可以接受删除,以提供合适的标志。

例如,一个模型提供了一个基于QAbstractListModel 的简单列表,它可以通过确保返回的标志包含Qt::ItemIsDropEnabledQt::ItemIsDropEnabled 来为每个项目启用拖放:

  Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const

  {

      Qt::ItemFlags defaultFlags = QStringListModel::flags(index);

      if (index.isValid())

          return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;

      else

          return Qt::ItemIsDropEnabled | defaultFlags;

  }

请注意,可以将项目拖放到模型的顶层,但只对有效的项目启用拖动。

在上面的代码中,由于该模型来自于QStringListModel,因此我们通过调用其对flags() 函数的实现来获得一组默认的标志。

编码导出的数据

当在拖放操作中从模型中导出数据项时,它们将被编码为与一个或多个MIME类型相对应的适当格式。模型声明它们可以通过重新实现QAbstractItemModel::mimeTypes() 函数,返回一个标准MIME类型的列表。

例如,仅提供纯文本的模型将提供以下实现:

  QStringList DragDropListModel::mimeTypes() const

  {

      QStringList types;

      types << "application/vnd.text.list";

      return types;

  }

模型还必须提供代码,以按照所公布的格式对数据进行编码。这是通过重新实现QAbstractItemModel::mimeData() 函数提供一个QMimeData 目标,就像在任何其他的拖放操作中一样。

下面的代码显示了与给定索引列表对应的每项数据如何被编码为纯文本并存储在QMimeData对象中。

  QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const

  {

      QMimeData *mimeData = new QMimeData;

      QByteArray encodedData;

      QDataStream stream(&encodedData, QIODevice::WriteOnly);

      for (const QModelIndex &index : indexes) {

          if (index.isValid()) {

              QString text = data(index, Qt::DisplayRole).toString();

              stream << text;

          }

      }

      mimeData->setData("application/vnd.text.list", encodedData);

      return mimeData;

  }

由于向函数提供了一个模型索引列表,因此这种方法足够通用,可以在分层模型和非分层模型中使用。

请注意,自定义数据类型必须声明为元对象,并且必须为它们实现流操作符。有关详细信息,请参阅QMetaObject类描述。

将删除的数据插入到模型中

任何给定的模型处理被删除数据的方式都取决于它的类型(列表、表或树)以及它的内容可能呈现给用户的方式。通常,用于容纳丢失数据的方法应该是最适合模型底层数据存储的方法。

不同类型的模型倾向于以不同的方式处理丢弃的数据。列表和表模型只提供存储数据项的平面结构。因此,当视图中现有项上的数据被删除时,它们可能插入新行(和列),或者它们可能使用提供的一些数据在模型中覆盖项的内容。树模型通常能够将包含新数据的子项目添加到它们的底层数据存储中,因此就用户而言,它的行为更可预测。

被丢弃的数据由模型的QAbstractItemModel::dropMimeData()的重新实现来处理。例如,一个处理简单字符串列表的模型可以提供一个实现,该实现分别处理被放到现有项上的数据和被放到模型顶层的数据(即,放到无效项上的数据)。

通过重新实现QAbstractItemModel::canDropMimeData(),模型可以禁止删除某些项,或者依赖于删除的数据。

模型首先必须确保操作应该被执行,提供的数据是可以使用的格式,并且它在模型中的目的地是有效的:

  bool DragDropListModel::canDropMimeData(const QMimeData *data,

      Qt::DropAction action, int row, int column, const QModelIndex &parent)

  {

      Q_UNUSED(action);

      Q_UNUSED(row);

      Q_UNUSED(parent);

      if (!data->hasFormat("application/vnd.text.list"))

          return false;

      if (column > 0)

          return false;

      return true;

  }

  bool DragDropListModel::dropMimeData(const QMimeData *data,

      Qt::DropAction action, int row, int column, const QModelIndex &parent)

  {

      if (!canDropMimeData(data, action, row, column, parent))

          return false;

      if (action == Qt::IgnoreAction)

          return true;

如果提供的数据不是纯文本,或者为删除指定的列号无效,那么简单的单列字符串列表模型可以指示失败。

要插入到模型中的数据会根据是否将其拖放到现有项上而被区别对待。在这个简单的示例中,我们希望允许在现有项之间、列表中的第一个项之前和最后一个项之后进行删除。

当拖放发生时,与父项对应的模型索引要么是有效的,表明拖放发生在项上,要么是无效的,表明拖放发生在与模型顶层对应的视图的某个位置

      int beginRow;

      if (row != -1)

          beginRow = row;

我们首先检查提供的行号,看看是否可以使用它将项插入到模型中,而不管父索引是否有效。

      else if (parent.isValid())

          beginRow = parent.row();

如果父模型索引有效,则删除发生在项上。在这个简单的列表模型中,我们找到项目的行号,并使用该值将删除的项目插入到模型的顶层。

      else

          beginRow = rowCount(QModelIndex());

当视图的其他地方出现了拖放,并且行号不可用时,我们将项目附加到模型的顶层。

在层次模型中,当某项上出现删除时,最好将新项作为该项的子项插入到模型中。在这里显示的简单示例中,模型只有一个级别,因此这种方法不合适。

解码导入的数据

dropMimeData() 的每个实现还必须解码数据并将其插入到模型的底层数据结构中。

对于一个简单的字符串列表模型,被编码的项目可以被解码并流化成一个QStringList:

      QByteArray encodedData = data->data("application/vnd.text.list");

      QDataStream stream(&encodedData, QIODevice::ReadOnly);

      QStringList newItems;

      int rows = 0;

      while (!stream.atEnd()) {

          QString text;

          stream >> text;

          newItems << text;

          ++rows;

      }

然后,可以将这些字符串插入到底层数据存储区中。为了保持一致性,这可以通过模型自己的界面来完成

      insertRows(beginRow, rows, QModelIndex());

      for (const QString &text : qAsConst(newItems)) {

          QModelIndex idx = index(beginRow, 0, QModelIndex());

          setData(idx, text);

          beginRow++;

      }

      return true;

  }

注意,模型通常需要提供QAbstractItemModel::insertRows()和QAbstractItemModel::setData()函数的实现。

代理模型

在模型/视图框架中,由单个模型提供的数据项可以由任意数量的视图共享,并且每个视图都可能以完全不同的方式表示相同的信息。自定义视图和委托是提供相同数据的完全不同表示的有效方法。但是,应用程序通常需要为相同数据的处理版本提供传统视图,例如为项列表提供不同排序的视图。

虽然将排序和过滤操作作为视图的内部函数来执行似乎是合适的,但这种方法不允许多个视图共享这种潜在的代价高昂的操作的结果。另一种方法包括在模型本身内部进行排序,从而导致类似的问题,即每个视图都必须显示根据最近的处理操作进行组织的数据项。

为了解决这个问题,模型/视图框架使用代理模型来管理单个模型和视图之间提供的信息。代理模型是从视图的角度来看,行为类似于普通模型的组件,并代表该视图从源模型中访问数据。模型/视图框架所使用的信号和插槽确保每个视图都被适当地更新,无论在其自身和源模型之间放置了多少个代理模型。

使用代理模型

可以在现有模型和任意数量的视图之间插入代理模型。Qt提供了一个标准的代理模型QSortFilterProxyModel,它通常是直接实例化和使用的,但也可以通过子类化来提供自定义的筛选和排序行为。QSortFilterProxyModel类可以以下方式使用:

      QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);

      filterModel->setSourceModel(stringListModel);

      QListView *filteredView = new QListView;

      filteredView->setModel(filterModel);

因为代理模型继承自QAbstractItemModel,所以它们可以连接到任何类型的视图,并且可以在视图之间共享。它们还可以用于处理从管道安排中的其他代理模型获得的信息。

QSortFilterProxyModel类被设计成可以被实例化并直接在应用程序中使用。通过子类化这些类并实现所需的比较操作,可以创建更专门的代理模型。

自定义代理模型

通常,代理模型中使用的处理类型涉及到将每个数据项从其在源模型中的原始位置映射到代理模型中的不同位置。在某些模型中,有些项目在代理模型中可能没有相应的位置;这些模型正在过滤代理模型。视图使用代理模型提供的模型索引访问项目,而这些索引不包含关于源模型或该模型中原始项目的位置的信息。

QSortFilterProxyModel允许在将源模型的数据提供给视图之前对其进行过滤,还允许将源模型的内容作为预先排序的数据提供给视图。

自定义过滤模型

QSortFilterProxyModel类提供了一个相当通用的过滤模型,可以在各种常见情况下使用。对于高级用户,可以子类化QSortFilterProxyModel,提供一种支持实现自定义过滤器的机制。

QSortFilterProxyModel的子类可以重新实现两个虚函数,当请求或使用代理模型的模型索引时调用它们:

  • filterAcceptsColumn()用于从源模型的一部分中筛选特定的列。
  • filterAcceptsRow()用于从部分源模型中筛选特定的行。

QSortFilterProxyModel中上述函数的默认实现返回true,以确保所有项都传递给视图;这些函数的重新实现应该返回false,以过滤掉单独的行和列。

自定义排序模型

QSortFilterProxyModel实例使用std::stable_sort()函数建立源模型中项目和代理模型中项目之间的映射,允许在不修改源模型结构的情况下向视图公开排序的项目层次结构。要提供自定义排序行为,请重新实现lessThan()函数来执行自定义比较。

模型子类别引用

模型子类需要提供在QAbstractItemModel基类中定义的许多虚函数的实现。需要实现的这些函数的数量取决于模型的类型——它是提供带有简单列表、表还是复杂的项层次结构的视图。继承自QAbstractListModel和QAbstractTableModel的模型可以利用这些类提供的函数的默认实现。以树形结构公开数据项的模型必须为QAbstractItemModel中的许多虚函数提供实现。

需要在一个模型子类中实现的函数可以分为三组:

  • Item data handling:项目数据处理:所有模型都需要实现函数,使视图和委托能够查询模型的维度、检查项目和检索数据。
  • Navigation and index creation: 导航和索引创建:分层模型需要提供函数,视图可以调用这些函数来导航它们公开的树状结构,并获得项目的模型索引。
  • Drag and drop support and MIME type handling: 拖放支持和MIME类型处理:模型继承了控制内部和外部拖放操作执行方式的函数。这些函数允许以其他组件和应用程序可以理解的MIME类型来描述数据项。

项目数据处理

模型可以提供对其提供的数据的不同级别的访问:它们可以是简单的只读组件,一些模型可能支持调整大小操作,而另一些模型可能允许编辑项。 

只读访问

要提供对模型提供的数据的只读访问,必须在模型的子类中实现以下功能:

flags()

由其他组件使用,以获取关于模型所提供的每个项的信息。在许多模型中,标志的组合应该包括Qt::ItemIsEnabled和Qt::ItemIsSelectable。

data()

用于向视图和委托提供项数据。通常,模型只需要为Qt::DisplayRole和任何特定于应用程序的用户角色提供数据,但为Qt::ToolTipRole、Qt::AccessibleTextRole和Qt::AccessibleDescriptionRole提供数据也是一个很好的实践。有关与每个角色相关联的类型的信息,请参阅Qt::ItemDataRole enum文档。

headerData()

提供具有在其标头中显示的信息的视图。信息仅由能够显示标头信息的视图检索。

rowCount()

提供模型公开的数据行数。

这四个函数必须在所有类型的模型中实现,包括列表模型(QAbstractListModel子类)和表模型(qabstractttablemodel子类)。

此外,以下函数必须在QAbstractTableModel和QAbstractItemModel的直接子类中实现:

columnCount()

提供模型公开的数据的列数。列表模型不提供此功能,因为它已经在QAbstractListModel中实现了。

可编辑的项目

可编辑的模型允许修改数据项,也可以提供允许插入和删除行和列的功能。要启用编辑,必须正确实现以下功能:

flags()

必须为每一项返回适当的标志组合。特别是,该函数返回的值必须包括Qt::ItemIsEditable,以及应用于只读模型中的项的值。

setData()

用于修改与指定模型索引相关联的数据项。为了能够接受用户界面元素提供的用户输入,这个函数必须处理与Qt::EditRole相关的数据。实现也可以接受与Qt::ItemDataRole指定的许多不同类型角色相关联的数据。在更改数据项之后,模型必须发出dataChanged()信号,将更改通知其他组件。

setHeaderData()

用于修改水平和垂直报头信息。在更改数据项之后,模型必须发出headerdatachhanged()信号,将更改通知其他组件。

可调整大小的模型

所有类型的模型都可以支持插入和删除行。表模型和层次结构模型也可以支持列的插入和删除。在模型尺寸发生之前和之后通知其他组件是很重要的。因此,可以实现以下函数来允许调整模型的大小,但实现必须确保调用适当的函数来通知附加的视图和委托:

insertRows()

用于向所有类型的模型添加数据的新行和项。实现必须在向任何底层数据结构插入新行之前调用beginInsertRows(),然后立即调用endInsertRows()。

removeRows()

用于从所有类型的模型中删除它们包含的行和数据项。实现必须在从任何底层数据结构中删除行之前调用beginRemoveRows(),然后立即调用endRemoveRows()。

insertColumns()

用于向表模型和层次模型添加新的列和数据项。实现必须在向任何底层数据结构插入新列之前调用beginInsertColumns(),然后立即调用endInsertColumns()。

removeColumns()

用于从表模型和层次模型中删除列及其包含的数据项。实现必须在从任何底层数据结构中删除列之前调用beginRemoveColumns(),并在之后立即调用endRemoveColumns()。

通常,如果操作成功,这些函数应该返回true。但是,也可能有些情况下,操作仅部分成功;例如,如果可以插入的行数少于指定的行数。在这种情况下,模型应该返回false,以表示无法使任何附加的组件能够处理该情况。

在调整大小API的实现中调用的函数发出的信号为附加组件提供了在任何数据变得不可用之前采取行动的机会。使用begin和end函数封装插入和删除操作也使模型能够正确地管理持久模型索引。

通常,开始和结束函数能够通知其他组件关于对模型的底层结构的更改。对于对模型结构的更复杂的变化,可能涉及内部重组、数据排序或任何其他结构变化,有必要执行以下顺序:

  • 发射layoutabouttochanged()信号
  • 更新代表模型结构的内部数据。
  • 使用changePersistentIndexList()更新持久索引
  • 发射layoutChanged()信号。

这个序列可以用于任何结构更新,代替更高级和方便的保护方法。例如,如果一个200万行的模型需要删除所有奇数行,那就是100万个每个有一个元素的可贴现范围。可以使用beginRemoveRows和endRemoveRows 100万次,但这显然是低效的。相反,这可以被标记为一次更新所有必要持久索引的单个布局更改。

模型数据的延迟填充

模型数据的延迟填充有效地允许延迟对有关模型信息的请求,直到视图实际需要它为止。

有些模型需要从远程源获取数据,或者必须执行耗时的操作,以获取有关数据组织方式的信息。由于视图通常要求尽可能多的信息,以便准确地显示模型数据,因此限制返回给它们的信息量,以减少对数据不必要的后续请求是有用的。

在分层模型中,查找给定项的子数是一项开销很大的操作,因此确保只在必要时调用模型的rowCount()实现是很有用的。在这种情况下,可以重新实现hasChildren()函数,为视图提供一种廉价的方法来检查子元素是否存在,并且在QTreeView的情况下,为它们的父元素绘制适当的装饰。

不管hasChildren()的重新实现返回true还是false,视图可能没有必要调用rowCount()来找出当前有多少个子节点。例如,如果父项没有展开显示,QTreeView不需要知道有多少个子项。

如果已知许多项都有子项,那么重新实现hasChildren()以无条件返回true有时是一种有用的方法。这确保了以后可以在尽可能快地对模型数据进行初始填充的同时,对每个项目进行检查。唯一的缺点是,在用户尝试查看不存在的子项之前,没有子项的项可能在某些视图中显示不正确。

导航和模型索引的创建

层次结构模型需要提供视图可以调用的函数来导航它们公开的树状结构,并获取项的模型索引。

父母和孩子

由于向视图公开的结构是由底层数据结构决定的,因此由每个模型子类通过提供以下函数的实现来创建自己的模型索引:

index()

给定父项的模型索引,此函数允许视图和委托访问该项的子项。如果无法找到与指定的行、列和父模型索引对应的有效子项,则该函数必须返回QModelIndex(),这是一个无效的模型索引。

parent()

提供与任何给定子项的父项对应的模型索引。如果指定的模型索引对应于模型中的顶级项,或者如果模型中没有有效的父项,则该函数必须返回一个无效的模型索引,该索引是由空的QModelIndex()构造函数创建的。

上面的两个函数都使用createIndex()工厂函数来生成其他组件使用的索引。通常,模型会向这个函数提供一些惟一的标识符,以确保模型索引可以在以后与其对应的项重新关联。

拖放支持和MIME类型处理

模型/视图类支持拖放操作,提供了对许多应用程序都足够的默认行为。但是,也可以自定义在拖放操作期间项目的编码方式,默认情况下是复制还是移动,以及如何将它们插入到现有模型中。

此外,便利视图类实现的专门行为应该与现有开发人员的预期密切相关。便利视图部分提供了这种行为的概述。

MIME数据

默认情况下,内置模型和视图使用内部MIME类型(application/x-qabstractitemmodeldatalist)来传递关于模型索引的信息。这指定项列表的数据,包含每个项的行号和列号,以及关于每个项支持的角色的信息。

使用这种MIME类型编码的数据可以通过调用QAbstractItemModel::mimeData()来获得,它带有一个包含要序列化的项的QModelIndexList。

当在自定义模型中实现拖放支持时,可以通过重新实现以下功能来导出特定格式的数据项:

mimeData()

这个函数可以重新实现,以不同于默认的application/x-qabstractitemmodeldatalist内部MIME类型的格式返回数据。

子类可以从基类中获得默认的QMimeData对象,并以其他格式向其添加数据

对于许多模型,用MIME类型(text/plain and image/png)表示的通用格式提供项的内容是有用的。注意,可以通过QMimeData::setImageData()、QMimeData::setColorData()和QMimeData::setHtml()函数轻松地将图像、颜色和HTML文档添加到QMimeData对象中。

 接收拖拽的数据

当对视图执行拖放操作时,将查询底层模型以确定它支持的操作类型和它可以接受的MIME类型。这个信息是由QAbstractItemModel::supportedDropActions()和QAbstractItemModel::mimeTypes()函数提供的。不覆盖QAbstractItemModel提供的实现的模型支持复制操作和项的默认内部MIME类型。

当序列化的项目数据被放到视图上时,数据会使用当前模型的QAbstractItemModel::dropMimeData()的实现被插入到当前模型中。这个函数的默认实现永远不会覆盖模型中的任何数据;相反,它尝试将数据项作为项的兄弟项或项的子项插入。

为了利用内置MIME类型的QAbstractItemModel的默认实现,新模型必须提供以下函数的重新实现:

insertRows()

这些函数使模型能够自动插入

使用QAbstractItemModel::dropMimeData()现有的实现提供的新数据

insertColumns()

setData()

允许用项填充新的行和列。

setItemData()

此函数为填充新项提供了更有效的支持。

要接受其他形式的数据,必须重新实现这些功能:

supportedDropActions()

用于返回拖放操作的组合,以指示模型所接受的拖放操作的类型。

mimeTypes()

用于返回可以由模型解码和处理的MIME类型的列表。通常,支持输入到模型中的MIME类型与它在编码数据以供外部组件使用时可以使用的MIME类型相同。

dropMimeData()

对通过拖放操作传输的数据进行实际解码,确定其在模型中设置的位置,并在必要时插入新的行和列。如何在子类中实现这个函数取决于每个模型所公开的数据的需求。

如果dropMimeData()函数的实现通过插入或删除行或列改变了模型的维度,或者修改了数据项,则必须小心确保发出所有相关的信号。简单地调用子类中其他函数的重实现(例如setData()、insertRows()和insertColumns()),以确保模型行为一致是很有用的。

为了确保拖动操作正常工作,必须重新实现以下从模型中删除数据的函数:

有关项目视图拖放的更多信息,请参阅Using drag and drop with item views.

方便视图

方便视图(QListWidget、QTableWidget和QTreeWidget)覆盖了默认的拖放功能,以提供不那么灵活但更自然的行为,适用于许多应用程序。例如,由于将数据放到QTableWidget的单元格中更常见,因此用正在传输的数据替换现有内容,底层模型将设置目标项的数据,而不是在模型中插入新的行和列。有关在方便视图中拖放的详细信息,您可以查看使用项目视图的拖放。

针对大量数据进行性能优化

canFetchMore()函数检查父对象是否有更多可用数据,并相应地返回true或false。fetchMore()函数的作用是:根据指定的父对象获取数据。这两个函数可以组合在一起,例如,在涉及增量数据的数据库查询中填充QAbstractItemModel。我们重新实现canFetchMore(),以指示是否有更多的数据要获取,并根据需要实现fetchMore()来填充模型。

另一个例子是动态填充树模型,当树模型中的分支时,我们重新实现fetchMore()。

如果fetchMore()的重新实现将行添加到模型中,则需要调用beginInsertRows()和endInsertRows()。同样,canFetchMore()和fetchMore()都必须重新实现,因为它们的默认实现返回false且不做任何事情。

模型/视图类

这些类使用模型/视图设计模式,在该模式中,底层数据(在模型中)与用户呈现和操作数据的方式(在视图中)分开保存。

QAbstractItemDelegate

用于显示和编辑模型中的数据项

QAbstractItemModel

项目模型类的抽象接口

QAbstractItemView

项目视图类的基本功能

QAbstractListModel

可以通过子类化来创建一维列表模型的抽象模型

QAbstractProxyModel

可以进行排序、过滤或其他数据处理任务的代理项模型的基类

QAbstractTableModel

可以进行子类化以创建表模型的抽象模型

QColumnView

列视图的模型/视图实现

QConcatenateTablesProxyModel

代理多个源模型,并连接它们的行

QDataWidgetMapper

数据模型的某个部分与小部件之间的映射

QFileSystemModel

针对本地文件系统的数据模型

QHeaderView

项目视图的标题行或标题列

QIdentityProxyModel

代理其未经修改的源模型

QItemDelegate

显示和编辑来自模型中的数据项的工具

QItemEditorCreator

允许不子类化创建项目编辑器创建者库

QItemEditorCreatorBase

在实现新的项编辑器创建者时,必须进行子类化的抽象基类

QItemEditorFactory

用于在视图和委托中编辑项目数据的小部件

QItemSelection

管理有关模型中所选项的信息

QItemSelectionModel

保持跟踪视图的选定项目

QItemSelectionRange

管理有关模型中选定项目的范围的信息

QListView

在一个模型上的列表或图标视图

QListWidget

基于项目的列表小部件

QListWidgetItem

与QListWidget项视图类一起使用的项

QModelIndex

用于在数据模型中的数据

QPersistentModelIndex

用于在数据模型中的数据

QSortFilterProxyModel

支持对在另一个模型和一个视图之间传递的数据进行排序和过滤

QStandardItem

项,用于与QStandardItemModel类一起使用

QStandardItemEditorCreator

注册小部件而不必子类化QItemEditorCreatorBase的可能性

QStandardItemModel

用于存储自定义数据的通用模型

QStringListModel

为视图提供字符串的模型

QStyledItemDelegate

显示和编辑来自模型中的数据项的工具

QTableView

表视图的默认模型/视图实现

QTableWidget

具有默认模型的基于项的表视图

QTableWidgetItem

用于QTableWidget类的项

QTableWidgetSelectionRange

不使用模型索引和选择模型而与模型中的选择进行交互的方法

QTreeView

树形视图的默认模型/视图实现

QTreeWidget

使用预定义树模型的树视图

QTreeWidgetItem

与QTreeWidget方便类一起使用的项目

QTreeWidgetItemIterator

遍历QTreeWidget实例中的项的方法

相关示例

See also Item Views Puzzle Example.

©2020Qt有限公司。此处所包含的文件贡献为其各自所有者的版权。本文中提供的文档是根据自由软件基金会发布的GNU自由文档许可版本1.3的条款进行授权的。Qt和各自的徽标是Qt公司有限公司在芬兰和/或世界其他国家的商标。所有其他商标均为其各自所有者的财产。

翻译的pdf链接为:CSDN

猜你喜欢

转载自blog.csdn.net/klp1358484518/article/details/127318211