代码之家  ›  专栏  ›  技术社区  ›  dtech

通过QML函子对C++模型进行排序和过滤?

  •  2
  • dtech  · 技术社区  · 6 年前

    我有一个多态性(如在任意角色中) QObject 主要从qml声明性实例化的模型, as in this answer ,我希望能够有自定义的数据“视图”,通过从代码字符串js functors生成的任意、潜在的运行时对模型进行排序和筛选,如下所示:

      DataView {
        sourceModel: model
        filter: function(o) { return o.size > 3 }
        sort: function(a, b) { return a.size > b.size }
      }
    

    这个 QSortFilterProxyModel 接口似乎并不特别适合该任务,而是专注于静态角色和预编译规则。

    我试着用 QJSValue C++方面的属性,但似乎是不可能的,C++代码只是不编译该属性类型。如果我将属性类型设置为 QVariant 我从qml获取错误消息,这些函数只能绑定到 var 性质。显然, var q-变体 这里的转换不像返回值那样进行。

    2 回复  |  直到 6 年前
        1
  •  2
  •   GrecKo    6 年前

    正如您所提到的,您可以使用qjsvalue。但那是静态的。如果你想使用像 filter: function(o) { return o.size > slider.value; } 使用动态滑块?你必须手动呼叫 invalidateFilter() .

    作为一个更实际的选择,您可以使用 QQmlScriptString 作为财产 QQmlExpression 去执行它。使用 qqmlexpression压力 允许您收到上下文更改的通知 setNotifyOnValueChanged .

    您的语法将更改为: filter: o.size > slider.value .

    如果您正在寻找开箱即用的解决方案,我已经在我的一个库中实现了这一点: SortFilterProxyModel on GitHub

    你可以看看 ExpressionFilter 和; ExpressionSorter ,它们的作用和你最初想要的一样。您可以在repo中检查完整的源代码。

    使用方法:

    import SortFilterProxyModel 0.2
    
    // ...
    
    SortFilterProxyModel {
        sourceModel: model
        filters: ExpressionFilter  { expression: model.size > 3 }
        sorters: ExpressionSorter { expression: modelLeft.size < modelRight.size }
    }
    

    但是,正如@ dTead所提到的,每一行的QML和C++之间来回的开销是相当明显的。这就是为什么我创建了更具体的过滤器和分类器。在你的情况下,我们会用 RangeFilter RoleSorter :

    import SortFilterProxyModel 0.2
    
    // ...
    
    SortFilterProxyModel {
        sourceModel: model
        filters: RangeFilter  {
            roleName: "size"
            minimumValue > 3
            minimumInclusive: true
        }
        sorters: RoleSorter { roleName: "size" }
    }
    

    这样做,我们有一个很好的声明性API,参数只从QML传递到C++。所有的过滤和排序然后完全在C++侧完成。

        2
  •  1
  •   dtech    5 年前

    更新:

    重新审视这个问题,我终于找到了一个最终的解决方案,所以我决定顺便介绍一些最新情况。首先,相关代码:

    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<QObject*>()));
      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<QObject*>()));
      l.append(_engine->newQObject(sourceModel()->data(right).value<QObject*>()));
      return m_sort.call(l).toBool();
    }
    

    我发现这个解决方案比qqmlscriptstring&qqmlexpression duo更简单、更安全、性能更好,它确实提供了通知的自动更新,但正如grecko的回答下面的评论中所阐述的,它有点不稳定,不值得这么做。

    获取外部上下文属性更改的自动更新的技巧是在返回实际函子之前简单地引用它们:

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

    下面是一个使用新速记函数语法的简单表达式,它引用 expanded; SS.showHidden; 为了在这些变化时触发重新计算,然后隐式返回函子

    o => expanded && (SS.showHidden ? true : !o.hidden)

    类似于:

    return function(o) { return expanded && (SS.showHidden ? true : !o.hidden) }

    它根据父节点是否展开、子节点是否隐藏以及隐藏的对象是否仍显示来过滤对象。

    此解决方案无法自动响应对 o.hidden 作为 o 在求值时插入到函子中,不能在绑定表达式中引用,但这可以在需要动态响应此类更改的视图委托中轻松实现:

    Connections {
          target: obj
          onHiddenChanged: triggerExplicitEvaluation()
    }
    

    请记住,用例涉及的架构较少/单一 QObject* 角色 model 这有助于改进数据模型,其中模型项数据是通过qml属性实现的,因此这里不适用任何角色或regex库存筛选机制,但同时,这也提供了使用单个机制实现基于任何criteria和任意项数据,性能非常好,尽管我最初很担心。它不实现排序顺序,只需翻转比较表达式结果就可以轻松实现排序顺序。