1.基本概念
在模型/视图架构中,模型提供了一个标准接口,用于视图和委托访问数据。在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中定义的标准角色所涵盖。通过为每个角色提供适当的物品数据,模型可以向视图提供提示,并委托视图将物品如何呈现给用户。不同类型的视图可以根据需要自由地解释或忽略此信息。还可以为特定于应用程序的目的定义额外的角色。
总结
模型索引以一种独立于任何底层数据结构的方式向视图和委托提供关于模型所提供的项目位置的信息。
元素项通过行号和列号以及父元素项的model索引进行引用。
模型索引是由模型根据其他组件(如视图和委托)的请求构建的。
如果在使用index()方法请求索引时为父元素指定了有效的模型索引,则返回的索引指向模型中父元素下面的元素。获得的索引指向该项的一个子项。
如果在使用index()方法请求索引时,为父元素指定了无效的模型索引,则返回的索引指向模型中的顶层元素。
角色区分与项相关联的不同类型的数据。
2.使用模型索引
为了演示如何使用模型索引从模型中检索数据,我们设置了一个没有视图的QFileSystemModel,并在一个小部件中显示文件和目录的名称。虽然这不是使用模型的正常方式,但它展示了模型在处理模型索引时使用的约定。
QFileSystemModel的加载是异步的,以最小化系统资源使用。在处理这个模型时,我们必须考虑到这一点。
我们用以下方式构建文件系统模型:
// 创建一个新的QFileSystemModel实例,该实例会用于追踪和展示文件系统的状态。
QFileSystemModel *model = new QFileSystemModel;
// 连接模型的directoryLoaded信号到一个lambda函数。每当一个目录被加载完成时,这个lambda函数就会被调用。
connect(model, &QFileSystemModel::directoryLoaded, [model](const QString &directory) {
// 获取目录在模型中的索引
QModelIndex parentIndex = model->index(directory);
// 获取该目录下的所有文件和子目录的数量
int numRows = model->rowCount(parentIndex);
});
// 设置模型的根路径。QDir::currentPath会返回当前运行的程序的工作目录。模型会追踪这个路径下所有文件和子目录的变化。
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()函数取得。我们指定模型索引和DisplayRole以字符串形式获取项目的数据。
QString text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.
}
上面的例子演示了从模型中检索数据的基本原则:
使用rowCount()和columnCount()可以得到模型的维度。这些函数通常需要指定一个父模型索引。
模型索引用于访问模型中的项。指定项目需要行、列和父模型索引。
要访问模型中的顶层元素,可以用QModelIndex()指定一个空的模型索引作为父索引。
项目包含不同角色的数据。要获取特定角色的数据,必须向模型提供模型索引和角色。
3.拓展
通过实现QAbstractItemModel提供的标准接口,可以创建新的模型。在创建新模型一节中,我们通过创建一个方便的即用模型来保存字符串列表来演示这一点。