Сортировать и фильтровать модель C ++ через функторы QML? - PullRequest
0 голосов
/ 05 июня 2018

У меня есть полиморфная (как в произвольных ролях) модель QObject, которая в основном декларативно создается из QML, , как в этом ответе , и я хотел бы иметь возможность иметь пользовательские "представления" данныхкоторые сортируют и фильтруют модель с помощью произвольной, и потенциально - среды выполнения, сгенерированной из JS-функторов строк кода, что-то вроде этого:

  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 в QVariant здесь не срабатывает, как для возвращаемых значений.

Ответы [ 2 ]

0 голосов
/ 06 июня 2018

Как вы упомянули, вы можете использовать QJSValue.Но это довольно статично.Что если вы хотите использовать фильтр типа filter: function(o) { return o.size > slider.value; } с динамическим ползунком?Вам придется вручную вызвать invalidateFilter().

. В качестве более практичной альтернативы вы можете вместо этого использовать QQmlScriptString в качестве свойства & QQmlExpression длявыполнить его.Использование QQmlExpression позволяет вам получать уведомления об изменениях контекста с помощью setNotifyOnValueChanged.

Ваш синтаксис изменится так: filter: o.size > slider.value.

Если выищу готовое решение, я реализовал это в моей библиотеке: SortFilterProxyModel на GitHub

Вы можете взглянуть на ExpressionFilter & ExpressionSorter, они делают то же самое, что вы изначально хотели.Вы можете проверить полный исходный код в репозитории.

Как его использовать:

import SortFilterProxyModel 0.2

// ...

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

Но, как упомянул @dtech, накладные расходы на переход назад и вперед между 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 ++.

0 голосов
/ 05 июня 2018

Обновление:

Пересматривая проблему, я наконец-то пришел к окончательному решению, поэтому решил добавить некоторые обновления.Во-первых, соответствующий код:

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, который предлагает автоматическое обновление уведомлений, но, как уже было сказано в комментариях нижеОтвет 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* роль модель , которая облегчает метаморфическую модель данных, в которой данные элемента модели реализуются через свойства QML, поэтому ни один из механизмов фильтрации ролей или регулярных выражений здесь не применим, но прив то же время это дает универсальность в использовании единого механизма для реализации сортировки и фильтрации на основе любых критериев и данных произвольных элементов, и производительность очень хорошая, несмотря на мои первоначальные опасения.Он не реализует порядок сортировки, который легко достижим, просто перевернув результат выражения сравнения.

...