Sort and filter a C++ model via QML functors?

后端 未结 2 1508
失恋的感觉
失恋的感觉 2021-01-20 23:47

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 hav

2条回答
  •  终归单人心
    2021-01-21 00:34

    Update:

    Revisiting the issue, I finally came with a finalized solution, so I decided to drop in some updates. First, the relevant code:

    void set_filter(QJSValue f) {
      if (f != m_filter) {
        m_filter = f;
        filterChanged();
        invalidate();
      }
    }
    
    void set_sorter(QJSValue f) {
      if (f != m_sort) {
        m_sort = f;
        sorterChanged();
        sort(0, Qt::DescendingOrder);
      }
    }
    
    bool filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const {
      if (!m_filter.isCallable()) return true;
      QJSValueList l;
      l.append(_engine->newQObject(sourceModel()->index(sourceRow, 0, sourceParent).data().value()));
      return m_filter.call(l).toBool();
    }
    
    bool lessThan(const QModelIndex & left, const QModelIndex & right) const {
      if (!m_sort.isCallable()) return false;
      QJSValueList l;
      l.append(_engine->newQObject(sourceModel()->data(left).value()));
      l.append(_engine->newQObject(sourceModel()->data(right).value()));
      return m_sort.call(l).toBool();
    }
    

    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:

    filter: { expanded; SS.showHidden; o => expanded && (SS.showHidden ? true : !o.hidden) }
    

    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 functor

    o => 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, as o 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:

    Connections {
          target: obj
          onHiddenChanged: triggerExplicitEvaluation()
    }
    

    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.

提交回复
热议问题