Model Semantics
First of all, you must ensure that the QAbstractItemModel
cannot be in an inconsistent state. This means that there are some signals that must be fired on the model before certain changes to the underlying data are done.
There is a fundamental difference between changes to structure and changes to data. Structure changes are the rows/columns of the model being added or removed. Data changes affect the value of existing data items only.
Structural changes require calling beginXxx
and endXxx
around the modification. You cannot modify any structure before calling beginXxx
. When you're done changing the structure, call endXxx
. Xxx
is one of: InsertColumns
, MoveColumns
, RemoveColumns
, InsertRows
, MoveRows
, RemoveRows
, ResetModel
.
If the changes affect many discontiguous rows/columns, it's cheaper to indicate a model reset - but be wary that selections on the views might not survive it.
Data changes that keep the structure intact merely require that dataChanged
is sent after the underlying data was modified. This means that there is a window of time when a call to data
might return a new value before dataChanged
is received by the object that queries the model.
This also implies that non-constant models are almost useless from non-QObject
classes, unless of course you implement bridge functionality using observer or similar patterns.
Breaking Update Loops
The Qt-idiomatic way of dealing with update loops on the model is by leveraging the item roles. It's entirely up to you how your model interprets the roles. A simple and useful behavior implemented by QStringListModel
is simply to forward the role from the setData
call to dataChanged
, otherwise ignoring the role.
The stock view widgets react only to dataChanged
with the DisplayRole
. Yet, when they edit the data, they call setData
with the EditRole
. This breaks the loop. The approach is applicable both to view widgets and to Qt Quick view items.
Insertion of Data into Sorted Models
As long as the model properly emits the change signals when the sorting is done, you'll be fine.
The sequence of operations is:
The view adds a row and calls model's insertRow
method. The model can either add this empty row to the underlying container or not. The key is that the empty row index must be kept for now.
The editing starts on an item in the row. The view state changes to Editing
.
Editing is done on the item. The view exits the editing state, and sets the data on the model.
The model determines the final position of the item, based on its contents.
The model invokes beginMoveRows
.
The model changes the container by inserting the item at the correct location.
The model invokes endMoveRows
.
At this point, everything is as you expect it to be. The views can automatically follow the moved item if it was focused prior to being moved. The edited items are focused by default, so that works fine.
Required Container Functionality
Your DataContainer
doesn't have enough functionality to make it work unless all access to it were to be done through the model. If you want to access the container directly, either make the container explicitly inherit QAbstractXxxxModel
, or you'll have to add a notification system to the container. The former is an easier option.
Your core question reduces to: can I have model functionality without implementing some variant of the model notification API. The obvious answer is: no, sorry, you can't - by definition. Either the functionality is there, or it isn't. You can implement the notification API using an observer pattern if you don't want the container to be a QObject
- then you'll need your model shim class. There's really no way around it.
The QFileSystemModel
gets notified by the filesystem about individual directory entries that have changed. Your container has to do the same - and this amounts to providing a dataChanged
signal, in some shape or form. If the model has items that get moved around or added/removed - its structure changes - it has to emit the xxxAboutToBeYyy
and xxxYyy
signals, by calling the relevant beginZzz
and endZzz
methods.
Indices
The most important underdocumented aspect of QModelIndex
is: its instances are only valid for as long as the model's structure hasn't changed. If your model is passed an index that was generated prior to a structure change, you're free to behave in an undefined way (crash, launch a nuclear strike, whatever).
The whole reason for the existence of QModelIndex::internalPointer()
is your use case of having an underlying, complex-indexed data container. Your implementation of the model's createIndex
method must generate index instances that store references to the DataContainer
's indices in some form. If those indices fit in a pointer, you don't need to allocate the data on the heap. If you need to allocate the container index storage on the heap, you must retain a pointer to this data and delete it any time the container's structure changes. You're free to do it, since nobody is supposed to use the index instance after a structure change.