Strange binding behavior when using binding on array's element
-
wrote on 18 Jul 2015, 11:30 last edited by 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 elementqml: 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: falseWhy is testObject.myArray[0].boolFlag equals FALSE on click 3 if its value was set to TRUE on click 2?
-
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 elementqml: 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: falseWhy is testObject.myArray[0].boolFlag equals FALSE on click 3 if its value was set to TRUE on click 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
withtestObject.myArray[0]
and you should notice the update. -
wrote on 18 Jul 2015, 12:46 last edited by
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. -
wrote on 18 Jul 2015, 20:43 last edited by
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.
-
wrote on 20 Jul 2015, 12:26 last edited by 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] : DONEqml: 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: falseSo 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.
-
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] : DONEqml: 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: falseSo 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.
@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 -
wrote on 21 Jul 2015, 10:13 last edited by
Exactly, there is no connection between "change notification semantics" for "var" and my problem.
-
Exactly, there is no connection between "change notification semantics" for "var" and my problem.
@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 theboolFlag
to get its original value i.efalse
. -
wrote on 21 Jul 2015, 13:54 last edited by
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. -
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.@geniuss I would suggest you to ask at Qt Interest Mailing List. You can get an answer directly from Qt Engineers regarding these internals.
-
wrote on 23 Jul 2015, 14:48 last edited by
@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/11