c++qtqmlqlistqproperty

QList<QList<QString>> passed into QML


I'm trying to pass a 2d QList as a Q_PROPERTY into QML, however, inside QML and i am unable to actually access any of the information.

some code:

c++: the q_property get populated by a q_invokable function in the constructor:

void Class::createNewGameArray(){
QList<QList<QString>> testArray;

for( int i = 0; i < _intervals.size(); ++i) {
    QList<QString> innerArray;
    testArray.append(innerArray);
        testArray[i].append(_intervals[i]);
        testArray[i].append("Audio");
}
for( int i = 0; i < _intervals.size(); ++i) {
    QList<QString> innerArray;
    testArray.append(innerArray);
        testArray[i+12].append(_intervals[i]);
        testArray[i+12].append("Text");
}
 std::random_shuffle(testArray.begin(),testArray.end());
Class::setGameArray(testArray);
emit gameArrayChanged(_newGameArray);

which returns this:

(("M7", "Text"), ("M3", "Text"), ("m3", "Text"), ("M6", "Audio"), ("TT", "Audio"), ("P4", "Text"), ("m7", "Audio"), ("m2", "Text"), ("m6", "Audio"), ("m6", "Text"), ("M7", "Audio"), ("P5", "Text"), ("P4", "Audio"), ("m2", "Audio"), ("M2", "Audio"), ("M3", "Audio"), ("P5", "Audio"), ("m3", "Audio"), ("M6", "Text"), ("TT", "Text"), ("m7", "Text"), ("Oct", "Audio"), ("Oct", "Text"), ("M2", "Text"))

exactly what i want.

i set the rootContext like so in main.cpp:

Class object;

QQmlApplicationEngine engine;
QQmlContext* context = engine.rootContext();

context->setContextProperty("object", &object);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

however, inside qml i only get

qml: QVariant(QList<QList<QString> >)

and am unable to actually do anything with it.

My goal, ideally, would be to be able to access the 2d qlist from qml in this manner:

object.gameArray[0][1] // return "Text"

I'm able to do this with regular QLists (without the 2d). Any help would be greatly appreciated!


Solution

  • QML does not inherently understand QLists, so in general it is not possible to pass in a QList of any type T and have QML able to access the items inside the list.

    However, the QML engine does have built in support for a few specific types of QList:

    Therefore if you can construct your list of lists using any combination of the 3 types above, then you can have a working solution. In your use case I would suggest the following construction:

    QList<QVariant(QStringList)>

    A final note before we try it... Just because this will work, it does not necessarily mean that it is a good idea. The QList contents are copied to Javascript arrays at runtime, and therefore any minor updates to any of the lists from the C++ will cause the entire list to be reconstructed as a new Javascript array, which could be expensive.

    Now, let's try it...

    myclass.h

    #ifndef MYCLASS_H
    #define MYCLASS_H
    #include <QStringList>
    #include <QVariant>
    
    class MyClass : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QList<QVariant> variantList READ variantList NOTIFY variantListChanged)
    
    public:
        explicit MyClass(QObject *parent = nullptr) : QObject(parent),
            m_variantList({
                          QStringList({ "apple", "banana", "coconut" }),
                          QStringList({ "alice", "bob", "charlie" }),
                          QStringList({ "alpha", "beta", "gamma" })
            }) { }
    
        QList<QVariant> variantList() const { return m_variantList; }
    
    signals:
        void variantListChanged();
    
    public slots:
    
    private:
        QList<QVariant> m_variantList;
    };
    
    #endif // MYCLASS_H
    

    main.qml

    import QtQuick 2.7
    import QtQuick.Controls 2.0
    
    ApplicationWindow {
        visible: true
        width: 640
        height: 480
    
        Column {
            id: column
    
            // will add the strings here from the handler below
        }
    
        Component.onCompleted: {
            console.log("variantList length %1".arg(myClass.variantList.length))
    
            for (var i = 0; i < myClass.variantList.length; i++) {
    
                console.log("stringList %1 length %2".arg(i).arg(myClass.variantList[i].length))
    
                for (var j = 0; j < myClass.variantList[i].length; j++) {
                    // print strings to the console
                    console.log("variantList i(%1), j(%2) = %3".arg(i).arg(j).arg(myClass.variantList[i][j]))
    
                    // add the strings to a visual list so we can see them in the user interface
                    Qt.createQmlObject('import QtQuick 2.7; Text { text: "i(%1), j(%2) = %3" }'.arg(i).arg(j).arg(myClass.variantList[i][j]), column)
                }
            }
        }
    }
    

    main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include "myclass.h"
    
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
    
        MyClass myClass;
        engine.rootContext()->setContextProperty("myClass", &myClass);
    
        engine.load(QUrl(QLatin1String("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
    
        return app.exec();
    }
    

    Runtime output

    qml: variantList length 3
    qml: stringList 0 length 3
    qml: variantList i(0), j(0) = apple
    qml: variantList i(0), j(1) = banana
    qml: variantList i(0), j(2) = coconut
    qml: stringList 1 length 3
    qml: variantList i(1), j(0) = alice
    qml: variantList i(1), j(1) = bob
    qml: variantList i(1), j(2) = charlie
    qml: stringList 2 length 3
    qml: variantList i(2), j(0) = alpha
    qml: variantList i(2), j(1) = beta
    qml: variantList i(2), j(2) = gamma
    

    visual output

    ... and it works :)