将QList<T*>转换为qml数组。

4

我是一位有用的助手,可以为您翻译文本。

我有一个继承自QObject的类Bar。我想在类Foo中拥有一个Bar指针的QList,并在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");

主界面.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 *> 可以正常工作,我可以访问它的成员 xconsole.log(foo.t1) 输出 qml: [Bar(0x10e4be0),Bar(0x10bd6d0)],但是 QList<Bar *> 不行。

当我试图访问成员 x 时,输出为 qml: undefined,console.log(foo.t2) 则输出 qml: QVariant(QList<Bar*>)

为什么 QList<Bar *> 在 QVariant 中被暴露?有没有办法像 QList<QObject *> 一样在 qml 中暴露它?


你要解决的任务是什么?看起来你的示例代码过于简化了。只需要一个不可变的列表(从QML和C++两侧)的情况很少见。我怀疑你需要一个用于ListView或类似内容的模型。 - Tomilov Anatoliy
4个回答

9

首先仔细阅读以下文章:

为了从C++数据结构中创建JavaScript数组或对象,您应该在QVariantList(它会被转换成JavaScript数组)或者QVariantMap(它会被转换成JavaScript对象)中公开那些C++的数据结构。

此外,“序列类型到JavaScript数组”的部分如下:

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>
在您的情况下,请注意:
其他序列类型不受透明支持,而是将任何其他序列类型的实例作为不透明的QVariantList在QML和C++之间传递。
因此,您应该准备一个QVariantList列表。还要注意的是,由于QVariantMap和QVariantList都是QVariant类型,因此您可以将映射插入到列表中或反之,以创建具有列表内部的复杂对象数组或复杂对象。

1
QObjectList (QList<QObject*>) 和 QAbstractItemModel 也可以使用:http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html - GrecKo
@GrecKo,你建议的QObjectList替代方案,QList<Bar*>不允许吗?Bar是QObject类型,而QObjectList是QList<QObject*>。在Bar和QObject之间进行类型转换会有问题吗? - piper2200s
1
@piper2200s 即使 Bar 继承自 QObject,也不允许使用 QList<Bar*>。作为替代方案,您可以使用 http://gitlab.unique-conception.org/qt-qml-tricks/qt-qml-models/。 - GrecKo

6

QML引擎将QList<QObject*>识别为一种特殊情况。您无法公开类型为QList<Bar*>的属性,引擎无法理解它,并且没有注册它作为新列表类型的方法。

另一个未被提及的选项是转换列表...尽管有些人可能认为这是罪恶的。

如果您必须将数据存储在QList<Bar*>中,则可以将其作为QList<QObject*>返回,如下所示...

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
}

它快速、简单,Bar类的所有对象属性和Q_INVOKABLE方法都可以在QML中使用。

我添加了一些检查以防万一,但如果您确定您的类是从QObject派生的,则可以选择不必理会。

QList<QObject *>支持文档在此处: http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qobjectlist-based-model。这不是巧合...它被选择为特殊情况;)每个QObject *都从QList复制,并用于在QML引擎内创建一个新的javascript数组。

将事物包含在variant中是可以的,只要引擎知道variant中的类型即可。您可以注册Bar类型,以便QML引擎能够理解:

  • QVariant(Bar*) ,甚至

  • QList<QVariant(Bar*)>

但是,您永远无法让引擎理解QList<Bar*>

(注意:如果Bar派生自QObject,则无需将其注册到QML中,支持将是隐式的)

其他选项... QQmlListPropertyQList<QVariant>可以工作,但已在其他答案中涵盖。 QAbstractListModel是另一个选项,但已经有足够的资源介绍如何完成。

完整代码:

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

所以,如果我理解正确,引擎不理解/支持在QVariant中包含什么?有趣的是,引擎支持QList<QObject*>,但似乎没有在任何地方记录。我想QVector<QObject*>QMap<QString, QObject*>也不受引擎支持。那么为什么QList<QObject *>如此特殊? - Eligijus Pupeikis
我在回答中添加了更多信息以回应您的评论。QList<QObject*>支持在此处有文档记录:http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qobjectlist-based-model。很抱歉,我不太了解`QVector`和`QMap`的QML支持。我的猜测是两者都没有任何支持。 - Mark Ch
将东西封装在 variant 内部是可以的,只要引擎知道 variant 内部的类型即可。实际上,你在 QVariant(QList<Bar*>) 中看到的是 QML 引擎自己将你的 QList<Bar*> 封装在一个 QVariant 中(对于大多数对象类型也是如此),但它无法解包 variant,因为它不理解 QList<Bar*> 这种类型。 - Mark Ch

4

1
这是我使用 QQmlListProperty 的方法。需要一些时间来适应它,但它的工作方式与你期望的完全相同。

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();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接