Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Strange binding behavior when using binding on array's element
QtWS25 Last Chance

Strange binding behavior when using binding on array's element

Scheduled Pinned Locked Moved QML and Qt Quick
qmlarraybinding
11 Posts 3 Posters 5.4k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • G Offline
    G Offline
    geniuss
    wrote on last edited by geniuss
    #1

    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?

    p3c0P 1 Reply Last reply
    0
    • G geniuss

      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?

      p3c0P Offline
      p3c0P Offline
      p3c0
      Moderators
      wrote on last edited by p3c0
      #2

      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.

      157

      1 Reply Last reply
      0
      • G Offline
        G Offline
        geniuss
        wrote on last edited by
        #3

        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.

        1 Reply Last reply
        0
        • X Offline
          X Offline
          xargs1
          wrote on last edited by
          #4

          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.

          1 Reply Last reply
          0
          • G Offline
            G Offline
            geniuss
            wrote on last edited by geniuss
            #5

            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.

            p3c0P 1 Reply Last reply
            0
            • G geniuss

              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.

              p3c0P Offline
              p3c0P Offline
              p3c0
              Moderators
              wrote on last edited by p3c0
              #6

              @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

              157

              1 Reply Last reply
              0
              • G Offline
                G Offline
                geniuss
                wrote on last edited by
                #7

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

                p3c0P 1 Reply Last reply
                0
                • G geniuss

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

                  p3c0P Offline
                  p3c0P Offline
                  p3c0
                  Moderators
                  wrote on last edited by
                  #8

                  @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.

                  157

                  1 Reply Last reply
                  0
                  • G Offline
                    G Offline
                    geniuss
                    wrote on last edited by
                    #9

                    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.

                    p3c0P 1 Reply Last reply
                    0
                    • G geniuss

                      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.

                      p3c0P Offline
                      p3c0P Offline
                      p3c0
                      Moderators
                      wrote on last edited by
                      #10

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

                      157

                      1 Reply Last reply
                      0
                      • G Offline
                        G Offline
                        geniuss
                        wrote on last edited by
                        #11

                        @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

                        1 Reply Last reply
                        0

                        • Login

                        • Login or register to search.
                        • First post
                          Last post
                        0
                        • Categories
                        • Recent
                        • Tags
                        • Popular
                        • Users
                        • Groups
                        • Search
                        • Get Qt Extensions
                        • Unsolved