KDTableToListProxyModel: a flattening proxy model
With this blog post we are going to kickstart our brand new blog series about KDToolBox. The first class we’re exploring is KDTableToListProxyModel, a table-to-list proxy model.
The main use case for KDTableToListProxyModel is exposing tabular models to Qt Quick. Qt Quick has a certain number of “view” elements that only support list models: for instance, ListView, PathView, Repeater, and so on. Multiple pieces of data for a given index (row) in the model are provided by using multiple roles for that index.
List models in Qt Quick
In practice, as a quick recap, using a list model in Qt Quick means that:
- our model class will handle multiple roles in its data() function, returning different pieces of data for the same index:
QVariant MyModel::data(const QModelIndelIndex &index, int role) const override { Q_ASSERT(checkIndex(index), QAbstractItemModel::CheckIndexOption::IndexIsValid)); int row = index.row(); // column is 0 by definition, this is a list model switch (role) { case NameRole: return ~~~; case PhoneNumberRole: return ~~~; } return {}; }
If you’re curious about that call to checkIndex(): see this earlier blog post of mine for more details.
- we establish a mapping between the values of the roles and the strings that we’re going to use in Qt Quick to refer to each role. This is done in the model’s roleNames() function:
QHash<int, QByteArray> MyModel::roleNames() const override { return { { NameRole, "name" }, { PhoneNumberRole, "phoneNumber" } }; }
- finally, we use these names from a delegate used in Qt Quick:
ListView { model: myModel delegate: Item { width: parent.width height: 30 Text { anchors.left: parent.left text: model.name // <-- HERE } Text { anchors.right: parent.right text: model.phoneNumber // <-- HERE } } }
Enter KDTableToListProxyModel
Sometimes we have a table model that we want to use in Qt Quick — that is, a model with multiple columns as well as rows. There could be many reasons for having such a model around: maybe it has been developed in the past targeting Qt Widgets, and now we want to reuse it when building a shiny new Qt Quick UI; or maybe it comes from a third party source, for instance it’s one of the built-in models in Qt (e.g. a QFileSystemModel).
No matter what, we’d like to simply reuse that model without changing its internals or developing a brand new one just for Qt Quick purposes (of course, if the model comes from a third party, we may not be able to change it at all).
There is an established solution in Qt for this kind of use cases: proxy models. A proxy model is a model that alters somehow the data offered by another model. Proxy models can perform sorting, or filtering (like QSortFilterProxyModel), or augment the data, and so on.
KDTableToListProxyModel is a proxy model that converts table models to list models. It does so by “flattening” the table’s data onto one column (the first). The data of each column is then exposed as roles, which can be freely configured by the user.
For instance, suppose we have a table model that holds country names, their flags and their population. The model structure is as follows:
- Column 0 has country names, under the Qt::DisplayRole
- Column 0 has also the country flag, under the Qt::DecorationRole
- Column 1 has the population, under the Qt::DisplayRole
Such a model is not usable as-is in Qt Quick (say, in a ListView) because indeed it has multiple columns. We can insert a KDTableToListProxyModel to flatten it, for instance like this:
auto tableToListProxy = new KDTableToListProxyModel; tableToListProxy->setSourceModel(tableModel); tableToListProxy->setRoleMapping(0, Qt::UserRole + 0, "name", Qt::DisplayRole); tableToListProxy->setRoleMapping(0, Qt::UserRole + 1, "flag", Qt::DecorationRole); tableToListProxy->setRoleMapping(1, Qt::UserRole + 2, "population", Qt::DisplayRole); // expose tableToListProxy to QML and use it from there
The main function is of course setRoleMapping(), which sets the mapping between the column/role of the source model to the role/roleName of the proxy. A call like
tableToListProxy->setRoleMapping(1, Qt::UserRole + 2, "population", Qt::DisplayRole);
means “map the Qt::DisplayRole of column 1 of the source model, to the role Qt::UserRole + 2 of the proxy, exposing that role as “population” as a role name”.
The reason why arguments are out of order is because most of the time you want to map Qt::DisplayRole, which happens to be the default value for the last argument, so you can omit it. And, since the result is a list, all columns get obviously mapped to column 0, so there is no need to specify that.
The result in a Qt Quick’s ListView will then look like this:
And voilà, we’re using a table model in Qt Quick without modifying it.
KDTableToListProxyModel is part of KDToolBox, KDAB’s collection of miscellaneous useful C++ classes and stuff. You can download it from our GitHub repository here. Have fun!