<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[QML SortFilterProxyModel in Qt 6.12: Composable, Range and Regular-expression filters]]></title><description><![CDATA[<p dir="auto">In Qt 6.10 we introduced a declarative <a href="https://doc.qt.io/qt-6/qml-qtqml-models-sortfilterproxymodel.html" target="_blank" rel="noopener noreferrer nofollow ugc"><code>SortFilterProxyModel</code></a> for QML. It lets you sort and filter data of any <code>QAbstractItemModel</code> directly in QML, without changing the source model and without writing a C++ <code>QSortFilterProxyModel</code> subclass. If you haven’t seen it yet, the introductory post, <a href="https://www.qt.io/blog/model-to-sort-and-filter-the-data-on-the-fly" target="_blank" rel="noopener noreferrer nofollow ugc">QML Model: Sort and filter the data on the fly</a>, walks through the basics using a process table viewer application.</p>
<p dir="auto">Many of you tried it and sent us feedback. Thank you! In Qt 6.12, it adds new filter types, makes <code>FunctionFilter</code> simpler to write, and cleans up a few names that didn’t follow Qt conventions. This post goes through the changes using the <strong>same process table viewer</strong>, so you can take the snippets from the <a href="https://www.qt.io/blog/model-to-sort-and-filter-the-data-on-the-fly" target="_blank" rel="noopener noreferrer nofollow ugc">post</a> and update them as you read.</p>
<h2>Changes from Qt 6.10 to Qt 6.12</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th></th>
<th>Qt 6.10</th>
<th>Qt 6.12</th>
</tr>
</thead>
<tbody>
<tr>
<td>Assigning the source model</td>
<td><code>model: processModel</code></td>
<td><code>sourceModel: processModel</code> (<code>model</code> is deprecated)</td>
</tr>
<tr>
<td><code>FunctionFilter</code></td>
<td>One argument of a helper <code>RoleData</code> component type</td>
<td>Each role is its own typed argument: <code>filter(cpuUsage: real)</code></td>
</tr>
<tr>
<td><code>FunctionSorter</code></td>
<td><code>function sort(lhs, rhs)</code></td>
<td><code>function compare(lhs, rhs)</code> (<code>sort</code> is deprecated)</td>
</tr>
<tr>
<td>Inverting a filter</td>
<td><code>invert: true</code></td>
<td><code>inverted: true</code> (renamed in 6.11)</td>
</tr>
<tr>
<td>Filtering by a range of values</td>
<td>Write a <code>FunctionFilter</code></td>
<td><strong>New:</strong> <code>RangeFilter</code></td>
</tr>
<tr>
<td>Filtering by a regular expression</td>
<td>Write a <code>FunctionFilter</code></td>
<td><strong>New:</strong> <code>RegExpFilter</code></td>
</tr>
<tr>
<td>Combining filters with AND / OR</td>
<td>Not possible (all filters are always ANDed)</td>
<td><strong>New:</strong> <code>AllOfFilter</code> and <code>AnyOfFilter</code>, which can be nested</td>
</tr>
</tbody>
</table>
<p dir="auto"><code>SortFilterProxyModel</code> is still a <strong>tech preview</strong> in Qt 6.12, so its API can still change in response to your feedback.</p>
<h2>Revisiting the process table viewer in Qt 6.12</h2>
<p dir="auto">As in the <a href="https://www.qt.io/blog/model-to-sort-and-filter-the-data-on-the-fly" target="_blank" rel="noopener noreferrer nofollow ugc">post</a>, we use a <code>processModel</code> that exposes one row per running process with the roles <code>pid</code>, <code>name</code>, <code>uid</code>, <code>state</code> and <code>cpuUsage</code>, and show it in a <code>TableView</code>. In Qt 6.12, the basic setup looks like this:</p>
<pre><code class="language-qml">import QtQuick
import QtQuick.Controls
import QtQml.Models

ApplicationWindow {
    width: 800
    height: 600
    visible: true

    ProcessModel { id: processModel }

    SortFilterProxyModel {
        id: processProxyModel
        sourceModel: processModel        // was: model: processModel
        sorters: [
            RoleSorter { roleName: "uid" }
        ]
        filters: [
            FunctionFilter {
                function filter(cpuUsage: real): bool {
                    return cpuUsage &gt; 0
                }
            }
        ]
    }

    TableView {
        anchors.fill: parent
        model: processProxyModel
        delegate: Rectangle {
            implicitWidth: 120
            implicitHeight: 30
            border.width: 1
            Text {
                anchors.centerIn: parent
                text: display
            }
        }
    }
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/6cbfcaed-7f6d-45f6-83c2-90def65bb846.png" alt="basic-setup.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">Two things changed compared to Qt 6.10: the source model is set with <code>sourceModel</code>, and the filter function takes the role it needs directly as an argument. The following sections describe both.</p>
<h2><code>sourceModel</code> replaces <code>model</code></h2>
<p dir="auto"><code>SortFilterProxyModel</code> is a proxy model, and proxy models in Qt already have a <code>sourceModel</code> property. Having a second property called <code>model</code> caused confusion, especially because views also have a <code>model</code> property. In Qt 6.12, use <code>sourceModel</code>:</p>
<pre><code class="language-qml">SortFilterProxyModel {
    sourceModel: processModel
}
</code></pre>
<p dir="auto">The <code>model</code> property still works but is deprecated. It now also accepts only a <code>QAbstractItemModel</code>, which matches what was supported in practice.</p>
<h2>A simpler <code>FunctionFilter</code></h2>
<p dir="auto">In Qt 6.10, a <code>FunctionFilter</code> received the row’s data through a helper component that you had to declare yourself:</p>
<pre><code class="language-qml">// Qt 6.10
FunctionFilter {
    component RoleData: QtObject {
        property real cpuUsage
    }
    function filter(data: RoleData): bool {
        return data.cpuUsage &gt; filter.text
    }
}
</code></pre>
<p dir="auto">In Qt 6.12, you list the roles you need as typed arguments of <code>filter()</code>. Each argument name must match a role name in the source model, and the proxy model passes in that role’s value for the row:</p>
<pre><code class="language-qml">// Qt 6.12
FunctionFilter {
    function filter(cpuUsage: real): bool {
        return cpuUsage &gt; Number(filter.text)
    }
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/dd70e70a-cbc0-41cd-b5ed-78caef05a0cb.png" alt="functionfilter-search.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">Several roles can be used at once:</p>
<pre><code class="language-qml">FunctionFilter {
    function filter(name: string, cpuUsage: real): bool {
        return name.startsWith("qt") &amp;&amp; cpuUsage &gt; 10
    }
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/a174a666-28aa-404f-b503-1156a7504a8d.png" alt="functionfilter-multirole.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">If an argument name doesn’t match any role, you get a warning that names it.</p>
<h2>Filtering a range of values with <code>RangeFilter</code></h2>
<p dir="auto">In the <a href="https://www.qt.io/blog/model-to-sort-and-filter-the-data-on-the-fly" target="_blank" rel="noopener noreferrer nofollow ugc">post</a>, we used a <code>FunctionFilter</code> to show only processes above a CPU threshold. Range checks like this are common, so Qt 6.12 adds a dedicated <a href="https://doc.qt.io/qt-6/qml-qtqml-models-rangefilter.html" target="_blank" rel="noopener noreferrer nofollow ugc"><code>RangeFilter</code></a>. It keeps the rows where the value of <code>roleName</code> is between <code>minimum</code> and <code>maximum</code>:</p>
<pre><code class="language-qml">SortFilterProxyModel {
    id: processProxyModel
    sourceModel: processModel
    filters: [
        RangeFilter {
            roleName: "cpuUsage"
            minimum: 70
            maximum: 100
        }
    ]
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/503700fc-adeb-4820-91e9-9668396adead.png" alt="rangefilter-basic.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">Both ranges are <strong>inclusive</strong> by default. To leave the boundary value itself out, wrap it in <code>exclusive()</code>:</p>
<pre><code class="language-qml">RangeFilter {
    roleName: "cpuUsage"
    minimum: exclusive(0)   // cpuUsage &gt; 0, idle processes are hidden
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/d8056125-ab20-46ea-bc51-089c544766aa.png" alt="rangefilter-exclusive.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">Set only <code>minimum</code> or only <code>maximum</code> to get an open-ended range. Because the ranges are ordinary properties, you can bind them to UI controls, and the view updates as the user drags:</p>
<pre><code class="language-qml">RangeSlider {
    id: cpuRange
    from: 0; to: 100
    first.value: 10; second.value: 100
}

SortFilterProxyModel {
    sourceModel: processModel
    filters: RangeFilter {
        roleName: "cpuUsage"
        minimum: cpuRange.first.value
        maximum: cpuRange.second.value
    }
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/9a40505c-14d4-4a1b-8f19-64cc2328d16a.png" alt="rangefilter-slider.png" class=" img-fluid img-markdown" /></p>
<h2>Filtering with <code>RegExpFilter</code></h2>
<p dir="auto">The <a href="https://www.qt.io/blog/model-to-sort-and-filter-the-data-on-the-fly" target="_blank" rel="noopener noreferrer nofollow ugc">post</a> also showed how to filter the table with a JavaScript regular expression inside a <code>FunctionFilter</code>. In Qt 6.12, use <a href="https://doc.qt.io/qt-6/qml-qtqml-models-regexpfilter.html" target="_blank" rel="noopener noreferrer nofollow ugc"><code>RegExpFilter</code></a> instead. Assign a regular expression literal to <code>regExp</code>:</p>
<pre><code class="language-qml">SortFilterProxyModel {
    sourceModel: processModel
    filters: RegExpFilter {
        roleName: "name"
        regExp: /^qt/i          // names starting with "qt", any case
    }
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/4d195656-1b20-4f91-bd61-4dfbec870563.png" alt="regexpfilter-literal.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">For a search field, build the expression from the text. An empty pattern lets every row through, so the full table shows while the field is empty:</p>
<pre><code class="language-qml">TextField {
    id: filter
    placeholderText: qsTr("Filter process name")
}

SortFilterProxyModel {
    sourceModel: processModel
    filters: RegExpFilter {
        roleName: "name"
        regExp: new RegExp(filter.text, "i")
    }
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/27420389-e8f7-46a5-abd5-440c6a7172c6.png" alt="regexpfilter-search.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">Like every filter, <code>RegExpFilter</code> checks all columns by default. If you know which column holds the data, set <code>column</code> to skip the other columns.</p>
<h2>Combining filters with <code>AllOfFilter</code> and <code>AnyOfFilter</code></h2>
<p dir="auto">In Qt 6.10, every filter in the <code>filters</code> list had to accept a row for it to be shown. This is a logical AND, and there was no way to express “this <em>or</em> that”. Qt 6.12 adds two filters that group other filters:</p>
<ul>
<li><a href="https://doc.qt.io/qt-6/qml-qtqml-models-alloffilter.html" target="_blank" rel="noopener noreferrer nofollow ugc"><code>AllOfFilter</code></a> accepts a row when <strong>all</strong> of its child filters accept it (AND).</li>
<li><a href="https://doc.qt.io/qt-6/qml-qtqml-models-anyoffilter.html" target="_blank" rel="noopener noreferrer nofollow ugc"><code>AnyOfFilter</code></a> accepts a row when <strong>at least one</strong> of its child filters accepts it (OR).</li>
</ul>
<p dir="auto">Child filters are declared directly inside the group. For example, to show processes that are either running or sleeping:</p>
<pre><code class="language-qml">SortFilterProxyModel {
    sourceModel: processModel
    filters: AnyOfFilter {
        ValueFilter { 
            roleName: "state"; 
            value: "Running" 
        }
        ValueFilter { 
            roleName: "state"; 
            value: "Sleeping" 
        }
    }
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/267ac86d-55bd-4e7d-91b2-27be78dd257f.png" alt="anyoffilter-state.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">Groups can be nested to build any condition. This filter shows processes that belong to the current user <strong>or</strong> use more than 50% CPU, <strong>and</strong> whose name matches the search field:</p>
<pre><code class="language-qml">filters: AllOfFilter {
    RegExpFilter {
        roleName: "name"
        regExp: new RegExp(filter.text, "i")
    }
    AnyOfFilter {
        ValueFilter {
            roleName: "uid"
            value: currentUid
        }
        RangeFilter {
            roleName: "cpuUsage"
            minimum: exclusive(50)
        }
    }
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/3b52bd39-b7e0-40b8-a8e3-95cb2fd7bb3c.png" alt="nested-filters.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">A group is itself a filter, so <code>enabled</code> and <code>inverted</code> apply to the whole group. This makes it easy to switch a set of conditions on and off from a single control:</p>
<pre><code class="language-qml">CheckBox { id: busyOrMine; text: qsTr("Only my or busy processes") }

AnyOfFilter {
    enabled: busyOrMine.checked
    ValueFilter {
        roleName: "uid"
        value: currentUid
    }
    RangeFilter {
        roleName: "cpuUsage"
        minimum: exclusive(50)
    }
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/72c990c2-0a69-4f61-919d-0832e6caad87.png" alt="group-toggle.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">When a group is disabled, it lets every row through, so only the rest of the filters apply.</p>
<h2><code>FunctionSorter</code>: <code>sort</code> becomes <code>compare</code></h2>
<p dir="auto">A <code>FunctionSorter</code> method returns a negative number, zero or a positive number, so it compares two rows rather than sorting them. In Qt 6.12, the method is called <code>compare()</code>. The <code>sort()</code> name still works, but it prints a deprecation warning.</p>
<p dir="auto">The following sorter lists running processes first:</p>
<pre><code class="language-qml">FunctionSorter {
    component ProcessData: QtObject {
        property string state
    }
    function compare(lhs: ProcessData, rhs: ProcessData): int {
        const rank = s =&gt; s === "Running" ? 0 : 1
        return rank(lhs.state) - rank(rhs.state)
    }
}
</code></pre>
<p dir="auto">Always give the arguments and the return value of <code>compare()</code> explicit types, as shown above. Untyped <code>compare()</code> functions are not supported.</p>
<h2><code>invert</code> is now <code>inverted</code></h2>
<p dir="auto">To follow the Qt property naming convention, the <code>invert</code> property of all filters was renamed to <code>inverted</code> in Qt 6.11:</p>
<pre><code class="language-qml">ValueFilter {
    roleName: "uid"
    value: 0
    inverted: true      // hide processes owned by root
}
</code></pre>
<h2>Putting it all together</h2>
<p dir="auto">Here is the process viewer with a field to filter process name, a CPU range slider and an toggle to show busy processes. It is sorted by CPU usage and then by name:</p>
<pre><code class="language-qml">import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQml.Models

ApplicationWindow {
    width: 900
    height: 600
    visible: true
    title: qsTr("Process viewer")

    property int currentUid: 1000   // the current user's id

    ProcessModel { id: processModel }

    SortFilterProxyModel {
        id: processProxyModel
        sourceModel: processModel

        filters: [
            RegExpFilter {
                roleName: "name"
                regExp: new RegExp(filter.text, "i")
            },
            RangeFilter {
                roleName: "cpuUsage"
                minimum: cpuRange.first.value
                maximum: cpuRange.second.value
            },
            AnyOfFilter {
                enabled: busyOrMine.checked
                ValueFilter { 
                    roleName: "uid"; 
                    value: currentUid 
                }
                RangeFilter { 
                    roleName: "cpuUsage"; 
                    minimum: exclusive(50) 
                }
            }
        ]

        sorters: [
            RoleSorter {
                roleName: "cpuUsage"
                sortOrder: Qt.DescendingOrder
                priority: 0
            },
            StringSorter {
                roleName: "name"
                caseSensitivity: Qt.CaseInsensitive
                priority: 1
            }
        ]
    }

    ColumnLayout {
        anchors.fill: parent

        RowLayout {
            TextField {
                id: filter
                placeholderText: qsTr("Filter process name")
                Layout.fillWidth: true
            }
            Label { text: qsTr("CPU %") }
            RangeSlider {
                id: cpuRange
                from: 0; to: 100
                first.value: 0; second.value: 100
            }
            CheckBox {
                id: busyOrMine
                text: qsTr("Only mine or busy")
            }
        }

        HorizontalHeaderView {
            syncView: tableView
            Layout.fillWidth: true
        }

        TableView {
            id: tableView
            Layout.fillWidth: true
            Layout.fillHeight: true
            clip: true
            model: processProxyModel
            delegate: Rectangle {
                implicitWidth: 140
                implicitHeight: 30
                border.width: 1
                Text {
                    anchors.centerIn: parent
                    text: display
                }
            }
        }
    }
}
</code></pre>
<p dir="auto"><img src="https://ddgobkiprc33d.cloudfront.net/4306a88f-5719-4dcd-96fc-d8ad1d571cec.png" alt="full-example.png" class=" img-fluid img-markdown" /></p>
<p dir="auto">There is no JavaScript filtering logic and no <code>invalidate()</code> call. Every filter is declared and bound to a control, and the table updates as the user types, drags or clicks.</p>
<h2>Migrating from Qt 6.10</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>If your 6.10 code has…</th>
<th>Change it to…</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>model: myModel</code></td>
<td><code>sourceModel: myModel</code></td>
</tr>
<tr>
<td><code>invert: true</code></td>
<td><code>inverted: true</code></td>
</tr>
<tr>
<td><code>function filter(data: RoleData)</code> using <code>data.cpuUsage</code></td>
<td><code>function filter(cpuUsage: real)</code>, and remove the <code>RoleData</code> component</td>
</tr>
<tr>
<td><code>function sort(lhs: T, rhs: T): int</code></td>
<td><code>function compare(lhs: T, rhs: T): int</code></td>
</tr>
<tr>
<td>A <code>FunctionFilter</code> that checks <code>value &gt;= a &amp;&amp; value &lt;= b</code></td>
<td><code>RangeFilter { roleName: "…"; minimum: a; maximum: b }</code></td>
</tr>
<tr>
<td>A <code>FunctionFilter</code> that runs a regular expression</td>
<td><code>RegExpFilter { roleName: "…"; regExp: /…/ }</code></td>
</tr>
<tr>
<td>Several proxy models or a large function to express OR</td>
<td><code>AnyOfFilter { … }</code>, nested inside <code>AllOfFilter</code> if needed</td>
</tr>
</tbody>
</table>
<p dir="auto">The old <code>FunctionFilter</code> style with a single <code>RoleData</code> argument is no longer supported, so update those filters when you move to Qt 6.12. Code that uses <code>model</code> or <code>sort()</code> keeps working, but prints deprecation warnings.</p>
<h2>Wrapping up</h2>
<p dir="auto">With Qt 6.12, SortFilterProxyModel provides more flexibility to sort and filter data in a declarative way. The common filtering use cases, such as filtering data within a range or matching a pattern, can be configured directly through RangeFilter and RegExpFilter, and the filters can be combined with logical AND and OR through AllOfFilter and AnyOfFilter. Also, the FunctionFilter has been made simpler. <code>SortFilterProxyModel</code> is still a tech preview, and your feedback shapes the final API. Try the new filters in the Qt 6.12 release and tell us what works for you and what is missing, on the <a href="https://forum.qt.io/">Qt Forum</a> or in the <a href="https://bugreports.qt.io/" target="_blank" rel="noopener noreferrer nofollow ugc">Qt bug tracker</a>.</p>
]]></description><link>https://forum.qt.io/topic/165235/qml-sortfilterproxymodel-in-qt-6.12-composable-range-and-regular-expression-filters</link><generator>RSS for Node</generator><lastBuildDate>Thu, 05 Nov 2026 07:28:46 GMT</lastBuildDate><atom:link href="https://forum.qt.io/topic/165235.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 03 Nov 2026 10:27:55 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to QML SortFilterProxyModel in Qt 6.12: Composable, Range and Regular-expression filters on Wed, 04 Nov 2026 14:23:35 GMT]]></title><description><![CDATA[<p dir="auto">yep. really cool.<br />
will try later ...</p>
<p dir="auto"><strong>curious</strong>:<br />
years ago, as I switched from <strong>BlackBerry10</strong> (Cascades/Qt 4.8) to <strong>Qt 5</strong> (QuickControls2) I really missed the<br />
<em>GroupDataModel</em> from Cascades, where I could do things like this:</p>
<pre><code>import bb.cascades 1.2
ListView {
    dataModel: GroupDataModel {
        sortingKeys: ["lastName", "firstName"]
        sortedAscending: true
        grouping: ItemGrouping.ByFirstChar
    }
}
</code></pre>
<p dir="auto">great to see what's now possible with Qt QML - THX :)</p>
]]></description><link>https://forum.qt.io/post/840759</link><guid isPermaLink="true">https://forum.qt.io/post/840759</guid><dc:creator><![CDATA[ekkescorner]]></dc:creator><pubDate>Wed, 04 Nov 2026 14:23:35 GMT</pubDate></item><item><title><![CDATA[Reply to QML SortFilterProxyModel in Qt 6.12: Composable, Range and Regular-expression filters on Tue, 03 Nov 2026 19:32:10 GMT]]></title><description><![CDATA[<p dir="auto">Hi and welcome to devnet,</p>
<p dir="auto">Nice feature !</p>
]]></description><link>https://forum.qt.io/post/840750</link><guid isPermaLink="true">https://forum.qt.io/post/840750</guid><dc:creator><![CDATA[SGaist]]></dc:creator><pubDate>Tue, 03 Nov 2026 19:32:10 GMT</pubDate></item></channel></rss>