1

I would like to be able to bind to a property of an item generated by Repeater to do something with it, e.g. to show its coordinates. For that purpose I am using itemAt() like this:

ListModel { id: modelNodes ListElement { name: "Banana"; x: 100; y: 200 } ListElement { name: "Orange"; x: 150; y: 100 } } Repeater { id: foo model: modelNodes Rectangle { x: model.x; y: model.y width: textBox.implicitWidth + 20 height: textBox.implicitHeight + 20 color: "red" Drag.active: true Text { id: textBox anchors.centerIn: parent color: "white" text: model.name + ": " + foo.itemAt(index).x } MouseArea { anchors.fill: parent drag.target: parent } } } Text { id: moo Binding { target: moo property: "text" value: foo.itemAt(0).x + " -> " + foo.itemAt(1).x } } 

Inside the delegate this works fine, but when I attempt to use it outside of the Repeater (i.e. to bind moo's text to it), I get the following error:

TypeError: Cannot read property 'x' of null

How to fix this?

4
  • My guess would be that at the time it tries to do the binding, the Repeater hasn't constructed its items yet. Maybe try doing the binding within Component.onCompleted? Commented Aug 18, 2021 at 17:55
  • @JarMan, in Component.onCompleted I can write an expression, not a binding. The text is indeed shown, but not updated when I drag the rectangles. Commented Aug 18, 2021 at 18:57
  • 2
    You can make a binding in Component.onCompleted using Qt.binding(). See the docs. Commented Aug 18, 2021 at 19:00
  • Bingo! I've learned something new. :) Thank you very much! I would be glad to accept your answer, when you write it. Commented Aug 18, 2021 at 19:04

2 Answers 2

3

The reason the Binding object doesn't work outside of the Repeater is because the Repeater has not constructed its items yet when the binding is being evaluated. To fix this, you can move the binding into the Component.onCompleted handler. Then just use the Qt.binding() function to do binding from javascript (docs).

Text { Component.onCompleted: { text = Qt.binding(function() { return foo.itemAt(0).x + ", " + foo.itemAt(1).x }) } } 
Sign up to request clarification or add additional context in comments.

Comments

0

You don't.
(or more precisely, you shouldn't)

Delegates shouldn't store state or data, just display it or be able to interact with it. In your case what you are after is the data stored in the model.

Your solution should be to modify your model in your delegates and get the data from your model if you want.

I've created a small example of what I mean:

import QtQuick 2.15 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 Window { visible: true width: 800 height: 640 ListModel { id: modelNodes ListElement { name: "Banana"; x: 50; y: 50 } ListElement { name: "Orange"; x: 50; y: 100 } } Row { anchors.centerIn: parent spacing: 1 Repeater { model: 2 // display 2 copy of the delegates for demonstration purposes Rectangle { color: "transparent" width: 300 height: 300 border.width: 1 Repeater { id: foo model: modelNodes Rectangle { x: model.x; y: model.y width: textBox.implicitWidth + 20 height: textBox.implicitHeight + 20 color: "red" DragHandler { dragThreshold: 0 } onXChanged: model.x = x // modify model data when dragging onYChanged: model.y = y Text { id: textBox anchors.centerIn: parent color: "white" text: model.name + ": " + foo.itemAt(index).x } } } } } } Instantiator { model: modelNodes delegate: Binding { // the hacky solution to the initial problem. target: myText property: model.name.toLowerCase() + "Point" value: Qt.point(model.x, model.y) } } Text { id: myText property point bananaPoint property point orangePoint anchors.right: parent.right text: JSON.stringify(bananaPoint) } ListView { anchors.fill: parent model: modelNodes delegate: Text { text: `${model.name} - (${model.x} - ${model.y})` } } } 

I've used a hacky solution to your initial problem with an Instantiator of Bindings, I don't really understand the usecase so that might not be the ideal solution. Here it creates a binding for every element of your model but that's weird. If you only want data from your first row, you may want to do when: index === 0 in the Binding. I've created a third party library to get a cleaner code : https://github.com/okcerg/qmlmodelhelper

This will result in the following code for your outside Text (and allowing you to get rid of the weird Instantiator + Binding part):

Text { readonly property var firstRowData: modelNodes.ModelHelper.map(0) text: firstRowData.x + ", " + firstRowData.y } 

Note that my point about not storing data in delegates (or accessing them from outside) still stands for whatever solution you chose.

3 Comments

Why shouldn't I? I have shown a perfectly valid use case, when the delegate is dragable. How to access the current position otherwise? You certainly don't want this data stored in the model, because of the abundance of updates, which will take place when you drag the delegate around.
Because the delegate can be deleted at any time (like when you scroll away from it in view). For dragging, best practice is to put the data into drag event itself (ideally using mimeData).
@DavidNovák, I am trying to replicate QGraphicsScene with Qml and QGraphicsItem stores its position. Do I miss something there?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.