6

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!

Leshii
  • 71
  • 1
  • 5
  • The cleanest approach is probably to encapsulate the list in a `QAbstractItemModel` (`QAbstractTableModel`). The easiest approach is to use `QQmlListProperty`. – m7913d Aug 21 '17 at 20:49
  • I'll look into it, thank you! – Leshii Aug 21 '17 at 20:56

2 Answers2

9

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:

  • QList<QObject *>
  • QList<QVariant>
  • QStringList - (not QList<QString>!!!)

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 :)

Mark Ch
  • 2,840
  • 1
  • 19
  • 31
0

Automatic conversions would only work for several specific types of containers, and that's it. Just because conversion A works, and conversion B works, doesn't mean that conversion A will work too.

You can pretty much forget about using the [] operator in all the cases where automatic conversion doesn't work.

A variant list of variant lists however might just work. I haven't tested it myself but there is a slim hope. However, you will have to manually do the conversion before you pass that stuff to QML.

An approach that will most definitely work is to create accessor functions, like for example QString Class::get(int row, int col) or you can have separate accessors to select a row, and then pass that result to another function to select a column to give you the string.

dtech
  • 47,916
  • 17
  • 112
  • 190
  • Thanks for the break down and i like the idea of the accessor functions. Thanks! – Leshii Aug 21 '17 at 21:14
  • You could also do a model but that's not really necessary unless you are dealing with views on the QML side. – dtech Aug 21 '17 at 21:17