3
votes

I have a class Bar which inherits from QObject. I want to have a QList of Bar pointers in class Foo and expose it in qml.

foo.h

#ifndef FOO_H
#define FOO_H

#include <QQuickItem>

class Bar : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int x READ x)

public:
    explicit Bar(QObject *parent = nullptr)
        : QObject(parent), mX(123) {}
    virtual ~Bar() {}

    int x() const { return mX; }
private:
    int mX;
};

class Foo : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(QList<QObject *> t1 READ t1)
    Q_PROPERTY(QList<Bar *> t2 READ t2)

public:
    explicit Foo(QQuickItem *parent = nullptr)
        : QQuickItem(parent) {
        mT1.append(new Bar(this));
        mT1.append(new Bar(this));

        mT2.append(new Bar(this));
        mT2.append(new Bar(this));
    }
    virtual ~Foo() {}

    QList<QObject *> t1() const { return mT1; }
    QList<Bar *> t2() const { return mT2; }

private:
    QList<QObject *> mT1;
    QList<Bar *> mT2;
};

#endif // FOO_H

qmlRegisterType("Custom.Foo", 1, 0, "Foo");

main.qml

import QtQuick 2.6
import QtQuick.Window 2.2
import Custom.Foo 1.0

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

    MouseArea {
        anchors.fill: parent
        onClicked: {
            console.log(foo.t1)
            console.log(foo.t1.x)
            console.log(foo.t2)
            console.log(foo.t2.x)
        }
    }
    Foo {
        id: foo
    }
}

QList<QObject *> works I can access it's member x and console.log(foo.t1) outputs qml: [Bar(0x10e4be0),Bar(0x10bd6d0)] but QList<Bar *> doesn't. When I try to access member x I get qml: undefined and console.log(foo.t2) outputs qml: QVariant(QList<Bar*>).

Why QList<Bar *> is exposed in QVariant? Is there a way to expose it in qml same way QList<QObject *>?

4
What is exactly task you solve? It seems your example code is oversimplified. It is rare case, when just immutable (from both QML and C++ sides) list needed. I suspect you need a model for a listview or something alike. - Tomilov Anatoliy

4 Answers

9
votes

First off read the following article carefully:

In order to create JavaScript arrays or objects from C++ data structures, you should expose those of C++ in QVariantList (which gets converted to JavaScript array) or QVariantMap (which gets converted to JavaScript object).

Also section "Sequence Type to JavaScript Array" says:

Certain C++ sequence types are supported transparently in QML as JavaScript Array types.

In particular, QML currently supports:

QList<int>
QList<qreal>
QList<bool>
QList<QString> and QStringList
QList<QUrl>
QVector<int>
QVector<qreal>
QVector<bool>

In your case, note that:

Other sequence types are not supported transparently, and instead an instance of any other sequence type will be passed between QML and C++ as an opaque QVariantList.

Therefore, you should prepare your list in a QVariantList. Also note that since both QVariantMap and QVariantList are of type QVariant, so you can insert a map into a list or vice versa, to create a complex array of objects or a complex object having a list inside.

5
votes

QML Engine recognizes QList<QObject*> as a special case. You can't expose a property of type QList<Bar*>, the engine doesn't understand it, and there is no way to register it as a new list type.

Another option which hadn't been mentioned is casting the list... as sinful as it may be considered by some.

If you must have your data stored in QList<Bar*> then you can return it as a QList<QObject*> as below...

QList<QObject *> t2() const {
    Q_ASSERT(sizeof(QObject*) == sizeof(Bar*));               // check pointers are the same size
    Q_ASSERT(sizeof(QList<QObject*>) == sizeof(QList<Bar*>)); // check lists are the same size
    Q_ASSERT(qobject_cast<Bar*>((QObject*)mT2.at(0)));        // check Bar is derived from QObject
    return *reinterpret_cast<const QList<QObject *>*>(&mT2);  // now cast the list
}

It's fast, easy, and all the object properties and Q_INVOKABLE methods of the Bar class are available in QML.

I've added a few checks in just in case, but if you can be certain your class is derived from QObject then you might choose not to bother.

QList<QObject*> support is documented here: http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qobjectlist-based-model. It is no coincidence that it is a special case... it has been chosen to be a special ;) Each QObject* is copied from the QList and used to create a new javascript array inside the QML Engine.

Encompassing things inside a variant is fine, so long as the engine knows the type which is inside the variant. You can register Bar type so that the QML engine could understand:

  • QVariant(Bar*) and even

  • QList<QVariant(Bar*)>.

However you can never get the engine to understand QList<Bar*>.

(note: if Bar derives from QObject, then there is no need to register it to QML, support will be implicit)

Other options... QQmlListProperty and QList<QVariant> will work, but have been covered in other answers. QAbstractListModel is another option, but there are already enough resources elsewhere to see how that is done.

full code:

foo.h

#ifndef FOO_H
#define FOO_H

#include <QQuickItem>

class Bar : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int x READ x)

public:
    explicit Bar(QObject *parent = nullptr)
        : QObject(parent), mX(123) {}
    virtual ~Bar() {}

    int x() const { return mX; }
private:
    int mX;
};

class Foo : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(QList<QObject *> t2 READ t2)

public:
    explicit Foo(QQuickItem *parent = nullptr)
        : QQuickItem(parent) {
        mT2.append(new Bar(this));
        mT2.append(new Bar(this));
    }
    virtual ~Foo() {}

    QList<QObject *> t2() const {
        Q_ASSERT(sizeof(QObject*) == sizeof(Bar*));               // check pointers are the same size
        Q_ASSERT(sizeof(QList<QObject*>) == sizeof(QList<Bar*>)); // check lists are the same size
        Q_ASSERT(qobject_cast<Bar*>((QObject*)mT2.at(0)));        // check Bar is derived from QObject
        return *reinterpret_cast<const QList<QObject *>*>(&mT2);  // now cast the list
    }

private:
    QList<Bar *> mT2;
};

#endif // FOO_H

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include "foo.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;

    qmlRegisterType<Foo>("Custom.Foo", 1, 0, "Foo");

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

    return app.exec();
}

main.qml

import QtQuick 2.6
import QtQuick.Window 2.2
import Custom.Foo 1.0

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

    MouseArea {
        anchors.fill: parent
        onClicked: {
            console.log(foo.t2)
            console.log(foo.t2[0].x)
            console.log(foo.t2[1].x)
        }
    }
    Foo {
        id: foo
    }
}
4
votes

To expose a list of QObject to Qml, Qt recommends to use QQmlListProperty.

To create this object you need to override some of its access function, like size(), at(), etc.

You should have a look to

1
votes

Here is how I would do it using QQmlListProperty. It takes awhile to get used to it, but it works exactly the way you'd expect.

Foo.h

#ifndef FOO_H
#define FOO_H

#include <QQuickItem>

class Bar : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int x READ x)

public:
    explicit Bar(QObject *parent = nullptr)
        : QObject(parent), mX(123) {}
    virtual ~Bar() {}

    int x() const { return mX; }
private:
    int mX;
};

class Foo : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(QQmlListProperty<Bar> bars READ bars)

public:
    explicit Foo(QQuickItem *parent = nullptr)
        : QQuickItem(parent) {
        mBars.append(new Bar(this));
        mBars.append(new Bar(this));
    }
    virtual ~Foo() {}

    QQmlListProperty<Bars> bars();
    void appendBar(Bar*);
    int barCount() const;
    Bar *bar(int) const;
    void clearBars();
    void replaceBar(int, Bar*);
    void removeLastBar();

private:
    QList<Bar *> mBars;
};

#endif // FOO_H

Foo.cpp

#include "Foo.h"

QQmlListProperty<Bar> Foo::bars()
{
    return {this, this,
            [](QQmlListProperty<Bar> *prop, Bar *newBar)
            {
                Foo *foo = qobject_cast<Foo*>(prop->object);
                if (foo) {
                    foo->appendBar(newBar);
                }
            },
            [](QQmlListProperty<Bar> *prop)->int
            {
                Foo *foo = qobject_cast<Foo*>(prop->object);
                if (foo ) {
                    return foo->barCount();
                }
                return 0;
            },
            [](QQmlListProperty<Bar> *prop, int index)->Bar*
            {
                Foo *foo = qobject_cast<Foo*>(prop->object);
                if (foo) {
                    return foo->bar(index);
                }
                return nullptr;
            },
            [](QQmlListProperty<Bar> *prop)
            {
                Foo *foo = qobject_cast<Foo*>(prop->object);
                if (foo) {
                    foo->clearBars();
                }
            },
            [](QQmlListProperty<Bar> *prop, int index, Bar* newBar)
            {
                Foo *foo = qobject_cast<Foo*>(prop->object);
                if (foo) {
                    foo->replaceBar(index, newBar);
                }
            },
            [](QQmlListProperty<Bar> *prop)
            {
                Foo *foo = qobject_cast<Foo*>(prop->object);
                if (foo) {
                    foo->removeLastBar();
                }
            }
    };
}

void Foo::appendBar(Bar* newBar) {
    mBars.append(newBar);
}

int Foo::barCount() const
{
    return mBars.count();
}

Bar *Foo::bar(int index) const
{
    return mBars.at(index);
}

void Foo::clearBars() {
    mBars.clear();
}

void Foo::replaceBar(int index, Bar *newBar)
{
    mBars[index] = newBar;
}

void Foo::removeLastBar()
{
    mBars.removeLast();
}