ImaginativeThinking.ca


A developers blog

How to use Qt’s QSortFilterProxyModel

By: Brad

In this tutorial I’m going to show you how to use Qt’s QSortFilterProxyModel to only present a subset of data to the presentation layer.

In Qt Quick you have a View and a Data Model where the Data Model holds the data that the View will present to the user. Your Data Model may hold data that needs to be presented in a number of different ways. The different presentation views may only need to show a subset of that data; In order to achieve this we need to present each View with a subset of the contents of the Data Model. We can do this via a Proxy Model such as the QSortFilterProxyModel.

In Qt there are classes called models which implement interfaces that allows them to satisfy all the hooks required by the framework so that views can query them for data and so they can be notified when that data changes. The base class for all models that exist in Qt is called the QAbstractItemModel, this model class has all the required methods and their base implementation satisfies the minimum required to allow views to hook up to them. Where the QAbstractItemModel is used as a base class for the sole purpose of presenting a collection of data to the presentation layer it is also inherited by a specialized class called the QAbstractProxyModel whose sole role is to allow the data to be manipulated without affecting the source data model; this way the source data model can be used by any number of different proxy modes and each proxy model can manipulate the data in different ways without effecting each other. The QAbstractProxyModel as the name implies is a general base class with just enough implementation to satisfy the requirements of allowing the data to be manipulated without effecting the source. Qt has a specialized class which inherits from the QAbstractProxyModel class called the QSortFilterProxyModel which can be used to sort and/or filter the data in the source model without effecting the source model it self. By having your view access a QSortFilterProxyModel instead of the QAbstractItemModel directly you can present your view with a subset of the data actually in the source data model.

Lets get started with some code shall we

First create a new Qt Quick Application (*.pro) project, remember this is a type of project that can have both C++ and QML. Call your new project QSortFilterProxyModel_Sample1.

First lets add a new C++ class called MyViewModel which we’ll use as our source data model. Have it extend the QStandardItemModel (which is a specialized version of the QAbstractItemModel) and define an enum that will specify some custom user roles. We’ll also have to override the virtual method roleNames so that we can define the reference keywords that the View can use to access each one of these custom roles.

#include <QStandardItemModel>
#include <QObject>

class MyViewModel : public QStandardItemModel {
    Q_OBJECT
public:
    enum MyViewModel_Roles
    {
        MyViewModel_Roles_Display = Qt::UserRole + 1,   /**< This role holds the display string for an entry in the model. */
        MyViewModel_Roles_Details,                      /**< This role holds the details string for an entry in the model. **/
        MyViewModel_Roles_KeyId,                        /**< This role holds the key id for an entry in the model. **/
        MyViewModel_Roles_Value,                        /**< This role holds the value for an entry in the model. **/
    };

    virtual QHash<int,QByteArray> roleNames() const {
        QHash<int, QByteArray> roles;
        roles[MyViewModel_Roles_Display] = "role_display";
        roles[MyViewModel_Roles_Details] = "role_details";
        roles[MyViewModel_Roles_KeyId] = "role_keyid";
        roles[MyViewModel_Roles_Value] = "role_value";
        return roles;
    }
}

In the constructor lets populate this model with some dummy data for use in this tutorial. Lets define 12 entries 4 will have a value assigned to the MyViewModel_Roles_Display role, 5 will have a value assigned to the MyViewModel_Roles_Details role, 3 will have a value assigned to the MyViewModel_Roles_KeyId role, and all will have a value assigned to the MyViewModel_Roles_Value role where half will have an even value and half will have an odd value.

// invisibleRootItem is part of the QStandardItemModel and is a simply way of getting the root of the model.
auto root = this->invisibleRootItem();     

// Create a new entry for the model
QStandardItem* entry = new QStandardItem();          
entry->setData("One", MyViewModel_Roles_Display );
entry->setData(0, MyViewModel_Roles_Value );
root->appendRow(entry);    // Append it to the root (this will be entry #1 at index/row zero (0) )

entry = new QStandardItem();
entry->setData("One", MyViewModel_Roles_Display );
entry->setData(2, MyViewModel_Roles_Value );
root->appendRow(entry); // Index/Row 1

entry = new QStandardItem();
entry->setData("One", MyViewModel_Roles_Display );
entry->setData(3, MyViewModel_Roles_Value );
root->appendRow(entry); // Index/Row 2

entry = new QStandardItem();
entry->setData("One", MyViewModel_Roles_Display );
entry->setData(4, MyViewModel_Roles_Value );
root->appendRow(entry); // Index/Row 3

entry = new QStandardItem();
entry->setData("Two", MyViewModel_Roles_Details );
entry->setData(5, MyViewModel_Roles_Value );
root->appendRow(entry); // Index/Row 4

entry = new QStandardItem();
entry->setData("Three", MyViewModel_Roles_Details );
entry->setData(6, MyViewModel_Roles_Value );
root->appendRow(entry); // Index/Row 5

entry = new QStandardItem();
entry->setData("Four", MyViewModel_Roles_Details );
entry->setData(7, MyViewModel_Roles_Value );
root->appendRow(entry); // Index/Row 6

entry = new QStandardItem();
entry->setData("Five", MyViewModel_Roles_Details );
entry->setData(8, MyViewModel_Roles_Value );
root->appendRow(entry); // Index/Row 7

entry = new QStandardItem();
entry->setData("Six", MyViewModel_Roles_Details );
entry->setData(9, MyViewModel_Roles_Value );
root->appendRow(entry); // Index/Row 8

entry = new QStandardItem();
entry->setData("Seven", MyViewModel_Roles_KeyId );
entry->setData(10, MyViewModel_Roles_Value );
root->appendRow(entry); // Index/Row 9

entry = new QStandardItem();
entry->setData("Eight", MyViewModel_Roles_KeyId );
entry->setData(11, MyViewModel_Roles_Value );
root->appendRow(entry); // Index/Row 10

entry = new QStandardItem();
entry->setData("hello", MyViewModel_Roles_KeyId );
entry->setData(12, MyViewModel_Roles_Value );
root->appendRow(entry); // Index/Row 11

In the main.cpp file we need to instantiate an instance of this class and assign it to the root context so that the view can access it. Note to add a root context you need to include the QQmlContext header.

#include <QApplication>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include "MyViewModel.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    // Create an instance of our ViewModel; this holds the total list of data
    auto vm = new MyViewModel();

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("MyModel", vm );
    engine.load(QUrl(QStringLiteral("qrc:///main.qml")));

    return app.exec();
}

Now that we have a model lets create a view to show it in. In the main.qml file that gets auto generated per the Qt Quick Application project template add the following ListView component to show the data stored in the model.

Item {
    id: root
    anchors.fill: parent

    ListView {
        anchors.fill: parent
        model: MyModel
        delegate: myDelegate
    }
    Component {
        id: myDelegate
        Rectangle {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 25
            color: "green"
            border.color: "black"

            Text {
                text: role_value
                anchors.centerIn: parent
            }
        }
    }
}

If you run this you will get the following list of all 12 entries showing the data stored in each entries MyViewModel_Roles_Value role (or attribute if you prefer to think of it that way).

QSortFilterProxyModel_Sample1

Now lets say we only want to show the entries who have the Details role value set, how would we do that?

Commercial break...


Enter the QSortFilterProxyModel

In the main.cpp file lets instantiate a new instance of the QSortFilterProxyModel and set it to filter out items that don't have a MyViewModel_Roles_Details attribute with some value (if the entry does not have that attribute its value will be blank).

#include <QApplication>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include "MyViewModel.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    // Create an instance of our ViewModel; this holds the total list of data
    auto vm = new MyViewModel();

    auto detailsProxyModel = new QSortFilterProxyModel();
    detailsProxyModel->setFilterRole( MyViewModel::MyViewModel_Roles::MyViewModel_Roles_Details);
    detailsProxyModel->setFilterRegExp( "^\\S+$" );
    detailsProxyModel->setSourceModel( vm );

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("MyModel", vm );
    engine.rootContext()->setContextProperty("DetailsRoleModel", detailsProxyModel );
    engine.load(QUrl(QStringLiteral("qrc:///main.qml")));

    return app.exec();
}

Notice here that we use the QSortFilterPorxyModel method setFilterRole() to tell the proxy what value to use when check if the entry should be included in the proxy and we use the setFilterRegExp() method to tell the proxy how to evaluate the value. So above we are saying that for every entry found in the source model look at the value it has stored in the MyViewModel_Roles_Details role and check if it passes the given regular expression; if so then include it in the proxy model, if not then ignore it (like it doesn't exist).

To visualize it below is a diagram of what our MyViewModel class looks like when fully populated:

QSortFilterProxyModel_12EntryModelDiagram

And here is what the detailsProxyModel instance looks like after we set its source model:
QSortFilterProxyModel_DetailsProxyModelDiagram

You can see here that Entries 1-4 and Entries 10-12 when the proxy model asks them for the value they have in the MyViewModel_Roles_Details role they will return a blank string (because they don't have that role defined) which does not satisfy the regular expression "^\\S+$" that we told the proxy model to use when evaluating entries. As a result the proxy model will skip them. If I were to ask the MyViewModel object for its row count it will return 12 however the detailsProxyModel will return only 5 because it is ignoring all entries which don't satisfy the set regular expression. The base implementations in the QSortFilterProxyModel for the parent(), index(), rowCount(), columnCount(), mapToSource(), mapFromSource(), data(), setData(), etc. will all ignore any entries that don't satisfy the filter. So if you ask the MyViewModel object to get you the entry at row 0 it will return Entry 1 however if you ask the detailsProxyModel for the entry at row 0 it will return you Entry 5.

Now that we have a proxy model which only presents the view with the entries who have a value in the MyViewModel_Roles_Details role we can update our view to display that value (we will no longer get blank boxes because all entries in the proxy model will have a value).

Item {
    id: root
    anchors.fill: parent

    ListView {
        id: detailsListView
        anchors.fill: parent
        model: DetailsRoleModel
        delegate: detailsDelegate
    }
    Component {
        id: detailsDelegate
        Rectangle {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 25
            color: "yellow"
            border.color: "black"

            Text {
                text: role_details
                anchors.centerIn: parent
            }
        }
    }
}

QSortFilterProxyModel_Sample1_Details

That's great and all but what if my filter if more elaborate then a regular expression?

Out of the box the QSortFilterProxyModel can filter roles or columns and can evaluate the data based on a string pattern, regular expressions, or wildcard characters. If however your filter is more elaborate, perhaps you need to check two values (i.e. two different roles like show me entries who have the MyViewModel_Roles_Details role and have an even MyViewModel_Roles_Value role) your going to have to start overriding the base implementation.

Enter the filterAcceptsRow() Virtual Method

The QSortFilterProxyModel exposes a virtual method called filterAcceptsRow() whose base implementation checks the set column or role against the given filter (string, wildcard, regular expression). This method is a predicate which simply asks for a yes or no response of should I include this row in my proxy model; If the method returns false the proxy model will ignore the entry. You can override this method and replace it with your own implementation as long as your implementation returns a simply yes or no (include or not to include).

Commercial break...


Lets give it a shot shall we

Lets say that we now want to show a view that only has the entries which have a even number in the MyViewModel_Roles_Value role and that we can't achieve this with a regular expression (lets just say for sake of argument). To do this then we must override the filterAcceptsRow() predicate.

First add a new class to the project called MyProxyModel_EvenEntries and have it extend the QSortFilterProxyModel. We can use all the base implementations provided to us by the QSortFilterProxyModel the only one we need to re-implement is the filterAccetpsRow() predicate so we need to redefine it in our new class.

MyProxyModel_EvenEntries.h
#ifndef MYPROXYMODEL_EVENENTRIES_H
#define MYPROXYMODEL_EVENENTRIES_H

#include <QSortFilterProxyModel>

class MyProxyModel_EvenEntries : public QSortFilterProxyModel
{
    Q_OBJECT
public:
    MyProxyModel_EvenEntries(){};
    virtual ~MyProxyModel_EvenEntries();

    bool filterAcceptsRow( int source_row, const QModelIndex& source_parent ) const override;

};
#endif // MYPROXYMODEL_EVENENTRIES_H
MyProxyModel_EvenEntries.cpp
#include "MyProxyModel_EvenEntries.h"
bool MyProxyModel_EvenEntries::filterAcceptsRow( int source_row, const QModelIndex& source_parent ) const
{
    // Here we can specify whatever filter logic we need to and can check multiple Roles. Below
    // we are simply checking the Value role to see if that value stored there is even, if so include the
    // data entry in this proxy model.

    bool ret( false );
    if ( this->sourceModel() != nullptr )
    {
        auto index = this->sourceModel()->index( source_row, 0, source_parent );
        if ( index.isValid() )
        {
            auto valueRole = index.data( MyViewModel::MyViewModel_Roles::MyViewModel_Roles_Value );
            if ( valueRole.isValid() )
            {
                bool ok( false );
                auto value = valueRole.toInt( &ok );
                if ( ok )
                {
                    if ( ( value % 2 ) == 0 )
                    {
                        ret = true;
                    }
                }
            }
        }
    }
    return ret;
}

So you can see here its pretty straight forward, we take the source_row parameter (note this is a row relative to the source model not to the proxy model) which was passed into the predicate and use it to look up the entry in the source model. We then pull out whatever data we need to in order to satisfy our filter logic (in this case its just the MyViewModel_Roles_Value role) and evaluate it ( in this case the evaluation logic is simply to check if the value is even or not). We can insert whatever logic here we need and check as many roles or other factors as our business logic requires us to as long as we simply return a true or false response (include or not to include).

If I update my main.cpp and my QML script I will get a view which simply shows only the entries which have an even value.

main.cpp
#include <QApplication>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include "MyProxyModel_EvenEntries.h"
#include "MyViewModel.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    // Create an instance of our ViewModel; this holds the total list of data
    auto vm = new MyViewModel();

    auto evenEntriesModel = new MyProxyModel_EvenEntries();
    evenEntriesModel->setSourceModel( vm );

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("MyModel", vm );
    engine.rootContext()->setContextProperty("EvenEntriesModel", evenEntriesModel );
    engine.load(QUrl(QStringLiteral("qrc:///main.qml")));

    return app.exec();
}
main.qml
Item {
    id: root
    anchors.fill: parent

    ListView {
        id: valueListView
        anchors.fill: parent
        model: EvenEntriesModel
        delegate: valueDelegate
    }
    Component {
        id: valueDelegate
        Rectangle {
            anchors.left: parent.left
            anchors.right: parent.right
            height: 25
            color: "ligthblue"
            border.color: "black"

            Text {
                text: role_value
                anchors.centerIn: parent
            }
        }
    }
}

QSortFilterProxyModel_Sample1_EvenEntries

There you have it, that is how to use the QSortFilterProxyModel to allow a view to only show a subset of the data contained within the root/source Data Model.

You can download sample applications which illustrate the use of a QSortFilterProxyModel here:
Sample 1: Four ListViews and Four ProxyModels (one a custom proxy model)
Sample 2: One ListView and Multiple QSortFilterProxyModels. The user can pick what data to show in the ListView.

Thank you I hope you have enjoyed and found this tutorial helpful. Feel free to leave any comments or questions you might have below and I’ll try to answer them as time permits.

Until next time think imaginatively and design creatively

Brad

My interest in computer programming started back in high school and Software Development has remained a hobby of mine ever since. I graduated as a Computer Engineering Technologist and have been working as a Software Developer for many years. I believe that software is crafted; understanding that how it is done is as important as getting it done. I enjoy the aesthetics in crafting elegant solutions to complex problems and revel in the knowledge that my code is maintainable and thus, will have longevity. I hold the designation Certified Technician (C.Tech.) with the Ontario Association of Computer Engineering Technicians and Technologists (OACETT), have been certified as a Professional Scrum Master level 1 (PSM I) and as a Professional Scrum Developer level 1 (PSD I) by Scrum.org as well as designated as an Officially Certified Qt Developer by the Qt Company. For more on my story check out the about page here

Feel free to write a reply or comment.