3

我目前正在将我的项目从 迁移QTreeWidgetQtreeView,并且由于对 Qt 模型视图设计的理解不足而导致了很多问题。到目前为止,即使在 Qt 示例中我也找不到答案。

我已经实现了我的QAbstractItemModel. QTreeView我正在通过data方法返回要查看的字符串。现在,底层数据将在运行时发生变化。为了处理这个问题,我的模型订阅了一个通知emit dataChanged(index(0,0), index(rowCount() - 1, LastColumn));。问题是:如何创建和清理QModelIndex对象?Qt 示例之一重新实现了index方法,所以我做了同样的事情:

QModelIndex CFileListModel::index(int row, int column, const QModelIndex &/*parent*/) const
{
    QModelIndex index = createIndex(row, column);
    return index;
}

但是,该示例中的数据是静态的,在我的情况下,它在运行时会发生变化。我的index实现是否正确?如果index多次调用相同的坐标怎么办?我需要在发射之前以某种方式清理旧索引dataChanged吗?

4

1 回答 1

4

鉴于 C++ 的语义,您关于“删除”索引的问题毫无意义。您根本无法销毁您从函数内部按值返回的对象 - 至少在不诉诸有目的的肮脏黑客的情况下不会。所以让我们忘记它。

指数的dataChanged信号和生命周期并不真正相关。当您的index()方法返回索引时,您不是可以“删除”它的人;调用模型index()方法的人负责破坏索引。没关系,您提供的索引无论如何都不会在免费存储中分配,因此删除的概念根本不适用。

QModelIndex就是它在盒子上写的:一个索引。谈到如何使用它时,它非常像 C++ 迭代器。它带有一些反映迭代器警告的警告:

  1. 它必须由模型使用工厂方法创建index()。在内部,您使用createIndex()工厂在模型中为您创建它。想想 C++ 容器的迭代器返回方法(begin()、、end()等)。

  2. 它必须立即使用然后丢弃。如果您对模型进行更改,它将不再有效。相同的一般限制适用于 C++ 容器迭代器。

  3. 如果您需要随着时间的推移保留模型索引,请使用QPersistentModelIndex. C++ 标准库不提供此功能。

索引的生命周期不在您的控制范围内。你创建它,你把它分发出去,期望它会根据这个协议被使用。用户(例如视图)应该根据上面列出的限制使用它。例如,如果视图保持索引的时间过长(通过干预修改),那么它会导致未定义的行为(例如崩溃)是完全可以的。

当您发出(或接收,如果您是视图或代理模型)dataChanged时,您不应期望在该点之前给出的任何索引仍然可用。持久性索引当然应该仍然有效,但是如果删除了指向的索引(考虑从电子表格中删除的单元格,而不是更改单元格的数据!),则可以使它们无效。

如果你给出一个索引,然后发出dataChanged,并且你的模型的任何方法都被那个旧索引调用,你可以自由地崩溃、断言、中止,等等。

让我们也清楚你是如何使用的dataChanged:只要给定索引处的数据项发生变化,你就应该发出它。你应该尽可能具体:简单地告诉你的观点,如果事实上一切都没有改变,那根本不是一个好主意。如果一个索引已更改,则发出信号topLeftbottomRight设置为相同的索引。如果一个小的矩形区域发生了变化,发射这个矩形的角。如果多个不相关的项目发生了变化,并且距离太远而无法有意义地捆绑在一个小的封闭索引矩形中,则您应该为每个已更改的项目单独指示此类更改。

您绝对应该使用modeltest来验证您的模型是否正常运行。

这可以通过将modeltest.cpp和添加modeltest.h到您的项目,并为每个模型实例实例化测试器来完成。您可以直接在模型中执行此操作:

#include "modeltest.h"

MyModel(QObject * parent) : ... {
   new ModelTest(this, parent);
   ...
}

您还需要处理模型的持久索引,这是一个单独的问题。文档说:

为可调整大小的数据结构提供接口的模型可以提供 insertRows()、removeRows()、insertColumns() 和 removeColumns() 的实现。在实现这些功能时,重要的是要通知任何连接的视图在模型尺寸发生之前和之后发生的变化:

  • insertRows() 实现必须在将新行插入数据结构之前调用 beginInsertRows(),然后立即调用 endInsertRows()。
  • insertColumns() 实现必须在将新列插入数据结构之前调用 beginInsertColumns(),然后立即调用 endInsertColumns()。
  • removeRows() 实现必须在从数据结构中删除行之前调用 beginRemoveRows(),然后立即调用 endRemoveRows()。
  • removeColumns() 实现必须在从数据结构中删除列之前调用 beginRemoveColumns(),然后立即调用 endRemoveColumns()。

这些函数发出的私有信号使附加组件有机会在任何数据变得不可用之前采取行动。使用这些 begin 和 end 函数封装插入和删除操作还使模型能够正确管理持久模型索引。如果您希望正确处理选择,则必须确保调用这些函数。如果插入或删除带有子项的项,则无需为子项调用这些函数。换句话说,父项将处理其子项。

于 2013-09-22T11:36:37.650 回答