我有一个类,用于永久存储一些以表格方式组织的项目。这个类与Qt完全无关,来自不同的库。让我们为这个问题的其余部分调用这个类DataContainer
。它提供了std-c++兼容的迭代器来访问和操作内容。
我需要通过Qt来显示和修改这些数据。我的想法是创建一个类DataContainerQtAdaptor
,该类继承自QAbstractTableModel
,并存储指向DataContainer
对象的指针。DataContainerQtAdaptor
充当DataContainer
对象的适配器,我的Qt应用程序内部的所有操作都是通过这个适配器完成的。然后我使用一个QTableView
小部件来显示信息。
不幸的是,DataContainer
可能被线程/进程更改。(例如,将DataContainer
看作封装数据库连接的某个C++类,而该数据库可能被其他人更改)。
问题:
1)假设我有一个函数,每次更改DataContainer
对象的内部结构时都调用它。必须调用的QAbstractTableModel
的正确功能是什么,以通知底层更改的模型?我需要“亲爱的模型,您的持久存储后端更改。请,更新自己,并发出信号给每个附加的视图,以反映这一变化”。
( 2)假设1)解。在通过GUI触发更改时,避免“双”GUI更新的最佳方法是什么?例如:用户单击表小部件->表小部件中的一个单元格,调用模型的setData
->模型将更改推送到后端->后端触发它自己的"onUpdate“函数->模型重新读取完整的后端(尽管它已经知道了更改) -> GUI再次更新。
3)用户应该能够通过GUI插入新的行/列并将数据放入其中。但是位置是由这些数据决定的,因为后端保持数据的排序。因此,我有以下问题:用户决定在末尾创建一个新行,并将新数据推送到后端。当重新读取后端/模型时,数据通常不在最后一个位置,而是插入到中间的某个位置,所有其他数据都已向前移动。我是否将表视图小部件的所有属性保持同步,比如“选择一个单元格”?
我相信,必须有一些简单的标准解决方案来解决所有这些问题,因为它与QFileSystemModel
的工作方式相同。用户选择一个文件,其他一些进程创建一个新文件。新文件将显示在视图中,随后的所有行都向前移动。选择也向前推进。
马提亚斯
发布于 2014-01-31 10:46:08
模型语义
首先,您必须确保QAbstractItemModel
不能处于不一致的状态。这意味着在对底层数据进行某些更改之前,必须在模型上触发一些信号。
对结构的更改与对数据的更改有根本的区别。结构更改是要添加或删除模型的行/列。数据更改仅影响现有数据项的值。
beginXxx
和endXxx
。在调用beginXxx
之前,不能修改任何结构。当您完成结构更改后,请调用endXxx
。Xxx
是InsertColumns
,MoveColumns
,RemoveColumns
,InsertRows
,MoveRows
,RemoveRows
,ResetModel
之一。
如果更改影响到许多不连续的行/列,则表示模型重置成本更低--但要小心,视图上的选择可能无法存活。dataChanged
。这意味着对data
的调用可能会在查询模型的对象接收到dataChanged
之前返回一个新值。这也意味着非常量模型对非QObject
类几乎毫无用处,当然,除非您使用观察者或类似的模式实现桥功能。
断续更新环
在模型上处理update循环的Qt-惯用方法是利用项角色。这完全取决于你的模型如何解释角色。QStringListModel
实现的一个简单而有用的行为就是将角色从setData
调用转发到dataChanged
,否则忽略角色。
股票视图小部件只对dataChanged
和DisplayRole
作出反应。然而,当他们编辑数据时,他们用EditRole
调用EditRole
。这就打破了循环。这种方法既适用于查看小部件,也适用于Qt快速视图项。
将数据插入排序模型
只要模型在完成排序时正确地发出更改信号,您就可以了。
行动顺序如下:
insertRow
方法。模型可以将空行添加到基础容器中,也可以不添加。关键是必须暂时保留空行索引。Editing
。beginMoveRows
。endMoveRows
。在这一点上,一切都如你所料。视图可以自动跟踪被移动的项目,如果它在被移动之前被聚焦。在默认情况下,编辑的项都是集中的,因此工作正常。
必需的容器功能
您的DataContainer
没有足够的功能使其工作,除非所有对它的访问都是通过模型完成的。如果您想直接访问容器,那么要么让容器显式继承QAbstractXxxxModel
,要么向容器添加通知系统。前者是一个更容易的选择。
您的核心问题归结为:我可以在不实现模型通知API的某些变体的情况下拥有模型功能吗?显而易见的答案是:不,对不起,你不能-从定义上来说。如果您不希望容器是一个QObject
,那么您将需要您的模型shim。没有办法可以绕过它。
文件系统通知QFileSystemModel
有关已更改的各个目录条目。您的容器也必须这样做--这相当于提供某种形状或形式的dataChanged
信号。如果模型中有被移动或添加/删除的项--其结构更改--则必须通过调用相关的xxxAboutToBeYyy
和endZzz
方法来发出beginZzz
和endZzz
信号。
索引
QModelIndex
最重要的未得到说明的方面是:它的实例只有在模型的结构没有改变的情况下才有效。如果您的模型通过了一个在结构更改之前生成的索引,您就可以自由地以一种未定义的方式进行操作(崩溃、启动核打击等)。
QModelIndex::internalPointer()
存在的全部原因是您有一个底层的、复杂的索引数据容器的用例。模型的createIndex
方法的实现必须生成以某种形式存储对DataContainer
索引的引用的索引实例。如果这些索引适合指针,则不需要分配堆上的数据。如果需要在堆上分配容器索引存储,则必须保留指向该数据的指针,并在容器的结构更改时随时删除它。您可以自由地这样做,因为在结构更改后,没有人应该使用索引实例。
发布于 2014-01-31 03:47:20
从方法bool QAbstractItemModel::insertRows(int row, int count, const QModelIndex & parent = QModelIndex())
的文档
如果您实现自己的模型,如果希望支持插入,则可以重新实现此函数。或者,您可以提供自己的API来修改数据。在这两种情况下,您都需要调用beginInsertRows()和endInsertRows()来通知其他组件模型已经更改。
removeRows()
和moveRows()
也是如此(他们有自己的begin*()
和end*()
方法)。为了修改现有项目的数据,有一个dataChanged()
信号。
是这样的(问题1的答案):
实现您自己的插入/删除/修改数据的方法,其中每个方法必须如下所示:
beginInsertRows(parentIndex, beginRow, endRow);
// code that modifies underlying data
endInsertRows();
必须提供beginRow
和endRow
,以通知将插入哪些行以及其中的行数(endRow- where )。
对于beginDeleteRows()
和beginMoveRows()
来说,情况是一样的。
当您有一个简单地修改现有项中的数据的方法时,该方法必须在末尾发出信号:dataChanged()
。
如果您对数据进行了大量更改,有时只需在执行此巨大修改的方法中调用beginResetModel()
和endResetModel()
就更简单了。它将导致所有视图刷新其中的所有数据。
对问题2的回答:
这取决于视图类实现,如果它将“双更新”。当您在视图中输入数据时,数据将通过模型中的一个版本方法(insertRows()
、setData()
等)发送到模型。这些方法的默认实现总是使用begin*()
和end*()
方法,因此模型会发出适当的通知信号。所有视图都会监听这些信号,包括输入数据的信号,因此将执行“双更新”。
定义此行为的唯一方法是继承视图并重新实现其受保护的插槽(如dataChanged()
和类似的),以避免在检测到该视图提供的值时进行更新。
我不确定Qt视图是否已经这样做了。要想解决这个问题,需要有人在Qt内部有更多的知识,或者研究Qt源代码(我目前还没有)。如果有人知道这一点,请评论,我会更新答案。
我认为从模型中重新加载数据并没有那么糟糕--它保证了您所看到的确实是来自模型的值。您可以避免编辑器和视图错误可能出现的问题。
对问题3的回答:
当你重新加载整个模型,那么就没有简单的方法来跟踪选择。在这种情况下,您需要询问view->selectionModel()
当前选择的情况,并在重新加载之后尝试恢复它。
但是,如果您执行部分刷新(使用我在答案1中描述的方法),则视图将为您跟踪所选内容。没什么好担心的。
最后评论:
如果您想编辑来自模型类外部的数据,您可以这样做。只要将begin*()
和end*()
方法公开为公共API,其他编辑数据的代码就可以通知模型和视图更改。
虽然这是可以做到的,但这不是一个好的做法。这可能会导致错误,因为无论您修改数据,都很容易忘记调用通知。如果您必须调用model来通知更改,为什么不将所有编辑代码移到模型内部并公开编辑API呢?
https://stackoverflow.com/questions/21478746
复制