QList<T*> to qml array

余生长醉 提交于 2020-01-24 08:45:30

问题


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 *>?


回答1:


First off read the following article carefully:

  • Data Type Conversion Between QML and C++

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.




回答2:


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
    }
}



回答3:


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

  • http://doc.qt.io/qt-5/qqmllistproperty.html

  • http://doc.qt.io/qt-5/qtqml-referenceexamples-properties-example.html



来源:https://stackoverflow.com/questions/46681920/qlistt-to-qml-array

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!