Доступ к модели Qt Quick ListView из вложенных элементов в пользовательском делегате

Мне трудно найти идиоматический способ доступа к модели ListView из ее делегата в QML.

Рассмотрим следующий довольно простой пользовательский делегат, который поддерживает флажок рядом с элементом и отслеживает текущий выбранный элемент в списке. (Для пояснения: выбор флажка и выбор списка независимы.)

Rectangle {
    id: wrapper

    required property int index
    required property string name
    required property bool selected

    width: ListView.view.width
    height: 32
    radius: 3
    color: "transparent"

    Label {
        anchors.left: parent.left
        anchors.right: selectedBox.left
        anchors.verticalCenter: parent.verticalCenter
        anchors.margins: 5
        text: wrapper.name

        MouseArea {
            anchors.fill: parent
            onClicked: wrapper.ListView.view.currentIndex = wrapper.index
        }
    }

    CheckBox {
        id: selectedBox

        anchors.right: parent.right
        anchors.verticalCenter: parent.verticalCenter
        checked: wrapper.selected

        onToggled: wrapper.ListView.view.model.setProperty(wrapper.index, "selected", checked)
    }
}

Это рабочий код, хотя заставить onToggle работать было особенно сложно. В конце концов я нашел эту волшебную ListView.view.model руну для доступа к базовой модели в документации Qt и понял, что могу добавить к ней идентификатор делегата.

Однако это кажется слишком многословным, чтобы быть правильным для задач, для которых были созданы делегаты.

Большинство онлайн-примеров жестко запрограммировали это значение для конкретной модели, что нарушает разделение концепций делегата и модели, и я не хочу идти по этому пути. Я также видел, как используется только model (последний список в связанных документах), но у меня это не работает, даже если я require property ListModel model для этого в делегате.

Итак, вопрос в том, есть ли более простой способ.

Вот пример списка и модели для тестирования делегата:

ListView {
    // ...
    model: myModel
    delegate: myDelegate
    highlight: Rectangle {
        color: "lightsteelblue"
        radius: 5
    }
    focus: true
}

ListModel {
    id: myModel

    ListElement {
        name: "Item 1"
        selected: false
    }

    ListElement {
        name: "Item 2"
        selected: false
    }
}

РЕДАКТИРОВАТЬ. На самом деле есть два хороших решения: короткое и более многословное, но надежное.

Я мог бы отказаться от всех необходимых объявлений свойств и использовать

    Label {
        // ...
        text: name
        // ...
    }

    CheckBox {
        // ...
        checked: selected
        onToggled: selected = checked
    }

Я не смог прийти к этому решению, потому что в моем первоначальном проекте было свойство модели с именем checked, и тогда было непонятно, как этот подход можно применить.

В случае конфликта имен можно создать обязательное свойство model, а затем привязать его к соответствующей строке модели списка:

Rectangle {
    id: wrapper

    required property var model
    // ...

    Label {
        // ...
        text: model.name
        // ...
    }

    CheckBox {
        // ..
        checked: model.selected
        onToggled: model.selected = checked
    }
}

Второй вариант кажется предпочтительнее, поэтому я выберу его.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
82
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Существует несколько способов получить доступ к модели из делегата. В основном это зависит от типа модели, которую использует ListView.

Обычно я предпочитаю явно отмечать свойство модели как необходимое, используя required property var model, особенно если вы определяете другие свойства по мере необходимости. В противном случае вы можете опустить это.

Например, в вашем случае вы можете определить делегата следующим образом:

Component {
    id: myDelegate

    CheckDelegate { // Need QtQuick.Controls
        required property var model

        width: ListView.view.width
        height: 32

        text: model.name

        checked = model.selected
        onCheckedChanged: model.selected = checked

        Timer { // Emulating an external entity that update the model
            interval: 4000
            onTriggered: parent.model.selected = !parent.model.selected
            running: true
        }
    }
}

В этом случае я инициализирую и связываю свойство checked с model.selected. Когда подается сигнал onCheckedChanged, я тоже обновляю модель.

Если внешний объект (таймер в предыдущем примере) изменит значение модели, проверенное состояние также будет обновлено.

Плохая часть этого подхода — повторение привязок:

  1. Когда пользователь нажимает CheckDelegate, генерируется сигнал onCheckedChanged.
  2. Сигнал вызывает функцию, которая обновляет свойство model.selected.
  3. Модель также излучает сигнал об изменении свойства selected.
  4. CheckDelegate.checked оценивает свойство model.selected из-за привязки, но в этом случае они равны, и никакие другие сигналы выдаваться не будут.

Пункт 4 не нужен, но он возникает из-за реализации CheckBox/CehckDelegate.

Лучший подход должен быть следующим: пользователи нажимают на делегата, генерируется сигнал щелчка и проверенное состояние не обновляется. Сигнал обновляет значение модели, которое, благодаря привязке, также обновляет проверяемое свойство.

Чтобы получить это, вам необходимо реализовать свой собственный элемент управления.

Хммм, это вроде сработало. Я не могу использовать CheckDelegate, так как визуальная подсказка для выбранного в данный момент элемента списка потеряна, но привязка model свойства и назначение model.selected при переключении флажка работает.

Alex Veleshko 05.06.2024 17:46

Могу только пожаловаться, что Qt выбрал для этого model свойства довольно запутанное имя. Я думал, что это относится ко всей модели списка, но, видимо, это только одна его строка. Да...

Alex Veleshko 05.06.2024 17:50

Я согласен с вами. Например, если вы используете ComboBox в качестве делегата, у него уже есть свойство с именем model. В данном случае я определяю новое свойство property var modelItem: ListView.view.model.get(index) (если вы используете ListModel)

Lorenzo Aldrighetti 05.06.2024 18:13

Сигнал, излучаемый при взаимодействии с пользователем: onToggled

GrecKo 11.06.2024 02:43

Хорошо, но после сигнала onToggled элемент управления принудительно меняет свое проверенное состояние. Реализация по умолчанию не позволяет вам управлять запросами переключения.

Lorenzo Aldrighetti 12.06.2024 08:49

Я думаю, ты поступаешь жестко. При использовании ListElements вы можете напрямую использовать его свойства. Нет необходимости использовать ListView.view.model. Вам больше не нужны эти необходимые свойства. Если я попытаюсь заставить ваш пример делегата работать, это будет примерно так:

Rectangle {
        id: wrapper

        property int selectedIndex: ListView.view.currentIndex
        onSelectedIndexChanged: {
            selected = selectedIndex === index
        }

        width: ListView.view.width
        height: 32
        radius: 3
        color: "transparent"

        Label {
            anchors.left: parent.left
            anchors.right: selectedBox.left
            anchors.verticalCenter: parent.verticalCenter
            anchors.margins: 5
            text: name
        }

        CheckBox {
            id: selectedBox

            anchors.right: parent.right
            anchors.verticalCenter: parent.verticalCenter
            checked: selected
        }

        MouseArea {
            anchors.fill: parent
            onClicked: {
                wrapper.ListView.view.currentIndex = index
            }
        }
    }

Я использовал обязательные свойства в документации Qt «В большинстве случаев вам следует использовать обязательные свойства для передачи данных модели вашим делегатам». Я попробовал ваш подход, но «проверено: выбрано» в CheckBox не работает должным образом: после включения флажка базовая модель не обновляется. (Я проверяю это, записывая в console.info всю модель.)

Alex Veleshko 05.06.2024 16:45

Кроме того, способ перемещения MouseArea из Label блокирует ввод с помощью мыши флажков. Однако они по-прежнему доступны через порядок табуляции и пробел.

Alex Veleshko 05.06.2024 16:47

Я не получил роль с selectedIndex, которую ты представил. Мне нужно, чтобы selected был привязан к флажку, а не к выбранной записи списка.

Alex Veleshko 05.06.2024 16:49

Хорошо, если я добавлю onToggled: selected = checked, модель начнет обновляться. Спасибо! Родина тебя не забудет!

Alex Veleshko 05.06.2024 17:41

Другие вопросы по теме

Похожие вопросы

Std::this_thread::sleep_for с пользовательскими ускоренными часами
Автоматическое определение возвращаемого типа std::variant для автоматических функций в C++
C++20, как использовать концепции для проверки перегруженных операторов
Приложение Qt Quick для Android отображает пустой экран, если оно запускается более 1 раза
Почему вызов оператора* для пустого std::optional вызывает неопределенное поведение, если я не сразу получаю доступ к возвращаемому значению?
Возможна ли агрегатная инициализация с помощью пользовательских полей в структуре с элементами, полученными из параметра шаблона, включая эти поля?
Почему «Размещение нового» для классов с виртуальными членами работает только тогда, когда буфер находится в локальном стеке?
Ошибка Flex «пропущен конец буфера» для очень специфических входных данных
Перепишите код объявления переменной C++ через AST
У меня проблема с помещением информации в файл типа json