QAbstractItemModel 和 QModelIndex 之间的相互作用
Posted
技术标签:
【中文标题】QAbstractItemModel 和 QModelIndex 之间的相互作用【英文标题】:Interplay between QAbstractItemModel and QModelIndex 【发布时间】:2019-07-20 20:05:06 【问题描述】:以下问题是关于 QAbstractItemModel
和 QModelIndex
类的设计以及它们之间的相互作用,如下面的代码示例所示:
class Data:
def __init__(self):
self.value = 42
class Model( QAbstractItemModel ):
def __init__( self ):
QAbstractItemModel.__init__(self)
data = Data()
modelIndex = self.createIndex( 1 , 2 , data ) ### 1
self.index( 1 , 2 , QModelIndex() ) ### 2
self.setData( modelIndex , data.value ) ### 3
self.dataChanged.emit( modelIndex , modelIndex )
modelIndex.data() ###4
self.data( modelIndex ) ### 5
-
应该如何创建
QModelIndex
。根据我对文档的阅读,答案是QAbstractItemModel::createIndex(),但它似乎不完整,因为此函数不提供有关 ModelIndex 与其父级的偏移量的任何信息。相反,这是由QAbstractItemModel::index() 完成的。有没有办法让这两个功能一起发挥作用?
应如何将数据存储在模型索引中,由模型索引、为模型索引或在模型索引中(不确定术语)存储的数据与internal pointer 之间的区别是什么?当模型索引没有 setData 函数时,它从哪里获得返回的data?内部指针是数据吗?可以是数据吗?
ModelIndex和Model返回的数据有什么区别?即QModelIndex::data() 和QAbstractItemModel::data( QModelIndex , int )?为什么 setter QAbstractItemModel::setData( QModelIndex , ... ) 只是虚拟的,而 getter QAbstractItemModel::data( QModelIndex , ... ) 是纯虚拟的。当然,API 应该能够返回它存储的数据。
我知道我的问题链接到 C++ API,而 sn-p 在 PySide 中。我这样做是因为这个问题涉及两个 API。
【问题讨论】:
【参考方案1】:考虑到您有很多与 QAbstractItemModel 和 QModelIndex 相关的问题,涉及实施和其他设计问题,我将花时间一一回复,因此我将编辑此答案以提供更多详细信息。
1.
QModelIndex没有它的parent是谁的信息,也没有表示偏移量的信息,它只保留了行、列、指向数据的指针和它所属的模型的信息(见here) .所以你知道你的父母是谁,这是你的任务,你必须重写模型的 parent() 方法并返回它。
def parent(self, index):
if not index.isValid():
return QtCore.QModelIndex()
childItem = index.internalPointer()
parentItem = childItem.parentItem()
if parentItem == self.rootItem:
return QtCore.QModelIndex()
return self.createIndex(..., ..., parentItem)
所以在 QModelIndex 的parent() 方法中,这个通过模型获取父级(参见here):def parent(self) : return self.model.parent(self)
。
关于index()和createIndex()方法之间的关系,第二个被第一个调用,但也必须和你的数据结构有关系。一个通用的实现是:
def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QtCore.QModelIndex()
parentItem = None
if not parent.isValid():
parentItem = self.rootItem
else:
parentItem = parent.internalPointer()
childItem = parentItem.child(...)
if childItem is not None:
return self.createIndex(row, column, childItem)
return QtCore.QModelIndex()
在 createIndex() 的情况下,仅使用行、列、模型和指向 childItem 的指针的信息创建 QModelIndex,该构造函数不在文档中,因为它是私有构造函数(请参阅here )。
2.
internalPointer是存储数据的内存位置的变量,也就是QModelIndex没有数据但知道在哪里,所以数据必须分开存放,所以当使用方法获取数据时data()的模型你必须获取internalPointer(),返回信息所在的item,角色根据这个获取数据。
def data(self, index, role=QtCore.Qt.DisplayRole):
if not index.isValid():
return
item = index.internalPointer()
value = item.data(..., role) # like item.value
return value
3.
QModelIndex 的data() 方法使用了模型的data() 方法(参见here),因此在概念上它们是相同的,例如:def data(self, role): return self.model.data(self, role)
。
不是每个模型都是可编辑的,例如 QStringListModel 不是,所以 setData() 方法没有必要被覆盖,所以 Qt 默认使模型不可编辑,即它们什么都不做并返回 false(参见here),那么就是说一定是virtual,也就是这个方法只有在没有被覆盖的情况下才会被修改,如果不做的话,就会调用父类的方法,也就是说,它不会做任何事情。与data() 方法不同,因为每个模型都必须返回信息,因此当它从 QAbstractItemModel 继承时开发人员必须强制覆盖该类,因此它被声明为纯虚拟。我建议您阅读以便更详细地了解差异:C++ Virtual/Pure Virtual Explained
【讨论】:
【参考方案2】:虽然@eyllanesc 的答案是正确的,但我一直在努力理解它,直到我长时间地盯着this article,然后才出现了一个模式。因此,我以我认为比我提出问题的顺序更合乎逻辑的方式来为他的回答做出贡献。
尽管顾名思义,QAbstractItemModel 更好地理解为模型数据的接口。通常,模型数据的根是 QAbstractItemModel 对象的成员,该对象充当模型数据的各种包装器。 (例如,如果数据存储在 SQL 数据库中,则需要采用不同的方法。)还有 QAbstractItemModel:
定义数据组件之间的(分层)关系。
提供用于在模型中添加和删除数据行和列的函数。 (这一事实是理解如何使用 QModelIndex 的关键。)
QModelIndex 有很多东西,但最重要的是它包含 指向每个数据组件的内部指针,除了一些 关于当前数据组件在 数据层次结构(位置可以改变)。现在应该清楚为什么文档声明:
模型索引应立即使用,然后丢弃。你 调用模型后不应依赖索引保持有效 改变模型结构或删除项目的函数。
这也是为什么返回 QModelIndex 的 QAbstractItemModel 的成员函数(例如 QModelIndex::index() 和 QModelIndex::parent() )必须每次创建一个新的,使用 QAbstractItemModel::createIndex()。
最后,正如@eyllanesc 所说,除非数据是可编辑的,否则不会强制执行 setter QAbstractItemModel::setData( QModelIndex , value, role )。
【讨论】:
见doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html以上是关于QAbstractItemModel 和 QModelIndex 之间的相互作用的主要内容,如果未能解决你的问题,请参考以下文章
如何在 QAbstractItemModel 和 QTreeView 类中找到子项?
QAbstractItemModel:为啥在向模型中插入大量项目时发出 dataChanged 和插入行信号这么慢?
如果实际上没有插入,调用 QAbstractItemModel::beginInsertRows() 和 endInsertRows() 会有啥影响?
在 QAbstractItemModel 中返回正数行和零列是不是可以?