I thought QML supported lambda functions because of JavaScript's support of anonymous functions and the fact that functions are first class objects, but they don't work how I expected. Take this code:
Item {
property var items: []
function handler( item ) {
console.log( item );
}
Component.onCompleted: {
for ( var i = 0; i < 3; ++i ) {
var item = someObj.createObject();
item.someValueChanged.connect( function() {
handler( item ); } );
items.push( item );
console.log( "Adding:", item );
}
}
Component {
id: someObj
Item {
property bool someValue: false
Timer {
running: true
onTriggered: {
parent.someValue = true;
}
}
}
}
}
I'm trying to use the lambda function() { handler( item ); }
so that when the someObj::someValueChanged
signal is emitted the emitting item is passed to the handler( item )
function.
I assumed that each loop would create a new instance of the lambda and that the item
reference would carry the reference of the someObj
instance created in that loop (i.e. item
would be captured by the lambda). But that doesn't seem to be the case as the output is:
qml: Adding: QQuickItem_QML_1(0x2442aa0)
qml: Adding: QQuickItem_QML_1(0x2443c00)
qml: Adding: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)
qml: QQuickItem_QML_1(0x2445370)
As you can see, either the whole function is being replaced on each loop or just the item
reference, so that ultimately only the last created someObj
is referred to. Can someone explain to me why lambdas (if that's even what it is) don't work the way I expect? And is this a QML issue, or a general JavaScript one?
Try something like this:
item.someValueChanged.connect(function(capture) {
return function() {
handler(capture)}
}(item))
Intuitive, right? :D
If JS used "block scope" there would be 3 different item
s being referenced for each loop iteration, and it would "work as expected". But with "function scope" there is only one item
referenced, and it references its final value, thus the need to use that hack to "capture" each value in time.
Just to explain it, in case it isn't immediately obvious, the signal is connected to a handler that is arbitrated by a function which captures the parameter value at the particular time as a discrete object, which is used to feed to the handler.
Hopefully, the incipient Qt 5.12 release will remedy that with the introduction of support for let
, a.k.a block scoped variables.
Update: I can confirm that using 5.12, it now works as expected:
let item = someObj.createObject(); // will produce 3 distinct obj refs
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With