0

Using Qt 5.6 QML (upgrade is not an option)... I need to pass a structure containing an array of structures between QML and C++ (both ways).

struct arrayStruct {
    bool    someParam;
};

struct someStruct {
    bool    topParam;

    QVector< arrayStruct >    arrayOfStructs;
};

With the following code, I can create an instance of the structure with the array and pass that around, but I can't modify the content of the array. I can't figure out what I'm missing...

        // Create an object in a QML context
        var workObj = objArrayComponent.createObject(mainForm)

        // These things work
        workObj.topParam = true
        workObj.objArray = [ { someParam : true }, { someParam : false }, {someParam : true } ]

        // Neither of the following work...
        // After this call, the value remains false
        workObj.objArray[1].someParam = true;
        // After this call, the length remains 3
        workObj.objArray.push( { someParam : true } )

        // This works
        workObj.objArray = [ { someParam : false }, { someParam : true }, {someParam : false } ]

I also tried a version of the code where I split out the READ/WRITE and created my own getter/setter. I verified the setter was called when the entire workObj.objArray was set, but it was not called for the other changes.

Full code below...

objarray.h (there is no objarray.cpp, MEMBER creates getter/setter):

#include <QObject>
#include <QVariant>

class SomeObj : public QObject
{
    Q_OBJECT
public:
    explicit SomeObj(QObject *parent = nullptr) : QObject(parent) {}

    Q_PROPERTY( bool someParam MEMBER mSomeParam NOTIFY someParamChanged )

signals:

    void someParamChanged();

public slots:

private:
    bool    mSomeParam;
};

class ObjArray : public QObject
{
    Q_OBJECT

    Q_PROPERTY( bool topParam MEMBER mTopParam NOTIFY topParamChanged )
    Q_PROPERTY( QVariantList objArray MEMBER mObjArray NOTIFY objArrayChanged )

public:
    explicit ObjArray(QObject *parent = nullptr) : QObject(parent) {}

signals:

    void topParamChanged();
    void objArrayChanged();

public slots:

private:
    bool            mTopParam;
    QVariantList    mObjArray;
};

main.cpp:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlEngine>
#include <QQmlContext>
#include <QQmlComponent>
#include "objarray.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    qmlRegisterType<ObjArray>("obj.array", 1, 0, "ObjArray");
    qmlRegisterType<SomeObj>("some.obj", 1, 0, "SomeObj");

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

main.qml:

import QtQuick 2.6
import QtQuick.Window 2.2
import obj.array 1.0
import some.obj 1.0

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    MainForm {
        id: mainForm

        Component {
            id: objArrayComponent
            ObjArray {}
        }

        anchors.fill: parent
        mouseArea.onClicked: {
            // Create an object in the form's context
            var workObj = objArrayComponent.createObject(mainForm)

            workObj.topParam = true
            workObj.objArray = [ { someParam : true }, { someParam : false }, {someParam : true } ]

            // So far so good
            // results: true, (3): [true, false, true]
            console.log( "results: " + workObj.topParam + ", " +
                        "(" + workObj.objArray.length + "): [" +
                        workObj.objArray[0].someParam + ", " +
                        workObj.objArray[1].someParam + ", " +
                        workObj.objArray[2].someParam + "] "
                        )

            // This works
            workObj.topParam = false

            // Neither of the following work...
            workObj.objArray[1].someParam = true;
            workObj.objArray.push( { someParam : true } )

            // Value is not changed
            // Array size doesn't change
            // results: false, (3): [true, false, true]

            console.log( "results: " + workObj.topParam + ", " +
                        "(" + workObj.objArray.length + "): [" +
                        workObj.objArray[0].someParam + ", " +
                        workObj.objArray[1].someParam + ", " +
                        workObj.objArray[2].someParam + "] "
                        )

        // This works
        workObj.objArray = [ { someParam : false }, { someParam : true }, {someParam : false } ]

        // results: false, (3): [false, true, false]
        console.log( "results: " + workObj.topParam + ", " +
                    "(" + workObj.objArray.length + "): [" +
                    workObj.objArray[0].someParam + ", " +
                    workObj.objArray[1].someParam + ", " +
                    workObj.objArray[2].someParam + "] "
                    )

            console.log(qsTr('Clicked on background. Text: "' + textEdit.text + '"'))
            Qt.quit()
        }
    }
}

EDIT:
Apparently QML variant arrays can't be modified, and I ran out of time to find a better solution, so I decided to just go with a quick-and-dirty get/set. This is fine for small structs, but scaling to bigger structs will be annoying.
no error checking for compactness...

public:
Q_INVOKABLE void setValue( QString name, int index, bool value )
{
    if( name == "someParam" )
        mStoreIt[index].someParam = value;
}

Q_INVOKABLE bool getValue( QString name, int index )
{
    if( name == "someParam" )
         return mStoreIt[index].someParam;
}

private:
QVector<someObj>  mStoreIt;
Ed K
  • 189
  • 2
  • 9
  • You can easily port a `QVector` to QML as a generic list. The one here can even be created declaratively and directly used as a model: https://stackoverflow.com/a/35161903/991484 – dtech Sep 08 '17 at 21:15
  • That's interesting, but it looks like its purpose is to pass around an object blindly (which ended up being my solution, though not as elegantly done). I was trying to figure out a way to create a nested object array that can be manipulated natively in QML (as in someobject.nested[x] = new otherobject, where all objects are custom constructs but treated as variants). – Ed K Sep 11 '17 at 12:42

1 Answers1

0

According to the documentation Qt site,

While this is a convenient way to store array and map-type values, you must be aware that the items and attributes properties above are not QML objects (and certainly not JavaScript object either) and the key-value pairs in attributes are not QML properties. Rather, the items property holds an array of values, and attributes holds a set of key-value pairs. Since they are stored as a set of values, instead of as an object, their contents cannot be modified individually:

Item {
    property variant items: [1, 2, 3, "four", "five"]
    property variant attributes: { 'color': 'red', 'width': 100 }

    Component.onCompleted: {
        items[0] = 10
        console.log(items[0])     // This will still be '1'!
        attributes.color = 'blue'
        console.log(attributes.color)     // This will still be 'red'!
    }
}
GAVD
  • 1,977
  • 3
  • 22
  • 40
  • Well, that's disappointing, I wish they mentioned it [here](http://doc.qt.io/qt-5/qtqml-cppintegration-data.html#qvariantlist-and-qvariantmap-to-javascript-array-and-object). I was hoping implementing a custom type would bypass this sort of thing, but I think I have a work-around, just have to experiment a bit. I'll post that once I confirm. Thanks. – Ed K Sep 08 '17 at 17:48