I'm having difficulty finding the idiomatic way to access ListView
's model from its delegate in QML.
Consider the following fairly simple custom delegate that supports a checkbox near the item and tracks the currently chosen item in the list. (To clarify: the checkbox selection and list selection are independent.)
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)
}
}
This is working code though making onToggle
work was particularly troublesome. In the end I found this magical ListView.view.model
rune to access the underlying model in the Qt docs and figured out I can prepend the delegate ID to it.
It seems to be too verbose to be correct for the task delegates were created for though.
The majority of examples online hardcode this value to a particular model which violates Delegate and Model concept separation and I don't want to go this route. I also saw just model
being used (the last listing in the linked docs), but it doesn't work for me even if I require property ListModel model
for it in the delegate.
So the question is whether there is a simpler way.
Here's a sample list and model to test the delegate:
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
}
}
EDIT. There are actually two good solutions, a short one and a more verbose but foolproof.
I could drop all required property declarations and use
Label {
// ...
text: name
// ...
}
CheckBox {
// ...
checked: selected
onToggled: selected = checked
}
I couldn't come to this solution because my initial design had the model property named checked
and then it's kind of unclear how this approach can be applied then.
In case of name collisions it's possible to make a required model
property and then it's bound to the corresponding row of the list model:
Rectangle {
id: wrapper
required property var model
// ...
Label {
// ...
text: model.name
// ...
}
CheckBox {
// ..
checked: model.selected
onToggled: model.selected = checked
}
}
The second options seems to be preferable, so I'll go with it.
There are several ways to reach the model from the delegate. It depends mainly on the type of model that the ListView
is using.
Commonly, I prefer to explicitly mark the model property as required using required property var model
, especially if you define other properties as required. Otherwise, you can omit it.
For example, in your case you can define a delegate in this way:
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
}
}
}
In this case, I initialize and bind the checked
property with model.selected
. When the signal onCheckedChanged
is emitted, I update the model too.
If an external entity (the timer in the previous example) changes the model value, also the checked state will be updated.
The bad part of this approach is the recurrence of the bindings:
onCheckedChanged
signal is emittedmodel.selected
propertyselected
property changedCheckDelegate.checked
evaluates the model.selected
property due to the binding, but in this case, they're equal and no other signals will be emittedPoint 4 is unnecessary but occurs due to a CheckBox/CehckDelegate implementation.
The best approach should be: users click on the delegate, a clicked signal is emitted and the checked state is not updated. The signal updates the model value that, thanks to the bind, updates the checked property too.
To obtain that, you have to implement your custom control