了解Qt视图模型架构:何时创建以及如何在QAbstractItemModel实现中清理索引?

时间:2013-09-22 07:31:25

标签: c++ qt qtreeview qabstractitemmodel

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

我已经实现了QAbstractItemModel。我正在返回要在QTreeViewdata方法中查看的字符串。现在,底层数据将在运行时更改。为了解决这个问题,我的模型订阅了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之前,我是否需要以某种方式清理旧索引?

1 个答案:

答案 0 :(得分:4)

您关于"删除"的问题根据C ++的语义,索引没有任何意义。你根本没有办法销毁一个你从函数内部返回值的对象 - 至少在没有诉诸目的的肮脏黑客的情况下。所以,让我们忘记吧。

dataChanged信号与指数的生命周期并不真正相关。当您的index()方法返回索引时,您不是能够删除"它;无论谁调用你的模型的index()方法,都有责任破坏索引。没关系,你提供的索引无论如何也没有在免费商店中分配,因此删除的概念根本不适用。

QModelIndex就是它所说的:一个索引。当谈到如何使用它时,它非常像C ++迭代器。它附带了一些反映迭代器警告的警告:

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

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

  3. 如果您需要保留模型索引,请使用QPersistentModelIndex。 C ++标准库没有提供此功能。

  4. 索引的生命周期超出您的控制范围。你创建它,你给它出一个期望它将根据这个协议使用。用户(例如视图)应该使用它,但受上面列出的限制。例如,如果一个视图持有一个索引太长时间(通过干预修改),那么它将导致未定义的行为(比如崩溃)就完全可以了。

    当您发出(或接收,如果您是视图或代理模型)dataChanged时,您不应期望在该点之前发出的任何索引仍然可用。当然,持久性索引仍然可以正常工作,但如果删除了指向索引,那么可以使这些索引无效(想想从电子表格中删除的单元格,小区的数据正在改变!)。

    如果您提供了一个索引,然后发出dataChanged,并且您的模型的任何方法都会使用旧索引进行调用,那么您就是自由崩溃,断言,中止,等等。

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

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

    这可以通过将modeltest.cppmodeltest.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()。
    •   
         

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