How to bind a delegate item property to top-level property
-
Would it be possible to provide an example of how to bind a property in a delegate item to a top-level property using the recommended method as of Qt 6.3.0 (beta3)?
The following example is contrived, but illustrates how I was shown in the past how to implement this sort of thing.
Here, I want the text value of each of the delegate Text items to update every time the top-level property
root.num
is updated.main.qml
import QtQuick import QtQuick.Controls Rectangle { id: root width: 400 height: 200 color: "beige" property int num: 0 Row { id: row spacing: 10 anchors.centerIn: parent Repeater { id: rpt model: 5 delegate: Text { id: txt text: root.num.toString() } } } Button { id: btn anchors.bottom: root.bottom anchors.horizontalCenter: root.horizontalCenter text: "Click!" onClicked: { console.log( root.num ) root.num += 2 } } }
But when I run qmllint on it, I get these diagnostic messages:
$ qmllint --compiler warning main.qml Warning: main.qml:23:11: Unqualified access text: root.num.toString() ^^^^ Warning: main.qml:23:11: Could not compile binding for text: Cannot access value for name root text: root.num.toString() ^^^^
Outside of that, the code seems to work just fine.
I am using Qt 6.3.0 beta3 on Kubuntu Linux 21.10.
Thanks very much in advance!
-
The issues qmllint has caught are very valid and result from referencing an outer-level variable that could be (though not in this case) defined in another file. As I understand it, Qt recommends against this given the potential for runtime type mismatches that may be external in nature. This would also prevent the QML compiler from compiling such code.
In many cases, this problem can be alleviated using a chain of required properties. However, I can find no way of "tunnelling" a property binding through a delegate using that approach.
-
@Bob64 said in How to bind a delegate item property to top-level property:
OK, sorry, it looks like I did not read your original message carefully enough. Nevertheless, I still don 't see why this is a problem. root seems pretty unambiguous in the scope hierarchy.
technically yes, but if the shown file would not have an Element with the id:root, qml actually traverses the the parents/child tree upwards, until your very own main.qml file or until it finds an item with the id:root
I ran into this, very hard and confusing to debug.
-
@J-Hilk I feel that I am still missing something.
"... if the shown file would not have an Element with the id:root ..."
Yes, I can understand why a warning would be shown in that case, but in this case it does have an element with id
root
, so why warn about it? Does qmllint not take any account of the context? -
In at least my case i get rid of the warning with kind of nonsense declaration:
delegate: Text { id: txt readonly property var root: root text: root.num.toString() }
EDIT: BUT BEWARE! As @jbrend wrote later - and i noticed later - this does not work in real application; the qmllint is happy but the real application does not work! Is no solution!
-
@supa5000 I tried your solution using Qt 6.3.0 (released), but I get the following qmllint warning:
$ qmllint --compiler warning main.qml Warning: main.qml:24:28: Could not compile binding for text: Cannot load property num from QVariant of ??::root with type QVariant. text: root.num.toString() ^^^
If I just run the file using qml, I get the following output to the console. The app window shows, but the Text items are not visible.
$ qml main.qml file:///srv/tests/qt/TunnelProp/main.qml:21:23: QML QQuickText*: Binding loop detected for property "root" file:///srv/tests/qt/TunnelProp/main.qml:21:23: QML QQuickText*: Binding loop detected for property "root" file:///srv/tests/qt/TunnelProp/main.qml:21:23: QML QQuickText*: Binding loop detected for property "root" file:///srv/tests/qt/TunnelProp/main.qml:21:23: QML QQuickText*: Binding loop detected for property "root" file:///srv/tests/qt/TunnelProp/main.qml:21:23: QML QQuickText*: Binding loop detected for property "root" file:///srv/tests/qt/TunnelProp/main.qml:24: TypeError: Cannot read property 'num' of undefined file:///srv/tests/qt/TunnelProp/main.qml:24: TypeError: Cannot read property 'num' of undefined file:///srv/tests/qt/TunnelProp/main.qml:24: TypeError: Cannot read property 'num' of undefined file:///srv/tests/qt/TunnelProp/main.qml:24: TypeError: Cannot read property 'num' of undefined file:///srv/tests/qt/TunnelProp/main.qml:24: TypeError: Cannot read property 'num' of undefined
If I use the type of the
root
item (Rectangle instead ofvar
), I get a similar but expanded warning:$ qmllint --compiler warning main.qml Warning: main.qml:24:28: Property "num" not found on type "QQuickRectangle" text: root.num.toString() ^^^ Warning: main.qml:24:28: Could not compile binding for text: Cannot load property num from QQuickRectangle of ??::root with type QQuickRectangle. text: root.num.toString() ^^^
The warning about QuickRectangle not having a
num
property makes sense, since my subclassed version derived from it has the property.In order to create compilable QML, I think we are supposed to be getting away from relying on the old context-based resolution mechanisms and also away from using
var
(QVariant) as a type, but I don't know what we are supposed to be doing in such a case from within a delegate.Does anyone have any updated information?
-
@jbrend My bad, you are right. It did pass qmllint and our test-cases - but indeed as you wrote, it does not work.
i will edit my old answear to include warning that dont do this.
No doing it like
readonly property var rootLoop: root text: rootLoop.num.toString()
makes the main application to work again, but guess what qmllint says: Unqualified access (on the 'root') part ..
-
I am keeping monologue here but another way to fix this is by "injecting" the upper scope variables via modelData;
function packItems(namespace, dataArray) { var ret = []; for (var loop = 0; loop < dataArray.length; loop++) { ret.push([namespace, dataArray[loop]]); } return ret; }
And then inject:
Repeater: model: packItems( {"root":root }, myDataArray ) delegate: Text { id: txt required property var modelData readonly property rootLoop: modelData[0]["root"]
This is fishy at most, but works and passes the qml lint; i kind of feel like ill doing this; in my case i need the upper level component information for:
- delegate item calls root.signalEmitFunction -- when certain button of the delegate item is clicked
- delegete item width depends on upper level item width
- delegate item position depends on upper level item properties (slider tickmark labels position to be precise)
- delegate item gives focus to upper level when released (i guess focus-manager or whatever could do this also)
-
Your packItems could be written like that instead:
return dataArray.map(data => {data: data, namespace: namespace});
and used like so :
readonly property QtObject rootLoop: modelData.namespace.root