在 model/view 架构中,model 提供一种标准接口,供视图和委托访问数据。在 Qt 中,这个接口由QAbstractItemModel
类进行定义。无论底层数据是如何存储的,只要是QAbstractItemModel
的子类,都提供一种表格形式的层次结构。视图利用统一的转换来访问模型中的数据。可是,须要提供的是,尽管模型内部是这样组织数据的,可是并不要求也得这样子向用户展现数据。数组
下面是各类 model 的组织示意图。咱们利用此图来理解什么叫“一种表格形式的层次结构”。架构
如上图所示,List Model 虽然是线性的列表,也有一个 Root Item(根节点),之下才是呈线性的一个个数据,而这些数据实际能够看做是一个只有一列的表格,可是它是有层次的,由于有一个根节点。Table Model 就比较容易理解,只是也存在一个根节点。Tree Model 主要面向层次数据,而每一层次均可以都不少列,所以也是一个带有层次的表格。函数
为了可以使得数据的显示同存储分离,咱们引入模型索引(model index)的概念。经过索引,咱们能够访问模型的特定元素的特定部分。视图和委托使用索引来请求所须要的数据。由此能够看出,只有模型本身须要知道如何得到数据,模型所管理的数据类型可使用通用的方式进行定义。索引保存有建立的它的那个模型的指针,这使得同时操做多个模型成为可能。指针
QAbstractItemModel *model = index.model();
模型索引提供了所须要的信息的临时索引,能够用于经过模型取回或者修改数据。因为模型随时可能从新组织其内部的结构,所以模型索引极可能变成不可用的,此时,就不该该保存这些数据。若是你须要长期有效的数据片断,必须建立持久索引。持久索引保证其引用的数据及时更新。临时索引(也就是一般使用的索引)由QModelIndex
类提供,持久索引则是QPersistentModelIndex
类。code
为了定位模型中的数据,咱们须要三个属性:行号、列号以及父索引。下面咱们对其一一进行解释。对象
咱们前面介绍过模型的基本形式:数据以二维表的形式进行存储。此时,一个数据能够由行号和列号进行定位。注意,咱们仅仅是使用“二维表”这个名词,并不意味着模型内部真的是以二维数组的形式进行存储;所谓“行号”“列号”,也仅仅是为方便描述这种对应关系,并不真的是有行列之分。经过指定行号和列号,咱们能够定位一个元素项,取出其信息。此时,咱们得到的是一个索引对象(回忆一下,经过索引咱们能够获取具体信息):索引
QModelIndex index = model->index(row, column, ...);
模型提供了一个简单的接口,用于列表以及表格这种非层次视图的数据获取。不过,正如上面的代码暗示的那样,实际接口并非那么简单。咱们能够经过文档查看这个函数的原型:接口
QModelIndex QAbstractItemModel::index(int row, int column, const QModelIndex &parent=QModelIndex()) const
这里,咱们仅仅使用了前两个参数。经过下图来理解一下:文档
在一个简单的表格中,每个项均可以由行号和列号肯定。所以,咱们只需提供两个参数便可获取到表格中的某一个数据项:字符串
QModelIndex indexA = model->index(0, 0, QModelIndex()); QModelIndex indexB = model->index(1, 1, QModelIndex()); QModelIndex indexC = model->index(2, 1, QModelIndex());
函数的最后一个参数始终是 QModelIndex(),接下来咱们就要讨论这个参数的含义。
在相似表格的视图中,好比列表和表格,行号和列号足以定位一个数据项。可是,对于树型结构,仅有两个参数就不足够了。这是由于树型结构是一个层次结构,而层次结构中每个节点都有多是另一个表格。因此,每个项须要指明其父节点。前面说过,在模型外部只能用过索引访问内部数据,所以,index()
函数还须要一个 parent 参数:
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);
由此咱们看到,若是只有行号和列号两个参数,B 的行号是 1,列号是 0,这同与 A 同级的行号是 1,列号是 0 的项相同,因此咱们经过 parent 属性区别开来。
以上咱们讨论了有关索引的定位。如今咱们来看看模型的另一个部分:数据角色。模型能够针对不一样的组件(或者组件的不一样部分,好比按钮的提示以及显示的文本等)提供不一样的数据。例如,Qt::DisplayRole
用于视图的文本显示。一般来讲,数据项包含一系列不一样的数据角色,这些角色定义在Qt::ItemDataRole
枚举中。
咱们能够经过指定索引以及角色来得到模型所提供的数据:
QVariant value = model->data(index, role);
经过为每个角色提供恰当的数据,模型能够告诉视图和委托如何向用户显示内容。不一样类型的视图能够选择忽略本身不须要的数据。固然,咱们也能够添加咱们所须要的额外数据。
总结一下:
index()
函数请求得到一个父项的可用索引,该索引会指向模型中这个父项下面的数据项。这个索引指向该项的一个子项;若是使用index()
函数请求得到一个父项的不可用索引,该索引指向模型的最顶级项;下面回到前面咱们曾经见过的模型QFileSystemModel
,看看如何从模型获取数据。
QFileSystemModel *model = new QFileSystemModel; QModelIndex parentIndex = model->index(QDir::currentPath()); int numRows = model->rowCount(parentIndex);
在这个例子中,咱们建立了QFileSystemModel
的实例,使用QFileSystemModel
重载的index()
获取索引,而后使用rowCount()
函数计算当前目录下有多少数据项(也就是行数)。前面一章中迷迷糊糊的代码,如今已经至关清楚了。
为简单起见,下面咱们只关心模型第一列。咱们遍历全部数据,取得第一列索引:
for (int row = 0; row < numRows; ++row) { QModelIndex index = model->index(row, 0, parentIndex);
咱们使用index()
函数,第一个参数是每一行行号,第二个参数是 0,也就是第一列,第三个参数是 parentIndex,也就是当前目录做为父项。咱们可使用模型的data()
函数获取每一项的数据。注意,该函数返回值是QVariant
,实际是一个字符串,所以咱们直接转换成QString
:
QString text = model->data(index, Qt::DisplayRole).toString(); // 使用 text 数据 }
上面的代码片断显示了从模型获取数据的一些有用的函数:
rowCount()
和columnCount()
得到。这些函数须要制定父项;QModelIndex()
建立一个空索引使用时,咱们得到的就是模型中最顶级项;