Strange binding behavior when using binding on array's element



  • I have an Item with a property. This property contains array of javascript objects. Each javascript object has properties.
    When I set binding for one of object's properties to some variable and its (variable) value changes triggering the binding then all properties in the array get reevaluated.

    Consider the following code:

    import QtQuick 2.4
    import QtQuick.Controls 1.3
    
    ApplicationWindow {
        id: container
    
        width: 640
        height: 480
    
        property int clicksCounter: 0
        property string name : "default name"
    
        Item {
            id: testObject
            property var myArray : [{
                name : container.name,
                boolFlag : false
            }]
        }
    
        Rectangle {
            x: 10
            y: 10
    
            width: 100
            height: 100
            color: "red"
    
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    container.clicksCounter++
    
                    console.log("CLICK #" + container.clicksCounter + "[RED SQUARE] : Changing value for property container.name and setting true to 'boolFlag' property of first array's element\n")
                    var element = testObject.myArray[0]
    
                    console.log("CLICK #" + container.clicksCounter + "[RED SQUARE] : [BEFORE] testObject.myArray[0].name: " + element.name + ', testObject.myArray[0].boolFlag: ' + element.boolFlag)
                    container.name = "new name"
                    element.boolFlag = true
                    console.log("CLICK #" + container.clicksCounter + "[RED SQUARE] : [AFTER] testObject.myArray[0].name: " + element.name + ', testObject.myArray[0].boolFlag: ' + element.boolFlag + "\n")
                }
            }
        }
    
        Rectangle {
            x: 120
            y: 10
    
            width: 100
            height: 100
            color: "blue"
    
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    container.clicksCounter++
    
                    var element = testObject.myArray[0]
                    console.log("CLICK #" + container.clicksCounter + "[BLUE SQUARE] : testObject.myArray[0].name: " + element.name + ', testObject.myArray[0].boolFlag: ' + element.boolFlag + "\n")
                }
            }
        }
    }
    

    Clicking red square changes the value of array, clicking blue square shows current boolean value of the flag.
    Here are the results of clicking the squares:

    (1)
    qml: CLICK #1[BLUE SQUARE] : testObject.myArray[0].name: default name, testObject.myArray[0].boolFlag: false

    (2)
    qml: CLICK #2[RED SQUARE] : Changing value for property container.name and setting true to 'boolFlag' property of first array's element

    qml: CLICK #2[RED SQUARE] : [BEFORE] testObject.myArray[0].name: default name, testObject.myArray[0].boolFlag: false
    qml: CLICK #2[RED SQUARE] : [AFTER] testObject.myArray[0].name: default name, testObject.myArray[0].boolFlag: true

    (3)
    qml: CLICK #3[BLUE SQUARE] : testObject.myArray[0].name: new name, testObject.myArray[0].boolFlag: false

    Why is testObject.myArray[0].boolFlag equals FALSE on click 3 if its value was set to TRUE on click 2?


  • Moderators

    Hi @geniuss

    var element = testObject.myArray[0]
    element.boolFlag = true
    

    Here you are updating the copy and not that object in array. Replace element with testObject.myArray[0] and you should notice the update.



  • Indeed, you're correct. This example was supposed to be a simulation of a problem from a real project which is too big to be posted in full here.
    But this example is wrong. I will make another example once I figure out how to reproduce the problem.



  • Keep in mind that bindings to "var" properties may not work as expected since they don't emit "changed" signals when they're modified. You might need to manually call myArrayChanged() after modifying it for the binding to work as expected.



  • Okay, I managed to reproduce it. But for this I had to add C++ part.

    C++ code is this:

    // main.cpp
    #include <QGuiApplication>
    #include <QQuickWindow>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    
    class Test : public QObject
    {
    
        Q_OBJECT
            Q_PROPERTY(QString emptyString READ getEmptyString NOTIFY valueChanged)
            QString getEmptyString(){ return ""; }
    
    public:
        Test(QObject* parent = 0) : QObject(parent) {}
    
        Q_INVOKABLE void triggerNotify()
        {
            valueChanged();
        }
    
    signals:
        void valueChanged();
    };
    
    int main(int argc, char *argv[])
    {
        QGuiApplication app(argc, argv);
        
        QQmlApplicationEngine engine;
        Test* test = new Test(&engine);
        engine.rootContext()->setContextProperty("__test__", (QObject*)test);
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, [](QObject* rootObject) {
            QQuickWindow *appWindow = qobject_cast<QQuickWindow *>(rootObject);
            appWindow->show();
        });
    
        engine.load(QUrl(QStringLiteral("qrc:/ui/main.qml")));
        return app.exec();
    }
    

    QML code is this:

    // main.qml
    import QtQuick 2.4
    import QtQuick.Controls 1.3
    
    ApplicationWindow {
        id: container
    
        width: 640
        height: 480
    
        property int clicksCounter: 0
    
        Item {
            id: testObject
            property var myArray : [{
                name : "CustomName" + __test__.emptyString,
                boolFlag : false
            }]
        }
    
        Rectangle {
            x: 10
            y: 10
    
            width: 100
            height: 100
            color: "red"
    
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    container.clicksCounter++
    
                    console.log("CLICK #" + container.clicksCounter + "[RED SQUARE] : Set testObject.myArray[0] to TRUE\n")
                    testObject.myArray[0].boolFlag = true
                    console.log("CLICK #" + container.clicksCounter + "[RED SQUARE] : DONE\n")
                }
            }
        }
    
        Rectangle {
            x: 120
            y: 10
    
            width: 100
            height: 100
            color: "blue"
    
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    container.clicksCounter++
    
                    console.log("CLICK #" + container.clicksCounter + "[BLUE SQUARE] : Triggering notify by calling C++ <Test::triggerNotify> method \n")
                    console.log("CLICK #" + container.clicksCounter + "[BLUE SQUARE] : [BEFORE] testObject.myArray[0].name: " + testObject.myArray[0].name + ', testObject.myArray[0].boolFlag: ' + testObject.myArray[0].boolFlag)
                    __test__.triggerNotify()
                    console.log("CLICK #" + container.clicksCounter + "[BLUE SQUARE] : [AFTER] testObject.myArray[0].name: " + testObject.myArray[0].name + ', testObject.myArray[0].boolFlag: ' + testObject.myArray[0].boolFlag)
                }
            }
        }
    }
    

    Here is what I get:

    qml: CLICK #1[RED SQUARE] : Set testObject.myArray[0] to TRUE
    qml: CLICK #1[RED SQUARE] : DONE

    qml: CLICK #2[BLUE SQUARE] : Triggering notify by calling C++ Test::triggerNotify method

    qml: CLICK #2[BLUE SQUARE] : [BEFORE] testObject.myArray[0].name: CustomName, testObject.myArray[0].boolFlag: true
    qml: CLICK #2[BLUE SQUARE] : [AFTER] testObject.myArray[0].name: CustomName, testObject.myArray[0].boolFlag: false

    So what happens here is that after I set testObject.myArray[0].boolFlag from FALSE to TRUE and call test.triggerNotify() method my flag automatically resets to its initial value. Same goes if any other type used - int, string, etc. Why does this happen?

    [UPDATE] QT 5.5 is used, Visual Studio 2013 Update 4 x32.


  • Moderators

    @geniuss Here is you answer:
    http://doc.qt.io/qt-5/qml-var.html#change-notification-semantics
    Edit: But that doesn't justify why it becomes false O_o



  • Exactly, there is no connection between "change notification semantics" for "var" and my problem.


  • Moderators

    @geniuss I think that is the way how it works. A change of that string will trigger reevaluation of the property myArray and thus causing the boolFlag to get its original value i.e false.



  • This is madness then :) It should not work that way normally in my understanding.
    Perhaps I'll post this as a bug if no one gives me a suitable answer.


  • Moderators

    @geniuss I would suggest you to ask at Qt Interest Mailing List. You can get an answer directly from Qt Engineers regarding these internals.



  • @p3c0 I tried but that didn't work. Folks on Stack Overflow don't know either. I got no other choice :
    https://bugreports.qt.io/browse/QTBUG-47407


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.