Understanding Qt view-model architecture: when to create and how to cleanup indexes in QAbstractItemModel implementation?

StackOverflow https://stackoverflow.com/questions/18941445

Pergunta

I'm currently migrating my project from QTreeWidget to QtreeView, and have a lot of problems caused by poor understanding of the Qt model-view design. So far I couldn't find answers even in Qt examples.

I've implemented my QAbstractItemModel. I'm returning strings to be viewed in the QTreeView through data method. Now, the underlying data will change in runtime. To handle this my model is subscribed to a notification that does emit dataChanged(index(0,0), index(rowCount() - 1, LastColumn));. The question is: how to create and cleanup QModelIndex objects? One of the Qt examples reimplements index method, so I did the same:

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

However, data is static in that example and in my case it changes at runtime. Is my index implemetation correct? What if index is called more than once for the same coordinates? Do I need to somehow cleanup old indexes before emitting dataChanged?

Foi útil?

Solução

Your question about "deletion" of indices makes no sense in the light of the semantics of C++. There is simply no way for you to destroy an object that you returned by value from inside of a function - at least not without resorting to purposeful dirty hacks. So let's forget about it.

The dataChanged signal and the lifetime of indices are not really related. When your index() method returns an index, you are not the one who can "delete" it; whoever called your model's index() method is responsible for destructing the index. Never mind that the index you give out is not allocated in the free store anyway, so the notion of deletion doesn't apply at all.

The QModelIndex is what it says on the box: an index. When it comes to how it might be used, it's very much like a C++ iterator. It comes with a few caveats that mirror iterator caveats:

  1. It must be created by the model using a factory method index(). Internally you use the createIndex() factory to create it for you within the model. Think of iterator-returning methods of C++ containers do (begin(), end(), etc.).

  2. It must be used at once and then discarded. It won't remain valid if you make changes to the model. The same general limitation applies to C++ container iterators.

  3. If you need to keep a model index over time use a QPersistentModelIndex. The C++ standard library doesn't offer this.

The lifetime of an index is out of your control. You create it, you give it out with an expectation that it will be used according to this protocol. The user (the view, for example) is supposed to use it subject to the limitations listed above. If a view, for example, holds on to an index for too long (through intervening modifications), it's entirely OK that it will result in undefined behavior (say, a crash).

When you emit (or receive, if you're a view or a proxy model) dataChanged, you shouldn't expect any indices given out prior to that point to remain usable. The persistent indices of course should still work, but it's ok to invalidate those if, say, the pointed-to index was removed (think of a cell being removed from a spreadsheet, not the cell's data being changed!).

If you gave out an index, then emit dataChanged, and any of your model's methods get called with that old index, you're free to crash, assert, abort, whatever.

Let's also be clear about how you use dataChanged: you're supposed to emit it whenever an item of data at a given index changes. You should be as specific as possible: it is not a good idea at all to simply tell your views that everything has changed if, in fact, it hasn't. If one index has changed, emit the signal with topLeft and bottomRight set to the same index. If a small rectangular area has changed, emit the corners of this rectangle. If multiple unrelated items have changed that are too far away to be meaningfully bundled in a small enclosing index rectangle, you should indicate such changes separately for each changed item.

You should definitely use modeltest to verify that your model behaves sanely.

This can be done by adding the modeltest.cpp and modeltest.h to your project, and instantiating the tester for each model instance. You can do it directly within your model:

#include "modeltest.h"

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

You also need to handle persistent indices for your model, and that's a separate issue. The documentation says:

Models that provide interfaces to resizable data structures can provide implementations of insertRows(), removeRows(), insertColumns(),and removeColumns(). When implementing these functions, it is important to notify any connected views about changes to the model's dimensions both before and after they occur:

  • An insertRows() implementation must call beginInsertRows() before inserting new rows into the data structure, and endInsertRows() immediately afterwards.
  • An insertColumns() implementation must call beginInsertColumns() before inserting new columns into the data structure, and endInsertColumns() immediately afterwards.
  • A removeRows() implementation must call beginRemoveRows() before the rows are removed from the data structure, and endRemoveRows() immediately afterwards.
  • A removeColumns() implementation must call beginRemoveColumns() before the columns are removed from the data structure, and endRemoveColumns() immediately afterwards.

The private signals that these functions emit give attached components the chance to take action before any data becomes unavailable. The encapsulation of the insert and remove operations with these begin and end functions also enables the model to manage persistent model indexes correctly. If you want selections to be handled properly, you must ensure that you call these functions. If you insert or remove an item with children, you do not need to call these functions for the child items. In other words, the parent item will take care of its child items.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top