PDA

View Full Version : My subclass of QAbstractProxyModel does not handle remove/insert items correctly



dgermann
31st May 2013, 10:54
Hello

I have some issues with updating a QTreeView correctly when the model, connected to the view through one or two proxy models, inserts or removes items.

The overall purpose is to have a view on the model that can switch between showing the model as a tree structure and as a flat structure (showing all items, leaves and internal nodes, in a table). It should also be able to sort both views. The model itself has a tree structure (which should not actually be converted into a flat structure).

My implementation uses the following classes:

Model, which is derived from QAbstractItemModel and provides a tree-structured model
Viewer, which is derived from QTreeView and looks onto the model
ViewerProxyModel, which is derived from QSortFilterProxyModel
TreeToTableProxyModel, which translates the tree structure of the model to a flat structure


The Viewer always looks onto an instance of ViewerProxyModel, which in turn either directly looks onto an instance of Model, or, if a flat hierarchy is desired, first onto an instance of TreeToTableProxyModel, which finally looks onto an instance of Model.

Now, my problem is that in the case where the TreeToTableProxyModel is used, the Viewer does not update properly if items are inserted into or removed from the Model instance. To handle these updates, I implemented the following methods:



void TreeToTableProxyModel::sourceRowsAboutToBeInserted (const QModelIndex& aParent, int aStart, int aEnd)
{
if (sourceModel())
{
QModelIndex modelStartIndex = sourceModel()->index(aStart, 0, aParent);
QModelIndex modelEndIndex = sourceModel()->index(aEnd, 0, aParent);
QModelIndex proxyStartIndex = mapFromSource(modelStartIndex);
QModelIndex proxyEndIndex = mapFromSource(modelEndIndex);

beginInsertRows(proxyStartIndex.parent(), proxyStartIndex.row(), proxyEndIndex.row());
//beginResetModel();
}
}

void TreeToTableProxyModel::sourceRowsAboutToBeRemoved( const QModelIndex& aParent, int aStart, int aEnd)
{
if (sourceModel())
{
QModelIndex modelStartIndex = sourceModel()->index(aStart, 0, aParent);
QModelIndex modelEndIndex = sourceModel()->index(aEnd, 0, aParent);
QModelIndex proxyStartIndex = mapFromSource(modelStartIndex);
QModelIndex proxyEndIndex = mapFromSource(modelEndIndex);

beginRemoveRows(proxyStartIndex.parent(), proxyStartIndex.row(), proxyEndIndex.row());
//beginResetModel();
}
}

void TreeToTableProxyModel::sourceRowsInserted(const QModelIndex& aParent, int aStart, int aEnd)
{
if (sourceModel())
{
//emit layoutAboutToBeChanged();
mMapFromSource.clear();
mMapToSource.clear();
createMap(...);
//emit layoutChanged();

endInsertRows();
//endResetModel();
}
}

void TreeToTableProxyModel::sourceRowsRemoved(const QModelIndex& aParent, int aStart, int aEnd)
{
if (sourceModel())
{
//emit layoutAboutToBeChanged();
mMapFromSource.clear();
mMapToSource.clear();
createMap(...);
//emit layoutChanged();

endRemoveRows();
//endResetModel();
}
}


which are connected to the appropriate signals of the Model instance:


connect(sourceModel(), SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)), this, SLOT(sourceRowsAboutToBeInserted(const QModelIndex&, int, int)));

etc. in the setSourceModel() method of TreeToTableProxyModel.

My problem is that I need to recompute the mapping of the TreeToTableProxyModel if items are inserted or removed. But when recomputing the mapping, I should probably emit layoutAboutToBeChanged() and layoutChanged(). This has to be done between beginInsertRows() and endInsertRows() or beginRemoveRows() and endRemoveRows(), which probably interferes with each other. Anyway, this solution does not update the Viewer properly. I did also test the variant without beginInsertRows()... and layoutChanged()... and directly using beginResetModel() and endResetModel(). This works almost fine. The only problem here (apart from the fact that it may be an overkill to reset the whole model) is that the sorting is not completely restored. If I first sort by column A and then by column B, items with the same value in column B remain sorted according to A. This secondary sorting is lost once the model has been reset.

I suppose I could use the beginReset() and endReset() variant and manually sort again by the correct columns. But this does not seem like the proper solution. How should I implement this?

pkj
3rd June 2013, 17:08
QAbstractProxyModel work with QPersistentModelIndex to create internal maps of items. I am assuming your TreeToTableProxyModel is QAbstractProxyModel inherited and not QSortFilterProxyModel.
Given your problem why don't you make your original model class run in two modes. Tree mode and flat mode. Define your index() and parent() functions according to the mode set. While changing the mode do a model reset.

dgermann
5th June 2013, 08:13
@pkj: Thank you. Yes, TreeToTableProxyModel is derived from QAbstractProxyModel. Sorry, I forgot this piece of information.

Since the original model is already quite big, I would rather not expand it by even more functionality. But you are right, that relinking models may not be very elegant. Instead of your solution, I may make TreeToTableProxyModel subclass QIdentityProxyModel and implement a switch between flat and tree mode there.

However, when I replace the QAbstractProxyModel by QIdentityProxyModel in TreeToTableProxyModel, I still get the same kind of error: the Viewer is not correctly updated, at least the secondary sorting gets lost. The basic question I have is quite general: when writing my own proxy model, how am I supposed to handle insertions and removals of items in the original model? The problem is that upon insertion/removal I need to recompute (at least a part of) the mapping, which in turn invalidates the layout of the items, which influences the sorting that the Viewer (or the ViewerProxyModel) is supposed to maintain.

pkj
5th June 2013, 13:23
The basic question I have is quite general: when writing my own proxy model, how am I supposed to handle insertions and removals of items in the original model? The problem is that upon insertion/removal I need to recompute (at least a part of) the mapping, which in turn invalidates the layout of the items, which influences the sorting that the Viewer (or the ViewerProxyModel) is supposed to maintain.
If you are inheriting from QSortFilterProxyModel or QIdentityProxyModel and you don't reimplement, parent(), index() functions, you don't need to do anything(notice you can't do that anyway!!). Those classes maintains the mapping through QPersistentModelIndexes. Open the code of the classes to see for yourself how the mappings are preserved to come up with something similar. What you really want is to rewrite index() and parent() functions since you need to flatten the tree up. Here, the functionality provided by these classes won't help as they essentially just change either the display by data() function or hide a few items. That is their intended use.

If you insist on having a new model, maintain a hash of treeItemObject and qpersistentmodelindex. Use it to arrive at index, parent and data functions. You can see the code QSortFilterProxyModel to get your ideas straight. And your new model cannot be a subclass of QAbstractProxyModel but only another QAbstractItemModel since you have to rewrite the index() and parent() functions.

dgermann
5th June 2013, 15:24
Well, I have already implemented those methods in TreeToTableProxyModel (setSourceModel(), mapFromSource(), mapToSource(), columnCount(), data(), index(), parent() and rowCount()). I get a flat view on the model, just as I want it. Therefore, as far as I can tell, the mapping works fine. My problem is how to consistently update the mapping -- and how to send the correct signals in the correct order such that also the QSortFilterProxyModel and the Viewer update correctly --, once rows are inserted to or removed from the original model.