Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QML: Lambda function works unexpectedly

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?

like image 925
cmannett85 Avatar asked Mar 06 '15 12:03

cmannett85


1 Answers

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 items 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
like image 183
dtech Avatar answered Sep 16 '22 20:09

dtech