QT Model/View 编程:MVC模型视图编程(一)

目录

QT Model/View 编程(模型/视图编程)简介

模型/视图体系结构

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

Models模型:

Views视图:

Delegates委托:

排序

便利类

使用模型和视图

Qt 中包含的两个模型

在现有模型中使用视图

模型类

基本概念

模型索引

行和列

项目的父项

Item roles项角色;数据元素显示方式

摘要,概述:

使用模型索引:

延伸阅读

视图类:

概念:

使用现有视图

使用模型

使用模型的多个视图

处理项目的选择

在视图之间共享选择

委托类

概念:

使用现有委托类

委托实例:

提供编辑

向模型提交数据

更新编辑器的几何体

编辑提示

处理项目视图中的选择

概念:

当前项目和选定项目

使用选择模型

选择项目

读取选择状态

 更新选择

选择模型中的所有项目

创建新模型

设计模型

只读示例模型

模型的尺寸

模型头和数据

可编辑模型

使模型可编辑

插入和删除行

下一步行动

项目视图便利类

列表控件:List widgets

树控件:Tree widgets

表格控件:

共同特点

隐藏项目

选择:

搜索

在项目视图中使用拖放

使用方便视图

使用模型/视图类

启用项目的拖放

输出数据的编码

将删除的数据插入模型

解码导入的数据

代理模型

使用代理模型

自定义代理模型

自定义筛选模型

自定义排序模型

模型子类化参考

需要在模型子类中实现的功能可分为三组:

项目数据处理

只读访问

可编辑项

可调整大小的模型

模型数据的惰性填充

导航和模型索引创建

父与子

拖放支持和MIME类型处理

MIME 数据

接受其他的数据

方便查看

针对大量数据的性能优化

The Model/View Classes:模型/视图类:

相关实例

另请参阅 Item Views Puzzle Example.


QT Model/View 编程(模型/视图编程)简介

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

模型/视图体系结构

        模型视图控制器(MVC)是一种源于Smalltalk的设计模式,通常用于构建用户界面。在设计模式中,Gamma等人写道: 

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

备注:通俗的讲,M就是数据的源,比如数据库。M根据数据库的数据结构定制成数据模型。V就是怎样显示数据,比如HTML所展现的视图,相当于V怎样把数据模型展示给用户。C代表的是控制层,介于M和V中间,来支持用户的交互。

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

QT中把控制层C叫做委托。这样更容易理解。但QT中的委托C描述更贴近现实中的开发。

QT Model/View 编程:MVC模型视图编程(一)_第1张图片

模型/视图架构

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

 

视图从模型中获取模型索引;这些是对数据项的引用。通过向模型提供模型索引,视图可以从数据源检索数据项。

        通常,模型/视图类可以分为上述三组:模型、视图和委托。这些组件中的每一个都由抽象类定义,这些抽象类提供公共接口,在某些情况下还提供功能的默认实现。抽象类意味着子类化,以便提供其他组件所期望的全套功能;这也允许编写专门的组件。

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

  1. 来自模型的信号通知视图有关数据源持有的数据的更改。
  2. 来自视图的信号提供有关用户与正在显示的项目的交互的信息。
  3. 来自委托的信号在编辑期间用于告诉模型和视图有关编辑器的状态。

Models模型:

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

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

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

        在创建新模型一节中讨论了模型子类化的过程。

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

  • QStringListModel用于存储QString项的简单列表。
  • QStandardItemModel管理更复杂的项目树结构,每个项目可以包含任意数据。
  • QFileSystemModel提供有关本地文件系统中文件和目录的信息。
  • QSqlQueryModel、QSqlTableModel和QSqlRelationalTableModel用于使用模型/视图约定访问数据库。

        如果这些标准模型不满足您的需求,您可以子类QAbstractItemModel、QAbstract ListModel或QAbstratchTableModel来创建您自己的自定义模型。

Views视图:

        为不同类型的视图提供了完整的实现:QListView显示项目列表,QTableView在表中显示模型中的数据,QTreeView在分层列表中显示模型数据项。这些类中的每一个都基于QAbstractItemView抽象基类。尽管这些类已准备好使用实现,但它们也可以被子类化以提供定制视图。

        在“View Classes 视图类”一节中查看可用视图。

Delegates委托:

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

委托在委托类Delegate Classes一节中进行了描述。

排序

        在模型/视图架构中有两种处理排序的方法;选择哪种方法取决于您的基础模型。

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

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

便利类

        许多便利类从标准视图类派生而来,以利于依赖Qt基于项的项视图和表类的应用程序。它们不打算被子类化。

        此类类的示例包括QListWidget、QTreeWidget和QTableWidget。

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

        如果您希望利用模型/视图方法提供的功能,同时仍然使用基于项的接口,请考虑使用视图类,如QListView、QTableView和QTreeView与QStandardItemModel。

使用模型和视图

        以下各节解释如何在Qt中使用模型/视图模式。每一节都包括一个示例,后面是一节介绍如何创建新组件。

Qt 中包含的两个模型

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

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

在现有模型中使用视图

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

QT Model/View 编程:MVC模型视图编程(一)_第2张图片

        我们设置了一个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->setWindowTitle("Two views onto the same file system model");
    splitter->show();
    return app.exec();

        在上面的示例中,我们忽略了如何处理项目的选择。在有关处理项目视图中的选择的部分中,将更详细地介绍此主题。

模型类

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

基本概念

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

QT Model/View 编程:MVC模型视图编程(一)_第3张图片

        模型还通过信号和时隙机制通知任何附加视图有关数据的更改。

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

模型索引

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

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

QAbstractItemModel *model = index.model();

        模型索引提供对信息片段的临时引用,可用于通过模型检索或修改数据。由于模型可能会不时重新组织其内部结构,因此模型索引可能会变得无效,不应存储。如果需要对一条信息进行长期引用,则必须创建持久模型索引。这为模型保持最新的信息提供了参考。临时模型索引由QModelIndex类提供,持久模型索引由QPersistentModelIndex类提供。

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

行和列

        在其最基本的形式中,模型可以作为一个简单的表来访问,其中的项通过行和列编号来定位。这并不意味着底层数据段存储在阵列结构中;使用行和列编号只是允许组件相互通信的约定。我们可以通过在模型中指定行和列编号来检索任何给定项的信息,并接收表示该项的索引:

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

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

QT Model/View 编程:MVC模型视图编程(一)_第4张图片

行和列

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

​

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

QT Model/View 编程:MVC模型视图编程(一)_第5张图片

父级、行和列

该图显示了树模型的表示,其中每个项都由父项、行号和列号引用。项目“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);
​

Item roles项角色;数据元素显示方式

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

        我们可以通过向模型传递与该项对应的模型索引,并通过指定角色来获得我们想要的数据类型,从而向模型请求该项的数据:

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

QT Model/View 编程:MVC模型视图编程(一)_第6张图片

Item roles:项角色;数据元素显示方式

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

 

“创建新模型”部分更详细地介绍了角色的一些特定用途。

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

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

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

    }

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

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

延伸阅读

        可以通过实现QAbstracteModel提供的标准接口来创建新模型。在创建新模型一节中,我们通过创建一个方便的现成模型来演示这一点,该模型用于保存字符串列表。

视图类:

概念:

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

        通过使用QAbstractItemModel提供的标准模型接口、QAbstractionItemView提供的标准视图接口以及以通用方式表示数据项的模型索引,实现了内容和表示的分离。视图通常管理从模型获得的数据的总体布局。它们可以自己渲染单个数据项,也可以使用委托处理渲染和编辑功能。

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

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

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

使用现有视图

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

QT Model/View 编程:MVC模型视图编程(一)_第7张图片

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

使用模型

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

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

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

QT Model/View 编程:MVC模型视图编程(一)_第8张图片

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

使用模型的多个视图

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

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

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

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

QT Model/View 编程:MVC模型视图编程(一)_第9张图片

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

处理项目的选择

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

        通常,除非要对模型或视图进行子类化,否则不需要直接操作选择的内容。但是,如果需要,可以访问选择模型的接口,这将在处理项目视图中的选择时进行探讨。

在视图之间共享选择

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

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

        第二视图给出了第一视图的选择模型。现在,两个视图都在同一选择模型上操作,保持数据和所选项目同步。

QT Model/View 编程:MVC模型视图编程(一)_第10张图片

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

委托类

概念:

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

        委托应该能够通过实现paint()和sizeHint()函数来呈现其内容。但是,简单的基于小部件的委托可以子类化QStyledItemDelegate而不是qAbstractItemDelege,并利用这些函数的默认实现。

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

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

使用现有委托类

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

        所有标准角色都由标准视图使用的默认委托处理。QStyledItemDelegate文档中描述了这些的解释方式。

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

委托实例:

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

QT Model/View 编程:MVC模型视图编程(一)_第11张图片

        我们从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,这取决于正在编辑的列。

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

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

    QSpinBox *spinBox = static_cast(editor);
    spinBox->setValue(value);
}

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

向模型提交数据

        当用户完成编辑数字显示框中的值时,视图通过调用setModelData()函数要求代理将编辑的值存储在模型中。

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

    model->setData(index, value, Qt::EditRole);
}

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

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

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

更新编辑器的几何体

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

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

        在这种情况下,我们只使用项目矩形中的视图选项提供的几何信息。呈现具有多个元素的项的委托不会直接使用项矩形。它将相对于项中的其他元素定位编辑器。

编辑提示

        编辑后,代理应向其他组件提供有关编辑过程结果的提示,并提供有助于后续编辑操作的提示。这是通过发出带有适当提示的closeEditor()信号来实现的。这是由默认的QStyledItemDelegate事件过滤器处理的,我们在构建时安装在数字显示框上。

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

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

        委托不必发出这些提示,但不发出这些提示的委托将较少集成到应用程序中,并且比发出提示以支持常见编辑操作的委托更不可用。

处理项目视图中的选择

概念:

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

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

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

        选择应用于由选择模型保存的模型索引的集合。最近应用的项目选择称为当前选择。即使在使用某些类型的选择命令后,也可以修改此选择的效果。本节后面将讨论这些问题。

当前项目和选定项目

        在视图中,始终存在一个当前项和一个选定项-两个独立的状态。项目可以是当前项目,也可以同时选择。视图负责确保始终存在当前项,例如,键盘导航需要当前项。

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

当前项 选中项
只能有一个当前项目。 可以有多个选定项目。
当前项目将通过键导航或鼠标按钮点击进行更改。 当用户与项目交互时,项目的选定状态取决于几种预定义模式(例如,单选、多选等)的设置或取消设置。
如果按下编辑键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);

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

QT Model/View 编程:MVC模型视图编程(一)_第12张图片

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

读取选择状态

        可以使用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);
    }

        上面的代码使用基于范围的for循环来迭代和修改与选择模型返回的索引相对应的项。

        选择模型发出信号以指示选择中的变化。这些组件通知其他组件对整个选择和项目模型中当前关注的项目的更改。我们可以将selectionChanged()信号连接到一个插槽,并检查模型中在选择更改时选择或取消选择的项目。该槽由两个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()函数时,如何更新选定项的内部记录。最常用的标志是选择标志,它指示选择模型将指定的项目记录为被选择。切换标志使选择模型反转指定项的状态,选择给定的任何未选择项,并取消选择任何当前选定项。取消选择标志取消选择所有指定项目。

        通过创建项目选择并将其应用于选择模型,更新选择模型中的各个项目。在下面的代码中,我们将第二次选择项应用到上面显示的表模型,使用Toggle命令反转给定项的选择状态。

 QItemSelection toggleSelection;

    topLeft = model->index(2, 1, QModelIndex());
    bottomRight = model->index(7, 3, QModelIndex());
    toggleSelection.select(topLeft, bottomRight);

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

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

QT Model/View 编程:MVC模型视图编程(一)_第13张图片

        默认情况下,选择命令仅对模型索引指定的单个项进行操作。但是,用于描述选择命令的标志可以与其他标志组合,以更改整个行和列。例如,如果仅使用一个索引调用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);

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

QT Model/View 编程:MVC模型视图编程(一)_第14张图片

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

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

选择模型中的所有项目

        若要选择模型中的所有项目,需要为模型的每个级别创建一个选择,以覆盖该级别中的所有项。我们通过检索与给定父索引的左上和右下项目对应的索引来实现这一点:

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)显示来自各种源的数据。

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

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

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

        有关更复杂模型的示例,请参见简单树模型示例。

        QabstracteModel子类的要求在模型子类参考文档中有更详细的描述。

设计模型

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

        然而,如果底层数据结构只能由层次树结构表示,则有必要将QAbstractItemModel子类化。在简单树模型示例中采用了这种方法。

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

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

只读示例模型

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

        在实现模型时,重要的是要记住,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私有成员变量中。

模型的尺寸

        我们希望模型中的行数与字符串列表中的字符串数相同。我们实现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);
}

        同样,只有当角色是我们支持的角色时,我们才返回有效的QVariant。在决定要返回的确切数据时,还考虑了标题的方向。

        并非所有视图都显示带有项目数据的标题,而那些显示标题的视图可能会被配置为隐藏它们。尽管如此,建议您实现headerData()函数,以提供有关模型提供的数据的相关信息。

        一个项目可以有多个角色,根据指定的角色提供不同的数据。我们模型中的项只有一个角色,DisplayRole,因此我们返回项的数据,而不考虑指定的角色。但是,我们可以在其他角色中重用我们为DisplayRole提供的数据,例如视图可以用来显示工具提示中项目信息的工具提示角色。

可编辑模型

        只读模型显示了如何向用户提供简单的选择,但对于许多应用程序,可编辑列表模型更有用。我们可以修改只读模型,通过更改我们为只读实现的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;

        由于此模型中的行对应于列表中的字符串,insertRows()函数在指定位置之前将多个空字符串插入到字符串列表中。插入的字符串数等于指定的行数。

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

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类的行为,该类实现了项选择和标题管理的通用行为。

列表控件:List widgets

        单级项目列表通常使用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);

树控件:Tree widgets

        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()函数来确定项是否隐藏,并且可以使用setItemHidded()隐藏项。

        由于此操作是基于项目的,因此相同的功能适用于所有三个便利类。

选择:

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

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

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

扩展选择:通常需要选择许多相邻项的小部件,如电子表格中的小部件需要扩展选择模式。在此模式下,可以使用鼠标和键盘选择小部件中的连续项目范围。如果使用修改键,也可以创建复杂的选择,包括许多与小部件中其他选定项目不相邻的项目。

如果用户在不使用修改键的情况下选择项目,则现有选择将被清除。

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

const QList 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 found = treeWidget->findItems(
        itemText, Qt::MatchWildcard);

for (QTreeWidgetItem *item : found) 
{
    item->setSelected(true);
    // Show the item->text(0) for each item.
}

        上面的代码导致选择树小部件中的项目,如果它们包含搜索字符串中给定的文本。此模式也可以用于列表和表小部件。

在项目视图中使用拖放

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

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

        对于只允许从视图中导出项且不允许将数据放入视图中的模型的要求比完全启用拖放模型的要求要少。

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

使用方便视图

        默认情况下,QListWidget、QTableWidget和QTreeWidget使用的每种类型的项都配置为使用一组不同的标志。例如,每个QListWidgetItem或QtreeWidget项最初都是启用的、可检查的、可选择的,并且可以用作拖放操作的源;还可以编辑每个QTableWidgetItem并将其用作拖放操作的目标。

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

  • 若要启用项拖动,请将视图的DrageEnabled属性设置为true。
  • 要允许用户在视图中放置内部或外部项,请将视图的viewport()的acceptDrops属性设置为true。
  • 若要向用户显示当前正在拖动的项目在被放置时的位置,请设置视图的showDropIndicator属性。这为用户提供了关于视图内项目放置的持续更新信息。

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

QListWidget *listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
listWidget->setDragEnabled(true);
listWidget->viewport()->setAcceptDrops(true);
listWidget->setDropIndicatorShown(true);

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

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

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

使用模型/视图类

        为拖放设置视图遵循与方便视图相同的模式。例如,QListView的设置方式与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::ItemsDragEnabled和Qt::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类型列表,来声明可用于提供项的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()来处理。例如,一个处理简单字符串列表的模型可以提供一个实现,该实现可以分别处理放置到现有项上的数据和放置到模型顶层的数据(即,放置到无效项上)。

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

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

bool DragDropListModel::canDropMimeData(const QMimeData *data,
    Qt::DropAction action, int row, int column, const QModelIndex &parent) const
{
    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;

        如果提供的数据不是纯文本,或者为drop提供的列号无效,则简单的一栏字符串列表模型可以指示失败。

        根据是否将要插入模型中的数据放置到现有项上,将对其进行不同的处理。在这个简单的示例中,我们希望允许在列表中第一项之前和最后一项之后的现有项之间进行拖放。

        发生删除时,与父项对应的模型索引将有效,表示删除发生在项上,或者无效,表示删除出现在视图中与模型顶层对应的某个位置。

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()和QAbstractionItemModel::setData()函数的实现。

代理模型

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

        虽然将排序和过滤操作作为视图的内部功能似乎是合适的,但这种方法不允许多个视图共享此类潜在昂贵操作的结果。另一种方法涉及模型本身的排序,导致类似的问题,即每个视图都必须显示根据最新处理操作组织的数据项。

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

使用代理模型

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

 QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);
 filterModel->setSourceModel(stringListModel);

 QListView *filteredView = new QListView;
 filteredView->setModel(filterModel);

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

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

自定义代理模型

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

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

自定义筛选模型

        QSortFilterProxyModel类提供了一个非常通用的过滤模型,可用于各种常见情况。对于高级用户,可以对QSortFilterProxyModel进行子类化,从而提供一种机制来实现自定义过滤器。

        QSortFilterProxyModel的子类可以重新实现两个虚拟函数,只要请求或使用代理模型的模型索引,就会调用这些函数:

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

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

自定义排序模型

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

模型子类化参考

        模型子类需要提供QabstracteModel基类中定义的许多虚拟函数的实现。需要实现的这些函数的数量取决于模型的类型——无论它提供的视图是简单的列表、表还是复杂的项目层次结构。从QAbstractListModel和QAbstract TableModel继承的模型可以利用这些类提供的函数的默认实现。以树状结构公开数据项的模型必须为QabstracteModel中的许多虚拟函数提供实现。

需要在模型子类中实现的功能可分为三组:

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

项目数据处理

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

只读访问

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

flags() 由其他组件使用以获得关于模型提供的每个项目的信息。在许多模型中,标志的组合应包括Qt::ItemIsEnabled和Qt::ItemIsSelectable。
data() 用于向视图和委托提供项目数据。通常,模型只需要为Qt::DisplayRole和任何特定于应用程序的用户角色提供数据,但为Qt::ToolTipRole、Qt::AccessibleTextRole和Qt::accessibledDescriptionRole提供数据也是很好的做法。有关与每个角色关联的类型的信息,请参阅Qt::ItemDataRole枚举文档。
headerData() 为视图提供要在其标题中显示的信息。该信息仅由可以显示标题信息的视图检索。
rowCount() 提供模型公开的数据行数。

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

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

columnCount() 提供模型公开的数据列数。列表模型不提供此函数,因为它已在QAbstractListModel中实现。

可编辑项

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

flags() 必须为每个项返回适当的标志组合。特别是,此函数返回的值必须包括Qt::ItemIsEditable以及应用于只读模型中的项的值。
setData() 用于修改与指定模型索引关联的数据项。为了能够接受用户界面元素提供的用户输入,该函数必须处理与Qt::EditRole相关的数据。该实现还可以接受与Qt::ItemDataRole指定的许多不同类型角色相关的数据。更改数据项后,模型必须发出dataChanged()信号,通知其他组件更改。
setHeaderData() 用于修改水平和垂直标题信息。更改数据项后,模型必须发出headerDataChanged()信号,通知其他组件更改。

可调整大小的模型

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

insertRows() 用于向所有类型的模型添加新行和数据项。实现必须在将新行插入任何底层数据结构之前调用beginInsertRows(),然后立即调用EndInsertRow()。
removeRows() 用于从所有类型的模型中删除行及其包含的数据项。实现必须在从任何底层数据结构中移除行之前调用BeginRemoverRows(),然后立即调用endRemoveRows()。
insertColumns() 用于向表模型和层次模型添加新的列和数据项。实现必须在将新列插入任何基础数据结构之前调用beginInsertColumns(),然后立即调用EndInsertColum()。
removeColumns() 用于从表模型和层次模型中删除列及其包含的数据项。实现必须在从任何底层数据结构中删除列之前调用beginRemoveColumns(),然后立即调用endRemoveColumns()。

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

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

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

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

        该序列可用于任何结构更新,以代替更高级和更方便的保护方法。例如,如果一个200万行的模型需要删除所有奇数行,也就是说,每个元素有100万个折扣范围。可以使用BeginRemoverRows和EndRemoverrows 100万次,但这显然效率低下。相反,这可以作为一个布局更改发出信号,它会立即更新所有必要的持久索引。

模型数据的惰性填充

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

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

        在分层模型中,查找给定项的子项的数量是一项昂贵的操作,确保模型的rowCount()实现仅在必要时调用是非常有用的。在这种情况下,可以重新实现hasChildren()函数,为视图提供一种廉价的方式来检查子项的存在,在QTreeView的情况下,为其父项绘制适当的装饰。

        无论hasChildren()的重新实现返回true还是false,视图都可能不需要调用rowCount()来确定存在多少子元素。例如,如果父项未展开显示,则QTreeView不需要知道有多少子项。

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

导航和模型索引创建

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

父与子

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

index() 给定父项的模型索引,此函数允许视图和委托访问该项的子项。如果找不到与指定的行、列和父模型索引对应的有效子项,则函数必须返回QModelIndex(),这是一个无效的模型索引。
parent() 提供与任何给定子项的父项对应的模型索引。如果指定的模型索引对应于模型中的顶级项,或者如果模型中没有有效的父项,则函数必须返回使用空QModelIndex()构造函数创建的无效模型索引。

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

拖放支持和MIME类型处理

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

        此外,便利视图类实现了专门的行为,这些行为应该严格遵循现有开发人员的预期。便利视图部分提供了此行为的概述。

MIME 数据

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

        使用此MIME类型编码的数据可以通过使用包含要序列化的项的QmodelExList调用QAbstractItemModel::mimeData()来获得。

        在自定义模型中实现拖放支持时,可以通过重新实现以下函数以专用格式导出数据项:

mimeData()

可以重新实现此函数以返回除默认应用程序/x-qabstractitemmodeldatalist内部MIME类型以外的格式的数据。

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

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

接受其他的数据

        在视图上执行拖放操作时,将查询基础模型,以确定其支持的操作类型以及可以接受的MIME类型。此信息由QAbstractItemModel::supportedDropActions()和QAbstractionItemModel::mimeTypes()函数提供。不覆盖QAbstractItemModel提供的实现的模型支持复制操作和项的默认内部MIME类型。

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

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

insertRows() 这些函数使模型能够使用QAbstractItemModel::dropMimeData()提供的现有实现自动插入新数据。
insertColumns()
setData() 允许用项填充新行和列。
setItemData() 此功能为填充新项提供了更有效的支持。

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

supportedDropActions() 用于返回拖放操作的组合,指示模型接受的拖放操作类型。
mimeTypes() 用于返回可由模型解码和处理的MIME类型列表。通常,支持输入到模型中的MIME类型与外部组件在编码数据时可以使用的类型相同。
dropMimeData() 对通过拖放操作传输的数据执行实际解码,确定将在模型中设置数据的位置,并在必要时插入新行和列。如何在子类中实现此函数取决于每个模型所公开的数据的要求。

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

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

  • removeRows()
  • removeRow()
  • removeColumns()
  • removeColumn()

有关项目视图拖放的详细信息,请参阅使用项目视图拖放。

方便查看

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

针对大量数据的性能优化

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

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

        如果重新实现fetchMore()将行添加到模型中,则需要调用beginInsertRows()和EndInsertRow()。此外,必须重新实现canFetchMore()和fetchMore(),因为它们的默认实现返回false,并且不做任何操作。

The Model/View Classes:模型/视图类:

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

QAbstractItemDelegate

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

QAbstractItemModel

项模型类的抽象接口

QAbstractItemView

项视图类的基本功能

QAbstractListModel

抽象模型,可子类化以创建一维列表模型

QAbstractProxyModel

代理项模型的基类,可以执行排序、筛选或其他数据处理任务

QAbstractTableModel

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

QColumnView

列视图的模型/视图实现

QConcatenateTablesProxyModel

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

QDataWidgetMapper

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

QFileSystemModel

本地文件系统的数据模型

QHeaderView

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

QIdentityProxyModel

代理其源模型未修改

QItemDelegate

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

QItemEditorCreator

可以创建项编辑器创建者库,而无需子类化QItemEditorCreatorBase

QItemEditorCreatorBase

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

QItemEditorFactory

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

QItemSelection

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

QItemSelectionModel

跟踪视图的选定项

QItemSelectionRange

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

QListView

模型上的列表或图标视图

QListWidget

基于项的列表小部件

QListWidgetItem

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

QModelIndex

用于定位数据模型中的数据

QModelRoleData

保存角色和与该角色关联的数据

QModelRoleDataSpan

跨越 QModelRoleData 对象

QPersistentModelIndex

用于定位数据模型中的数据

QSortFilterProxyModel

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

QStandardItem

用于QStandardItemModel类的项

QStandardItemEditorCreator

无需子类QItemEditorCreatorBase即可注册小部件

QStandardItemModel

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

QStringListModel

为视图提供字符串的模型

QStyledItemDelegate

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

QTableView

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

QTableWidget

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

QTableWidgetItem

与QTableWidget类一起使用的项

QTableWidgetSelectionRange

在不使用模型索引和选择模型的情况下与模型中的选择交互的方式

QTreeView

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

QTreeWidget

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

QTreeWidgetItem

与QTreeWidget便利类一起使用的项

QTreeWidgetItemIterator

在QTreeWidget实例中迭代项的方法

相关实例

  • Dir View
  • Spin Box Delegate
  • Pixelator
  • Simple Tree Model
  • Chart

另请参阅 Item Views Puzzle Example.

你可能感兴趣的:(Qt及C++应用,qt,mvc,模型/视图/委托)