在前面两章的基础之上,我们将开始介绍 model 的通用概念。
在 model/view 架构中,model 提供一种标准接口,供视图和委托访问数据。在 Qt 中,这个接口由QAbstractItemModel
类进行定义。不管底层数据是如何存储的,只要是QAbstractItemModel
的子类,都提供一种表格形式的层次结构。视图利用统一的转换来访问模型中的数据。但是,需要提供的是,尽管模型内部是这样组织数据的,但是并不要求也得这样子向用户展示数据。
下面是各种 model 的组织示意图。我们利用此图来理解什么叫“一种表格形式的层次结构”。

如上图所示,List Model 虽然是线性的列表,也有一个 Root Item(根节点),之下才是呈线性的一个个数据,而这些数据实际可以看作是一个只有一列的表格,但是它是有层次的,因为有一个根节点。Table Model 就比较容易理解,只是也存在一个根节点。Tree Model 主要面向层次数据,而每一层次都可以都很多列,因此也是一个带有层次的表格。
为了能够使得数据的显示同存储分离,我们引入模型索引(model index)的概念。通过索引,我们可以访问模型的特定元素的特定部分。视图和委托使用索引来请求所需要的数据。由此可以看出,只有模型自己需要知道如何获得数据,模型所管理的数据类型可以使用通用的方式进行定义。索引保存有创建的它的那个模型的指针,这使得同时操作多个模型成为可能。
QAbstractItemModel *model = index.model();
模型索引提供了所需要的信息的临时索引,可以用于通过模型取回或者修改数据。由于模型随时可能重新组织其内部的结构,因此模型索引很可能变成不可用的,此时,就不应该保存这些数据。如果你需要长期有效的数据片段,必须创建持久索引。持久索引保证其引用的数据及时更新。临时索引(也就是通常使用的索引)由QModelIndex
类提供,持久索引则是QPersistentModelIndex
类。
为了定位模型中的数据,我们需要三个属性:行号、列号以及父索引。下面我们对其一一进行解释。
我们前面介绍过模型的基本形式:数据以二维表的形式进行存储。此时,一个数据可以由行号和列号进行定位。注意,我们仅仅是使用“二维表”这个名词,并不意味着模型内部真的是以二维数组的形式进行存储;所谓“行号”“列号”,也仅仅是为方便描述这种对应关系,并不真的是有行列之分。通过指定行号和列号,我们可以定位一个元素项,取出其信息。此时,我们获得的是一个索引对象(回忆一下,通过索引我们可以获取具体信息):
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()
创建一个空索引使用时,我们获得的就是模型中最顶级项; - 数据项包含了不同角色的数据。为获取特定角色的数据,必须指定这个角色。
25 评论
温柔可爱帅气漂亮美丽善良玉树临风风流倜傥英俊潇洒的楼主你好,Qt学习之路2,被我捣鼓成了rst文档,并放到GitHub维护了。
所有内链都指向了原网址,这样也算是转载吧?可以么?
过奖了哦~看到那一长串修饰词…多谢帮忙维护啦!
咦,网址没了呢。再试试~
https://github.com/joinAero/qt-study-road-2
能不能讲讲持久模型索引的应用 QPersistentModelIndex
网上基本找不到相关文章
先看看文档呢?不知道有什么问题?
豆子你好:
最后的源代码里面,我编译运行后,numRows 为0。。麻烦豆子讲解一下。
这个你可以试试换个运行目录看看有没有结果?会不会是当前目录的问题?
QFileSystemModel *model = new QFileSystemModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
int numRows = model->rowCount(parentIndex);
改为以下内容,可正确显示该目录下的内容
QDirModel*model = new QDirModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
int numRows = model->rowCount(parentIndex);
文中的代码测试是正确的。QDirModel 已经是不建议使用的了,仅为向前兼容才保留的;QFileSystemModel 比它性能更好。
俺这里在5.5 Qt Creator下也是numRows = 0 读不到数据~
调试发现目录取到,下面也有东西!
parentIndex显示只这样QModelIndex(0,0,0x13e07e70,QFileSystemModel(0x1554dfa0)
后面的数字像是指针,不清楚什么问题?
QFileSystemModel *model = new QFileSystemModel;
QModelIndex parentIndex = model->index(QDir::currentPath());
qDebug() << "Columns: " << parentIndex.column();
qDebug() << " Rows: " <rowCount(parentIndex);
qDebug() << "ROWS: " << numRows << parentIndex <columnCount(parentIndex) ;
//这里读到的rows为0,原因待查
执行结果是这样
Columns: 0
Rows: 0
ROWS: 0 QModelIndex(0,0,0x14007e70,QFileSystemModel(0x15a1dfa0)) 4
就是这里嘛~上面有个兄弟也提了这疑问~本来路径对了~下面也有文件也有目录~但是后面取rowCount就是0~
QFileSystemModel建立之后,直接调用rowCount时返回零,因为它使用一个单独的线程来填充树。当树被填充之后,才能正确地返回rowCount。
而QDir没有多线程,可以正确返回已填充的树rowCount。缺点在于阻塞式地执行,程序会有一段时间的“假死”,特别是如果文件夹层级很多的时候,卡顿会很明显
可以使用directoryLoaded 信号。连接槽的方式,处理显示的目录
豆纸你好
Note: When implementing a table based model, rowCount() should return 0 when the parent is valid. 这是我在文档中看到的rowCount的解释,是不是因为FileSystemMode是table类型的所以返回0?
豆子老师,你好。请教个问题。代码大致如下:
1 QStandardItemModel *import_model = new QStandardItemModel(this);
2 import_model->setHeaderData(0,Qt::Horizontal,tr("location"));
3 import_model->setHeaderData(1,Qt::Horizontal,tr("status"));
4 QStandardItem *pItem = new QStandardItem("First Item");
5 import_model->setItem(0,0, pItem);
...
想问下,第4句的new操作后,却没有delete操作(因为delete后,界面上就展示不出来了),这样不是就会存在内存泄漏吗?而且试了下,import_model调用clear( ),然后释放其指向的内存空间后,pItem指针指向的内存空间确实还是存在的。
这个需要了解下源代码。猜测如果你的测试没有错误的话,clear() 可能没有真正释放内存,而是等到整个 model 析构之后才会释放。
豆子老师,你好。今天找了Qt的源码,查看了下clear( ),其确实是将model下各个结点的内存空间释放了的(delete后,指针未被赋为NULL),包括removeRow( )和removeRows( )。我之前就简单地判断下指针是否为NULL,来判断其指向的内存空间是否被释放。谢谢~~
使用 NULL 判断的确是不可行的,除非析构之后显式设置为 NULL 或者使用智能指针(话说当时我是以为你用的内存泄露检测工具测试的)。
博主,role可以理解为model的一个属性吗?比如表格控件中的某个单元格,它的role如果是Qt::EditRole,就是告诉view这是一个可以编辑的部件,然后view就把它显示为一个可以编辑的文本框?
Qt::EditRole还好理解,像Qt::TextAlignmentRole(文本对齐角色?)怎么理解呢?
role 可以理解成是附加属性,类似于给 model 设置了一个 map,role 作为 map 的 key。Qt 会根据这个 map 决定如何显示 model。所以,Qt::TextAlignmentRole 就是说对齐是要怎样显示。个人感觉,可以把 role 看作是一种“偷懒”,因为有了这个类似 map 一样的 role,model 就不用提供一堆比如 setEdit(),setTextAlignment() 之类的函数。
再问一下:文本对齐有左对齐、右对齐、居中对齐等不同的方式,那Qt::TextAlignmentRole还能设置不同的值(联机帮忙中没显示有不同的值)?还是说Qt::TextAlignmentRole只是告诉view这是文本对齐的属性,具体的对齐方式由另外的值来设置?
QT好多概念都是点到即止啊
版主~看不到图了….