Michael Vlach Michael Vlach - 1 month ago 8
C++ Question

sourceModel()->createIndex() in QAbstractProxyModel sub-class

I am attempting to create a proxy model that dynamically maps items from source model.

Following the implementation of

QIdentityProxyModel
with the intention to go from there I have discovered that it is in fact impossible to replicate it by examining the 4 core functions:

mapFromSource()
mapToSource()
index()
parent()


Consider this based on
QIdentityProxyModel
:

mapFromSource()

QModelIndex ProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
if(sourceIndex.isValid())
return createIndex(sourceIndex.row(), sourceIndex.column(), sourceIndex.internalPointer());
else
return QModelIndex();
}


mapToSource()

QModelIndex ProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
if(proxyIndex.isValid())
return sourceModel()->createIndex(proxyIndex.row(), proxyIndex.column(), proxyIndex.internalPointer());
else
return QModelIndex();
}


index()

QModelIndex ProxyModel::index(int row, int column, const QModelIndex &parent) const
{
const QModelIndex sourceParent = mapToSource(parent);
const QModelIndex sourceIndex = sourceModel()->index(row, column, sourceParent);
return mapFromSource(sourceIndex);
}


parent()

QModelIndex ProxyModel::parent(const QModelIndex &index) const
{
const QModelIndex sourceIndex = mapToSource(index);
const QModelIndex sourceParent = sourceIndex.parent();
return mapFromSource(sourceParent);
}


THE ISSUE

Problem lies in
mapToSource()
line

return sourceModel()->createIndex(proxyIndex.row(), proxyIndex.column(), proxyIndex.internalPointer());


The
QAbstractItemModel::createIndex
is protected function and cannot be used unless the caller is declared friend. That is obviously not an option without modifying the
QAbstractItemModel
class directly. The only alternative is to use regular
QAbstractItemModel::index
but that requires parent in form of
QModelIndex
as one of the arguments. However doing this:

return sourceModel()->index(proxyIndex.row(), proxyIndex.column(), proxyIndex.parent());


causes an infinite loop due to the fact that
parent()
function relies on
mapToSource()
and vice versa now.

The alternative approach shown for example here relies on the stored map of
QPersistentModelIndex
objects that are queried in the above mentioned functions. This approach while viable has several disadvantages:


  • It requires a lots of extra memory to store the indexes

  • All persistent indexes need to be updated by the source model upon every structure change (as opposed to on demand index lookup with dynamic models)

  • Queries to the map may be slow when iterating over kyes of the map (this can be remedied by making two identical maps in reverse at the expense of yet more memory)



Hence my questions:

Is there another way to handle the hierarchical proxy model dynamically without relying on createIndex() and without running into infinite loop of function calls?

Is it in fact necessary to create and maintain the structure of the proxy model in a storage vis-à-vis the source structure or is there a way to create a dynamic proxy model?

Thanks!

UVV UVV
Answer

Perhaps this is a late reply, but I've just faced the same problem. I solved it by casting sourceModel() to my model, meanwhile I declared my ProxyModel as a friend.

This is my mapToSource definition:

QModelIndex ProxyModel::mapToSource(const QModelIndex& proxyIndex) const
{
   Model* pModel = qobject_cast<Model*>(sourceModel());
   if (!pModel || !proxyIndex.isValid()) return QModelIndex();

   ...
   return pModel->createIndex(row, col, proxyIndex.internalPointer());
}

and the friend declaration:

class Model : public QAbstractTableModel
{
   Q_OBJECT

   friend class ProxyModel;
   ...
};

I understand your concern about using only QAbstractItemModel interface (believe me, I had the same), but let's face it the ProxyModel can only be used with my Model and nothing else (at least according to my implementation). Therefore I don't see anything bad in "friendship" like this. As for UB there shouldn't be any issues here as well, because qobject_cast would return 0 if other model other than mine was provided.

Comments