I have a polymorphic (as in arbitrary roles) QObject
model that is mostly instantiated declaratively from QML, as in this answer, and I would like to be able to have custom data "views" that sort and filter the model via arbitrary, and potentially - runtime generated from code strings JS functors, something like that:
DataView {
sourceModel: model
filter: function(o) { return o.size > 3 }
sort: function(a, b) { return a.size > b.size }
}
The QSortFilterProxyModel
interface doesn't seem to be particularly well suited to the task, instead being fixated on static roles and pre-compiled rules.
I tried using QJSValue
properties on the C++ side, but it seems like it is not possible, the C++ code just doesn't compile with that property type. And if I set the property type to QVariant
I get error messages from QML that functions can only be bound to var
properties. Evidently, var
to QVariant
conversion doesn't kick in here as it does for return values.
Update:
Revisiting the issue, I finally came with a finalized solution, so I decided to drop in some updates. First, the relevant code:
I found this solution to be simpler, safer and better performing than the QQmlScriptString & QQmlExpression duo, which does offer automatic updates on notifications, but as already elaborated in the comments below GrecKo's answer, was kinda flaky and not really worth it.
The hack to get auto-updates for external context property changes is to simply reference them before returning the actual functor:
Here is a simple expression using the new shorthand function syntax, it references
expanded; SS.showHidden;
in order to trigger reevaluations if those change, then implicitly returns the functoro => expanded && (SS.showHidden ? true : !o.hidden)
which is analogous to:
return function(o) { return expanded && (SS.showHidden ? true : !o.hidden) }
which filters out objects based on whether the parent node is expanded, whether the child node is hidden and whether hidden objects are still displayed.
This solution has no way to automatically respond to changes to
o.hidden
, aso
is inserted into the functor upon evaluation and can't be referenced in the binding expression, but this can easily be implemented in the delegates of views that need to dynamically respond to such changes:Remember that the use case involves a schema-less / single
QObject*
role model that facilitates a metamorphic data model where model item data is implemented via QML properties, so none of the role or regex stock filtering mechanisms are applicable here, but at the same time, this gives the genericity to use a single mechanism to implement sorting and filtering based on any criteria and arbitrary item data, and performance is very good, despite my initial concerns. It doesn't implement a sorting order, that is easily achievable by simply flipping the comparison expression result.As you mentionned, you could use QJSValue. But that's pretty static. What if you want to use a filter like
filter: function(o) { return o.size > slider.value; }
with a dynamic slider ? You'll have to manually callinvalidateFilter()
.As a more practical alternative, you could instead use
QQmlScriptString
as a property &QQmlExpression
to execute it. UsingQQmlExpression
allows you to be notified of context changes withsetNotifyOnValueChanged
.Your syntax would change to be like so :
filter: o.size > slider.value
.If you are looking for an out of the box solution, I've implemented this in a library of mine : SortFilterProxyModel on GitHub
You can take a look at
ExpressionFilter
&ExpressionSorter
, those do the same as what you initially wanted. You can check the complete source code in the repo.How to use it :
But as @dtech mentionned, the overhead of going back and forth between qml and c++ for each row of the model is quite noticeable. That's why I created more specific filters and sorters. In your case, we would use
RangeFilter
andRoleSorter
:Doing like this, we have a nice declarative API and the parameters are only passed once from qml to c++. All the filtering and sorting is then entirely done on the c++ side.