Qt Commercial Support Weekly #10: Sorting, filtering and advanced manipulation with proxy models

In Qt there is a fairly powerful itemview framework which enables the implementation of model/view concepts.  As you probably know already there are two classes that are available to aid the sorting and filtering of model data.  These classes being QAbstractProxyModel and QSortFilterProxyModel.  The beauty of these classes is that they present the source model's data in a different way without manipulating the original data itself, so your original model will stay in the same state as it was originally in.  This is handy if you don't want to have to readjust the data where you got it from (such as a file or a source you have only read-only access to).

 

For the simple cases, such as general sorting and general filtering then QSortFilterProxyModel out of the box is sufficient enough for your needs as this provides basic sorting and filtering (as you can assume by the name).  So you can either call sort() to do a sort on the source model, or you can set a string/regexp that should be filtered on setFilterFixedString() or setFilterRegExp() or setFilterWildcard().  For the slightly more advanced usage you can reimplement the filterAccepts*() functions for custom filtering and lessThan() for custom sorting.  So if you just want to do a basic sort or filter on your model then this is the way to go.

 

However, when you need to do something more advanced then you need to subclass in order to provide the functionality that you need.  For example, you might want to only show certain elements in a tree hierarchy regardless of whether the parent would be visible or reorder the elements in such a way that they have different parents to what they have in the source model.  A common mistake when it comes to providing your own functionality in this respect is to subclass QSortFilterProxyModel because it provides all of the mapping from source to proxy indicies for you, whereas you would be better placed by subclassing QAbstractProxyModel instead.

 

It might look daunting to subclass QAbstractProxyModel because there are a number of pure virtual functions and it looks like you have to understand a lot of how models work behind the scenes in order to get this to work.  Granted this is partly true, but thanks to the help of a subclass I created for a previous blog post I did for Nokia (which is also available in the FAQs too via the customer portal) then it is easy to set up your own advanced functionality by adapting this to your own needs.

 class SortProxy : public QAbstractProxyModel
{
    Q_OBJECT
public:
    SortProxy(QObject *parent = 0) : QAbstractProxyModel(parent)
    {
        fixModel();
    }
    int rowCount(const QModelIndex &parent) const
    {
        QModelIndex sourceParent;
        if (parent.isValid())
            sourceParent = mapToSource(parent);
        int count = 0;
        QMapIterator<QPersistentModelIndex, QPersistentModelIndex> it(proxySourceParent);
        while (it.hasNext()) {
            it.next();
            if (it.value() == sourceParent)
                count++;
        }
        return count;
    }
    int columnCount(const QModelIndex &parent) const
    {
        return 1;      
    }
    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const
    {
        QModelIndex sourceParent;
        if (parent.isValid())
            sourceParent = mapToSource(parent);
        QMapIterator<QPersistentModelIndex, QPersistentModelIndex> it(proxySourceParent);
        while (it.hasNext()) {
            it.next();
            if (it.value() == sourceParent && it.key().row() == row &&
                it.key().column() == column)
                return it.key();
        }
        return QModelIndex();
   }
    QModelIndex parent(const QModelIndex &child) const
    {
        QModelIndex mi = proxySourceParent.value(child);
        if (mi.isValid())
            return mapFromSource(mi);
        return QModelIndex();
    }
    QModelIndex mapToSource(const QModelIndex &proxyIndex) const
    {
        if (!proxyIndex.isValid())
            return QModelIndex();
        return mapping.key(proxyIndex);
    }
    QModelIndex mapFromSource(const QModelIndex &sourceIndex) const
    {
        if (!sourceIndex.isValid())
            return QModelIndex();
        return mapping.value(sourceIndex);
    }
private:
    void fixModel()
    {
        mapping.clear();
        proxySourceParent.clear();
        for (int i=0;i<list.size();i++) {
            QStandardItem *si = list.at(i);
            QPersistentModelIndex proxy;
            QPersistentModelIndex sourceParent;
          proxy = createIndex(si->row(), si->column(), si->index().internalPointer());
            if (si->parent())
                sourceParent = si->parent()->index();
            mapping.insert(QPersistentModelIndex(si->index()), proxy);
            proxySourceParent.insert(proxy, sourceParent);
        }
    }
    QMap<QPersistentModelIndex, QPersistentModelIndex> mapping;
    QMap<QPersistentModelIndex, QPersistentModelIndex> proxySourceParent;
};
 

 

What the above gives you out of the box is a means to reorder the items however you see fit, the rowCount() function takes care of counting how many children the parent actually has so you don't need to change anything there at all.  columnCount() is hardcoded to 1 for convenience but you could adapt it in a similar way here too or have your own approach to how many columns you show.  The mapTo/FromSource functions again can be left as they are as they are using the two QMaps to do the necessary mapping and get the relevant information from there as does the parent() function.  The index() function is effectively doing the same, using the mapping to find the right proxy index as this was created inside the fixModel() function.  So these functions can be used out of the box without having to be changed for your own model.

 

The bulk of the actual setting up of how you want the model to appear is in the fixModel() function, this would be called whenever you want to cause a change.  Again for convenience I have used a list of QStandardItems which is basically all the items in the source model, you can use a list of QPersistentModelIndexes instead so that you can adapt it to your own model easily.  But what you do inside the fixModel function is to set up all the mappings to and from the source to the proxy model.  At the same time you map the proxy index to whatever it's source parent is.  This enables you to map to any other item inside the source model without worrying about whether there is a proxy index for the corresponding index at this point in time.  So if you wanted to reverse the order of items so that the grandchilds were on top and the top level parents were on the bottom then this would be possible to achieve.

To demonstrate that approach you would implement the fixModel() like this:

 void fixModel()
    {
        mapping.clear();
        proxySourceParent.clear();
        for (int i=0;i<list.size();i++) {
            QStandardItem *si = list.at(i);
            QPersistentModelIndex sourceParent;
            QPersistentModelIndex proxy;
            if (si->hasChildren()) {
                sourceParent = si->child(0)->index();
                proxy = createIndex(0, 0, sourceParent.internalPointer());
            } else {
                QStandardItem *parentItem = si;
                while (parentItem->parent() && parentItem->parent() != si->model()->invisibleRootItem())
                    parentItem = parentItem->parent();
                proxy = createIndex(parentItem->row(), 0, 0);
            }
            mapping.insert(QPersistentModelIndex(si->index()), proxy);
            proxySourceParent.insert(proxy, sourceParent);
        }
    }
 

While using QStandardItemModel does make it convenient in some aspects, it actually came with it's own pitfalls which I discovered when implementing the reverse hierarchy approach, in the original fixModel() approach it was using the internal pointer of the index to help with keeping things unique.  In QStandardItemModel this actually represents the parent item of the item itself, therefore I hit a problem when I was readjusting the top level items as they all shared the same parent item and since they were the first child item it meant that it ended up with the exact same model index for all of the top level items in the proxy model.  So this is something to watch out for when you are using createIndex() in your own proxy implementation, remember that you can always fall back to checking the source index's internal pointer if you need this information later on, so the proxy can have it's own unique means of identification.  You could use the QStandardItemModel's approach and just use a pointer that represents the source parent and this would be sufficient enough.  Generally however, all you need to do is replace those few lines to implement the behavior you want.  In fact, the if block inside that fixModel() implementation above is all you need to take out and replace.

 

And there you have it, some of you may remember this being used in a blog on the Nokia's Qt Labs blog and this is the case as I did something similar there in the past too, but I decided that it would be worth while revisiting it for the benefit of those who missed it the first time around and to provide again a means to achieve your own advanced manipulation of the source model in a fairly straightforward manner.  You can find a complete example that shows a different means of manipulating the model in the customer portal via the FAQs at https://na5.salesforce.com/50170000000ac17.

 

Of course, if you need further help on this then contact Qt Commercial Support as always via the customer portal - http://qt.digia.com/customerportal.


Blog Topics:

Comments