Diego Thomé
pretty good you are a genius, tanks <3
When developing with the Qt framework, you might already have stumbled over its model view framework, consisting of classes like QAbstractItemModel, QTreeView and QSortFilterProxyModel.
In particular, QSortFilterProxyModel is interesting: In the simplest case, you create a model which you plug into a view, which displays the exposed items. The QSortFilterProxyModel in turn can be plugged in between the source model and the view. With its help, we can easily filter the list of items or reorder them. We can even stack such proxy models, which allows to combine different sorting and filtering criteria as needed.
When it comes to QML, things are not quite as straightforward. Of course, you can continue to use the C++ model/view classes, including proxy models. However, if you have models which are defined solely on QML side, or you want to make use of the possibility of QML to use other kinds of models - in the simplest case a simple integer number - you need another solution.
Here, the QML type DelegateModel proofs to be useful. Although it is (in my opinion) not documented as good as it could be, we will use it to implement sorting and filtering of an arbitrary source model in QML. However, a warning ahead: If you have larger data sets, consider sticking with the C++ model classes instead! Only use this approach when you know that your data sets are relatively small, so the impact of doing the filtering in the QML layer does not impact your app too much.
Let’s start with what we want to archive in the end: A GUI which has a simple model (a number which the user can change) and which we filter and sort entirely in QML. The code for such an application could roughly look like this:
import QtQuick 2.9
import QtQuick.Window 2.3
import QtQuick.Controls 2.4
import QtQml.Models 2.3
import QtQuick.Layouts 1.3
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
SortFilterModel {
id: delegateModel
lessThan: function(left, right) {
if (view.sortLexically) {
return left.modelData.toString() < right.modelData.toString();
} else {
return left.modelData < right.modelData;
}
}
filterAcceptsItem: function(item) {
return item.modelData % 2 == view.remainder;
}
model: numberOfItems.value
delegate: Text {
id: item
text: qsTr("This is item #%1").arg(modelData)
}
}
ListView {
id: view
property int remainder: 0
property bool sortLexically: true
anchors {
left: parent.left
right: parent.right
top: parent.top
bottom: controls.top
}
model: delegateModel
onRemainderChanged: delegateModel.update()
onSortLexicallyChanged: delegateModel.update()
}
RowLayout {
id: controls
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
Button {
text: qsTr("Toggle odd/even")
onClicked: view.remainder = (view.remainder + 1) % 2;
}
Button {
text: qsTr("Toggle sort")
onClicked: view.sortLexically = !view.sortLexically
}
Slider {
id: numberOfItems
from: 10
to: 1000
value: 100
Layout.fillWidth: true
}
}
}
And this is how it looks like when rendered:

Let’s quickly jump through the app code. First, we instantiate out SortFilterModel (we are going to look at it’s code later):
SortFilterModel {
id: delegateModel
lessThan: function(left, right) {
if (view.sortLexically) {
return left.modelData.toString() < right.modelData.toString();
} else {
return left.modelData < right.modelData;
}
}
filterAcceptsItem: function(item) {
return item.modelData % 2 == view.remainder;
}
model: numberOfItems.value
delegate: Text {
id: item
text: qsTr("This is item #%1").arg(modelData)
}
}
It is basically what would you expect:
lessThan property. This function is used to compare two entries of the model and returns true of the left one is less than the right one, otherwise false. The parameters left and right are containers which have the same properties that otherwise would be exposed to the delegate of a view when used with the same model as source.filterAcceptsItem gets and item and returns true if the item shall be visible or false otherwise.model property is set to the source model we want to use. We can set it to any model we need, be it a QAbstractItemModel, a list or even a simple number. In our case, we bind it to a number (which is coming from a slider component defined later).delegate. This is what is potentially new and unexpected, as usually we set delegates in Views. However, as we use a DelegateModel as a base, we set the delegate in the model.Next, let’s continue with the view:
ListView {
id: view
property int remainder: 0
property bool sortLexically: true
anchors {
left: parent.left
right: parent.right
top: parent.top
bottom: controls.top
}
model: delegateModel
onRemainderChanged: delegateModel.update()
onSortLexicallyChanged: delegateModel.update()
}
Nothing special. We anchor the view and set it’s model property to our SortFilterModel. We also define two helper properties (remainder and sortLexically) which we use to control sorting and filtering. Note that we call the update() method of our SortFilterModel when these two properties change to apply the changed sorting and filtering. Also note that we do not set a delegate in the view, as this is coming from our source model.
Finally, we add some controls to change sorting, filtering and also the amount of items in the source model:
RowLayout {
id: controls
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
Button {
text: qsTr("Toggle odd/even")
onClicked: view.remainder = (view.remainder + 1) % 2;
}
Button {
text: qsTr("Toggle sort")
onClicked: view.sortLexically = !view.sortLexically
}
Slider {
id: numberOfItems
from: 10
to: 1000
value: 100
Layout.fillWidth: true
}
}
We have…
So far, so good. Next, let’s have a look at how the SortFilterModel is implemented.
SortFilterModel Componentimport QtQuick 2.9
import QtQml.Models 2.3
DelegateModel {
id: delegateModel
property var lessThan: function(left, right) { return true; }
property var filterAcceptsItem: function(item) { return true; }
function update() {
if (items.count > 0) {
items.setGroups(0, items.count, "items");
}
// Step 1: Filter items
var visible = [];
for (var i = 0; i < items.count; ++i) {
var item = items.get(i);
if (filterAcceptsItem(item.model)) {
visible.push(item);
}
}
// Step 2: Sort the list of visible items
visible.sort(function(a, b) {
return lessThan(a.model, b.model) ? -1 : 1;
});
// Step 3: Add all items to the visible group:
for (i = 0; i < visible.length; ++i) {
item = visible[i];
item.inVisible = true;
if (item.visibleIndex !== i) {
visibleItems.move(item.visibleIndex, i, 1);
}
}
}
items.onChanged: update()
onLessThanChanged: update()
onFilterAcceptsItemChanged: update()
groups: DelegateModelGroup {
id: visibleItems
name: "visible"
includeByDefault: false
}
filterOnGroup: "visible"
}
As announced, the component is based on QML’s DelegateModel. We actually just need a few things on top:
property var lessThan: function(left, right) { return true; }
property var filterAcceptsItem: function(item) { return true; }
We define two properties lessThan and filterAcceptsItem which shall be set to functions which are then used to sort and filter our source model. The defaults are set such that the SortFilterModel basically does an identity mapping, not changing the sorting nor visibility of the items from the source model.
groups: DelegateModelGroup {
id: visibleItems
name: "visible"
includeByDefault: false
}
filterOnGroup: "visible"
This is something you have to get used to: A DelegateModel uses different so called groups to do its job. It has a default, built in group called items, which is always there. By assigning a single or a list of DelegateModelGroup instances we can add more groups. For our model, we add just one new group visible, into which we put the items that are visible later on. By setting it’s includeByDefault property to false, items added to the source model are not automatically added to the group. Lastly, we set the filterOnGroup property to visible. This way, only items that are in the visible group are shown.
function update() {
if (items.count > 0) {
items.setGroups(0, items.count, "items");
}
// Step 1: Filter items
var visible = [];
for (var i = 0; i < items.count; ++i) {
var item = items.get(i);
if (filterAcceptsItem(item.model)) {
visible.push(item);
}
}
// Step 2: Sort the list of visible items
visible.sort(function(a, b) {
return lessThan(a.model, b.model) ? -1 : 1;
});
// Step 3: Add all items to the visible group:
for (i = 0; i < visible.length; ++i) {
item = visible[i];
item.inVisible = true;
if (item.visibleIndex !== i) {
visibleItems.move(item.visibleIndex, i, 1);
}
}
}
This function is basically the heart of the implementation. It is responsible for filtering and sorting the source model. For this, the model does the following:
visible group, by calling the setGroups method of the items group. It can be used to change the group memberships of several items at once. In our case, we set the group membership of all items in the items group to only ["item"] only.items group. Each entry has a member model, which contains a map of all properties from the source model - this is basically what would also be visible in a delegate. If the filterAcceptsItem function returns true, we put the item into an intermediate JavaScript array.sort method on the JavaScript array, passing in a custom sort function, which is based on our lessThan function.visible group. This is done by setting each item’s inVisible property to true. In addition, we use the mode method of the visible group to move the item to the position where it belongs to.That’s it!
Sure 😎
The great thing about using DelegateModel as base for our SortFilterModel, we can deal with everything: The DelegateModel just passes through the properties of the items in the source model. In the simplest case where you have a number as model, we only have the modelData attribute, which is the index of the item in the source model. If we use something else, the attributes are transparently made available, too. Here’s an example using a ListModel as base:
import QtQuick 2.9
import QtQuick.Window 2.3
import QtQuick.Controls 2.4
import QtQml.Models 2.3
import QtQuick.Layouts 1.3
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListModel {
id: nameModel
ListElement { name: "Alice"; team: "Crypto" }
ListElement { name: "Bob"; team: "Crypto" }
ListElement { name: "Jane"; team: "QA" }
ListElement { name: "Victor"; team: "QA" }
ListElement { name: "Wendy"; team: "Graphics" }
}
RowLayout {
id: controls
anchors {
left: parent.left
top: parent.top
right: parent.right
}
TextField {
id: nameFilter
placeholderText: qsTr("Search by name...")
Layout.fillWidth: true
onTextChanged: sortFilterModel.update()
}
RadioButton {
id: sortByName
checked: true
text: qsTr("Sort by name")
onCheckedChanged: sortFilterModel.update()
}
RadioButton {
text: qsTr("Sort by team")
}
}
SortFilterModel {
id: sortFilterModel
model: nameModel
filterAcceptsItem: function(item) {
return item.name.includes(nameFilter.text)
}
lessThan: function(left, right) {
if (sortByName.checked) {
var leftVal = left.name;
var rightVal = right.name;
} else {
leftVal = left.team;
rightVal = right.team;
}
return leftVal < rightVal ? -1 : 1;
}
delegate: Text {
text: name + " (" + team + ")"
}
}
ListView {
anchors {
left: parent.left
top: controls.bottom
right: parent.right
bottom: parent.bottom
}
model: sortFilterModel
}
}

As already pointed out: Do not use this approach for large lists of items. As sorting and filtering is done in QML/JavaScript, this could be a performance killer for your app.
Also note that with the SortFilterModel as it is implemented, you also get no “dynamic” filtering. When e.g. sorting or filtering criteria change, you have to manually call the update method.
I am a software/firmware developer, working in Dresden, Germany. In my free time, I spent some time on developing apps, presenting some interesting findings here in my blog.
pretty good you are a genius, tanks <3
Thanks for the good tutorial and your neat component!
thank you for this idea, i will check DelegateModel
Thanks for the smart Component. You should just correct the lessThan function that should return 0 in case of equality. Something like this, but not optimized:
visible.sort(function(a, b) {
return lessThan(a.model, b.model) ? -1 : (lessthan(b.model,a.model ? 1 : 0))
});
Thanks Daes for your comment.
You are right in that with the current approach “equal” list elements will get reordered. However, this was perfectly fine for the use case I had 😉
If someone needs this feature, instead of implementing a lessThan() function, a compare() one could be implemented, which returns -1, 0 or 1 as desired. This function would just be used like visible.sort(compare).
Hi, thanks for this nice explanation, it helps make sense of the DelegateModel documentation. What would you consider to be a large list; 10, 100, 500, 1000, 10000? I’m looking at a list of items that can’t credibly be > 1000 but with several filter terms. I was hoping to avoid QFilterProxyModel in C++, I’d prefer to keep the flexibility of a QML implementation.
As so often: It depends on your concrete application 😉
In the examples above, the model had - at least on my machine - no issues with a list of 1000 entries. I guess also 10000 is not a real issue (you can give it a try: Increase the max value for the slider in the example above and see how far you can get).
However, the example also has only trivial logic for the filtering. If your filter is very compute intensive, it might be worth factoring this part out to C++. However, even then you could maybe live without having to implement the complete model on C++ side but just implement your filter function there and make it available to QML.
In general, if you prefer to do as much on the QML side as possible, you can start by using the built in models and only “migrate” functionality to C++ if you experience performance issues later on. If you use a ListModel as base, it should be possible to easily do such a move without having to rewrite too much of your existing QML code.
Thanks, this is awesome!