我有一个类用于永久存储某些项目,这些项目以表格方式组织。这个类与Qt完全无关,来自不同的库。让我们在本问题的其余部分调用此类DataContainer
。它提供了与std-c ++兼容的迭代器来访问和操作内容。
我需要通过Qt GUI显示和修改数据。我的想法是创建一个继承自DataContainerQtAdaptor
的类QAbstractTableModel
并存储指向DataContainer
对象的指针。 DataContainerQtAdaptor
充当DataContainer
对象的适配器,我Qt应用程序内部的所有操作都是通过此适配器完成的。然后我使用QTableView
小部件来显示信息。
不幸的是,线程/进程可能会更改DataContainer
。 (例如,将DataContainer
视为封装数据库连接的某个C ++类,并且该数据库可能被其他人更改。)
问题:
1)假设我每次更改DataContainer
对象的内部结构时都会调用一个函数。必须调用QAbstractTableModel
的正确函数是什么,以通知模型的潜在变化?我需要像#34;亲爱的模型,你的持久存储后端改变了。请更新自己并向每个附加的视图发出信号,以反映此更改"。
2)让我们说1)解决了。什么是避免" double"如果通过GUI触发更改,GUI更新?例如:用户点击表格小部件中的单元格 - >表格小部件调用模型的setData
- > model将更改推送到后端 - >后端触发自己的" onUpdate"功能 - > model重新读取完整的后端(虽然它已经知道了变化) - > GUI第二次更新
3)用户应该能够通过GUI插入新的行/列并将数据放入其中。但是这个位置是由这个数据决定的,因为后端会保持数据的排序。因此,我有以下问题:用户决定在结尾创建一个新行,并将新数据推送到后端。重新读取后端/模型时,此数据通常不在最后位置,但已插入中间某处,所有其他数据已向前移动。我是否保留了表视图小部件的所有属性,例如"选择一个单元格"同步?
我相信,所有这些问题必须有一些简单的标准解决方案,因为它与QFileSystemModel
的工作方式相同。用户选择文件,而其他一些进程创建新文件。新文件显示在视图中,所有后续行向前移动。选择也向前发展。
的Matthias
答案 0 :(得分:6)
首先,您必须确保QAbstractItemModel
不能处于不一致状态。这意味着在对基础数据进行某些更改之前,必须在模型上触发一些信号。
结构更改和数据更改之间存在根本区别。结构更改是要添加或删除的模型的行/列。数据更改仅影响现有数据项的值。
结构更改需要围绕修改调用beginXxx
和endXxx
。在调用beginXxx
之前,无法修改任何结构。当您完成更改结构后,请致电endXxx
。 Xxx
之一是:InsertColumns
,MoveColumns
,RemoveColumns
,InsertRows
,MoveRows
,RemoveRows
,ResetModel
如果更改影响了许多不连续的行/列,则表明模型重置的成本会更低 - 但要小心,视图上的选择可能无法生存。
保持结构完整性的数据更改仅需要在修改基础数据后发送 。这意味着,在查询模型的对象收到dataChanged
之前,对data
的调用可能会返回新值时会有一个时间窗口。
这也意味着非常量模型在非dataChanged
类中几乎无用,除非您使用观察者或类似模式实现桥接功能。
在模型上处理更新循环的Qt-idiomatic方法是利用项目角色。完全取决于您的模型如何解释角色。 QObject
实施的简单有用的行为只是将角色从QStringListModel
调用转发到setData
,否则忽略该角色。
股票视图小工具仅对dataChanged
dataChanged
作出反应。然而,当他们编辑数据时,他们会使用DisplayRole
来setData
。这打破了循环。该方法适用于查看窗口小部件和Qt快速查看项目。
只要模型在排序完成后正确发出变化信号,您就可以了。
操作顺序为:
视图会添加一行并调用模型的EditRole
方法。模型可以将此空行添加到底层容器中。关键是现在必须保留空行索引。
编辑从行中的项目开始。视图状态更改为insertRow
。
对项目进行编辑。视图退出编辑状态,并在模型上设置数据。
模型根据其内容确定项目的最终位置。
该模型调用Editing
。
模型通过将项目插入正确的位置来更改容器。
该模型调用beginMoveRows
。
此时,一切都如你所愿。如果移动的项目在移动之前被聚焦,则视图可以自动跟随移动的项目。默认情况下,编辑的项目会被聚焦,因此工作正常。
您的endMoveRows
没有足够的功能使其正常工作除非通过该模型完成对它的所有访问。如果要直接访问容器,请使容器明确继承DataContainer
,否则您必须向容器添加通知系统。前者是一个更容易的选择。
您的核心问题简化为:我是否可以在不实现模型通知API的某些变体的情况下拥有模型功能。显而易见的答案是:不,对不起,你不能 - 按照定义。功能是否存在,或者它不是。如果您不希望容器为QAbstractXxxxModel
,则可以使用观察者模式实现通知API - 然后您需要模型填充类。真的没办法解决它。
文件系统会通知QObject
有关已更改的各个目录条目。你的容器也必须这样做 - 这相当于提供某种形状或形式的QFileSystemModel
信号。如果模型包含移动或添加/删除的项目 - 其结构发生更改 - 则必须通过调用相关dataChanged
和{{1}来发出xxxAboutToBeYyy
和xxxYyy
信号方法。
beginZzz
最重要的未记录方面是:只要模型的结构没有改变,它的实例才有效。如果您的模型传递了一个在结构更改之前生成的索引,那么您可以自由地以不确定的方式行事(崩溃,发动核打击,无论如何)。
endZzz
存在的全部原因是您拥有一个基础的,复杂索引的数据容器的用例。您对模型QModelIndex
方法的实现必须生成以某种形式存储对QModelIndex::internalPointer()
索引的引用的索引实例。如果这些索引适合指针,则不需要在堆上分配数据。如果需要在堆上分配容器索引存储,则必须保留指向此数据的指针,并在容器的结构发生更改时将其删除。您可以自由地执行此操作,因为在结构更改后没有人应该使用索引实例。
答案 1 :(得分:2)
从方法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-beginRow)。
对于beginDeleteRows()
和beginMoveRows()
,它们是相同的。
当你有一个简单修改现有项目数据的方法时,这个方法必须在最后发出信号:dataChanged()
。
如果您对数据进行了大量更改,有时在执行此大修改的方法中调用beginResetModel()
和endResetModel()
会更简单。它将导致所有视图刷新其中的所有数据。
回答问题2:
如果它将“双重更新”,这取决于View类的实现。在视图中输入数据时,数据将通过模型中的一种编辑方法(insertRows()
,setData()
等)发送到模型。这些方法的默认实现始终使用begin*()
和end*()
方法,因此模型会发出正确的通知信号。所有视图都会侦听这些信号,包括用于输入数据的信号,因此将执行“双重更新”。
定义此行为的唯一方法是继承View并重新实现其受保护的插槽(如dataChanged()
等),以避免在检测到此视图提供的值时进行更新。
我不确定Qt视图是否已经这样做了。对此的回答需要有更多受过Qt内部教育的人,或者查看Qt源代码(目前我还没有)。如果有人知道这一点,请发表评论,我会更新答案。
我认为从模型重新加载数据并不是那么糟糕 - 它保证你所看到的确实是模型的价值。您可以避免编辑器和View错误可能出现的问题。
回答问题3:
当您重新加载整个模型时,没有简单的方法来跟踪选择。在这种情况下,您需要询问view->selectionModel()
当前选择并尝试在重新加载后恢复它。
但是,如果您进行部分刷新(使用我在答案1中描述的方法),则视图将为您跟踪选择。无需担心。
最终评论:
如果要编辑模型类外部的数据,可以执行此操作。只需将begin*()
和end*()
方法公开为公共API,因此编辑数据的其他代码可以通知模型和视图有关更改。
虽然可以做到,但这不是一个好习惯。它可能会导致错误,因为在修改数据的任何地方都很容易忘记调用通知。如果您必须调用模型API来通知有关更改,为什么还没有移动所有编辑代码来模拟模型并公开编辑API?