3

I have an Item with a property. This property contains an array of JavaScript objects wich in turn contain other 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 are reset to their initial values. I've created a small demo to show the problem.

C++ code:

// main.cpp
#include <QGuiApplication>
#include <QQuickWindow>
#include <QQmlApplicationEngine>
#include <QQmlContext>

class Test : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString value READ value NOTIFY valueChanged)
public:
    Test(QObject* parent = 0) : QObject(parent) {}
    QString value() const { return ""; }
    Q_SIGNAL void valueChanged();   
};

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    Test test;
    engine.rootContext()->setContextProperty("__test__", &test);
    engine.load(QUrl(QStringLiteral("qrc:/ui/main.qml")));
    return app.exec();
}

#include "main.moc"

QML code:

// main.qml
import QtQuick 2.4
import QtQuick.Controls 1.3

ApplicationWindow {
    id: container
    visible: true

    width: 640
    height: 480

    property int clicksCounter: 0

    Item {
        id: testObject
        property var myArray : [{
            name : "CustomName" + __test__.value,
            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::valueChanged> 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__.valueChanged()
                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::valueChanged> 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.valueChanged() method, my flag automatically resets to its initial value. Same goes for any other type used - int, string, etc.

Why does this happen?

Visual Studio has update 4 installed.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
geniuss99
  • 101
  • 5
  • 1
    I've simplified your test case a bit. The `visible` property lets you show the window on startup. All signals are invokable by definition, so you don't need to forward them to another invokable method. It might be a Qt bug in the binding implementation in QML... – Kuba hasn't forgotten Monica Jul 21 '15 at 21:06
  • I've posted this as a bug: https://bugreports.qt.io/browse/QTBUG-47407 – geniuss99 Aug 07 '15 at 17:24

1 Answers1

1

Very tricky, but this is intended behavior of QML. What you're suffering from is a conflict in binding assignment vs. procedural assignment, which is a common pitfall of QML.

First, you've set up a binding to myArray that (in effect) says that any time __test__.value changes, re-assign a literal array to myArray. And later you subvert that binding by procedurally modifying that array. Remember that the literal array is really JavaScript code that's being evaluated every time a signal fires in the binding.

So, what you're seeing in the last console.log is not that the bool has been reverted, but instead that the entire myArray has been reset to a new value, rebuilt from the binding.

What you really want is a new bool property that you can assign to, and your literal array gets bound to this property. Like this:

property bool myBoolFlag: false
property var myArray : [{
        name : "CustomName" + __test__.value,
        boolFlag : myBoolFlag
    }]

Then, when you assign to myBoolFlag the array gets rebuilt. And when you fire valueChanged the array also gets rebuilt, but this time the bool value is pulled from the property and has the right value.

Chris Dolan
  • 8,905
  • 2
  • 35
  • 73
  • If this is true then isn't it an architectural flaw in qml's binding mechanism? Because I want to set binding to array's element (or even to some internal structures of such element if it's JS object) not to `myArray` itself. Why should it be a problem to provide such support in qml? Your suggested fix via external variable `myBoolFlag` is actually not usable for modifiable arrays with binding containing more than one element. And thus the whole binding mechanism becomes unusable for such arrays. – geniuss99 Aug 12 '15 at 09:10
  • Flaw? I wouldn't go that far, but to each his own. The binding system is not meant for complex generic objects like arrays. Consider wrapping your array in a QML class which has an explicit signal for when that array changes, and do not use a binding to store the array. Or use a C++ class to hold your complex data structure, so you have complete control over its signal/slot behavior. – Chris Dolan Aug 12 '15 at 13:49