从QML访问C++ QLists

31
如果我在C++中有一系列的东西,我该如何将它暴露给QML(在Qt5/QtQuick 2中)?似乎QML只能理解派生自QObject的类,这是个问题,因为QObject不能放入QList或者复制。我应该怎么做?
struct Thing
{
    int size;
    QString name;
};

class ThingManager : public QObject
{
    Q_OBJECT

    // These macros support QtQuick, in case we one day want to use it to make a slick
    // interface (when QML desktop components are released).
    Q_PROPERTY(QList<Thing> things READ things NOTIFY thingssChanged)

public:
    // ...
    QList<Thing> things() const;

    // ...

};

这样我就可以在 QML 中做类似这样的事情:

var a = thingManager.things[0].name;

1
或许这只是一次瞎猜,但也许使用 Q_DECLARE_METATYPE() 和/或 qRegisterMetaType() 可以帮助解决问题? - Bret Kuhns
作为一条技术注释,从技术上讲,QML 可以与任何派生自 QDeclarativeObject 的东西一起使用。 - Deadron
9个回答

27

或者,您可以使用QVariantListQList<QVariant>),当传递给QML时,它会自动转换为JavaScript数组,并且可以从C++和QML中读取和写入


但是 QVariant 不能保存复合值,对吗? - Timmmm
3
QVariant几乎可以容纳“任何东西”,包括另一个QVariantListQVariantMap - Dickson
啊,有趣,我不知道! - Timmmm
通常支持许多模板类型的QList:QList C++序列类型在QML中作为JavaScript数组类型透明地支持 http://qt-project.org/doc/qt-5/qtqml-cppintegration-data.html - johnbakers

25

在尝试解决类似问题时,我遇到了这个问题,我想在QML中使用C++代码作为模型源。TheBootroo提供的答案指引了我正确的方向,但对我来说并未完全奏效。我没有足够的声望直接回答他(但我给他点了赞)。

我正在使用Qt 5.0.0,我发现这个链接非常有帮助。

ThingManager的定义应该如下更改。

class ThingManager : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QList<QObject*> things READ getThings NOTIFY thingsChanged)

public:
    QList<QObject*> getThings () const { return m_things; }

signals:
    void thingsChanged ();

private:
    QList<QObject*> m_things;
};

请注意,我将getThings的返回类型更改为QList<QObject*>。如果不进行更改,Qt会警告说它“无法处理未注册的数据类型'QList<Thing*>'”。

在QML代码中,可以通过模型访问Thing的属性,如model.modelData.size和model.modelData.name。


1
qt5版本的文档:http://qt-project.org/doc/qt-5.0/qtquick/qtquick-modelviewsdata-cppmodels.html - Roman A. Taycher
1
通常支持许多模板类型的QList:QList C++序列类型在QML中作为JavaScript数组类型透明地支持。请参阅qt-project.org/doc/qt-5/qtqml-cppintegration-data.html。 - johnbakers
2
你可以使用 qmlRegisterType<Thing>("com.yourapp",1,0,"Thing") 函数注册类型,而不需要使用 QObject。你的 "Thing" 必须声明为一个类。 - Nulik

11

在使用 QML 的更多经验后,我发现用 QAbstractListModel 来实现列表是最好的方法。

你可以让你的 Thing 继承自 QObject,这样它就可以被存储在 QVariant 中(在注册之后)。然后你可以返回实际的 Thing 作为模型项,你可以在 Repeater 中通过 model.display.a_property_of_thing 访问它,列表长度可用 model.count 去获取。

这种方式有以下优缺点:

  1. 快速 - 它不会复制整个列表来访问一个元素。
  2. 可以轻松获得列表更改的动画效果(添加、重新排列和删除项目)。
  3. 从 QML 中很容易使用。
  4. 为了使动画正常工作,每当你修改列表时,你都需要做一些稍微麻烦的簿记记录(beginInsertRows() 等)。

...

class Things : public QObject
{
...
};

Q_DECLARE_METATYPE(Thing*)

class ThingList : public QAbstractListModel
{
    Q_OBJECT
    
public:
    explicit ThingList(QObject *parent = 0);
    ~ThingList();

    int rowCount(const QModelIndex& parent = QModelIndex()) const override;
    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;

public slots:

    // Extra function to get the thing easily from outside Repeaters.
    Thing* thing(int idx);

private:
    QList<Thing*> mThings;
};

int ThingList::rowCount(const QModelIndex& parent) const
{
    return mThings.size();
}

QVariant ThingList::data(const QModelIndex& index, int role) const
{
    int i = index.row();
    if (i < 0 || i >= mThings.size())
        return QVariant(QVariant::Invalid);

    return QVariant::fromValue(mThings[i]);
}

Thing* ThingList::thing(int idx)
{
    if (idx < 0 || idx >= mThings.size())
        return nullptr;

    return mThings[idx];
}

1
这确实是最好的方法,即使它不太容易设置 - 至少在第一次使用时。 - qCring

5

啊,我找到了答案(我想是的,但未经过测试):QQmlListProperty

在示例中有几个用法,例如在qtdeclarative/examples/quick/tutorials/gettingStartedQml/filedialog/directory.*中:

不幸的是,目前只能使用只读列表。


通常支持许多模板类型的QList:QList C++序列类型在QML中作为JavaScript数组类型透明地支持 http://qt-project.org/doc/qt-5/qtqml-cppintegration-data.html 这包括对QList的更新,而不仅仅是只读。 - johnbakers

3

关于QObject,你的理解是错误的,它们可以以指针的形式简单地放入QList中,下面的代码可以完美地实现:

class Thing : public QObject
{
    Q_OBJECT

    Q_PROPERTY (int     size READ getSize CONSTANT)
    Q_PROPERTY (QString name READ getName CONSTANT)

public:
    Thing(QObject * parent = NULL) : QObject(parent) {}

    int     getSize () const { return m_size; }
    QString getName () const { return m_name; }

private:
    int     m_size;
    QString m_name;
};

class ThingManager : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QList<Thing*> things READ getThings NOTIFY thingsChanged)

public:
    QList<Thing*> getThings () const { return m_things; }

signals:
    void thingsChanged ();

private:
    QList<Things*> m_things;
};

最好使用“QList<QObject*> getThings()”。 - Brent81
无法正常工作错误:无法处理未注册的数据类型“QList<Thing*>”以用于属性... 它与QObject*一起正常工作。 - Keo
但它能暴露出来进行编写吗? - sancelot

2

1
< p > eatyourgreens 给出的答案是正确的。通过以这种方式实现您的类,您可以访问尽可能多的后代。我发现另一个有用的提示是在 QML 代理元素内部为我们的模型创建别名。

ListView {
   anchors.fill: parent
   model: thing_manager.things
   delegate: ItemDelagate {}
   clip: true
   spacing: 10
}

然后在ItemDelegate.qml中,您可以为模型创建别名,以避免一直使用model.modelData。

Item{
    width: 600
    height: 200
    property var thing: model.modelData
    Rectangle {
        anchors.fill: parent
        color: "red"
        Text {
            text: thing.name // or any other field
        }
    }
}

0

存在一个好的解决方案,但没有提到:

class ThingManager : public QObject
{
Q_OBJECT

// These macros support QtQuick, in case we one day want to use it to make a slick
// interface (when QML desktop components are released).
Q_PROPERTY(QList<Thing> things MEMBER m_things NOTIFY thingssChanged)

// ...
private:
// ...
QList<Thing> m_things;
// ...

};

可读写。无需昂贵的函数调用和数据复制。只需在 QML 中直接访问类成员:

var a = thingManager.things[0].name;

更多信息请参阅文档:https://doc-snapshots.qt.io/qt5-dev/properties.html


这并不是那么简单。在QML中使用上述方法,我无法访问像count()这样的方法,因为QList<Thing>实际上不是Javascript数组,也不能使用length属性。 - Allan Ruin
如果QML和C++之间的数据类型转换得到官方支持,(https://doc.qt.io/qt-5/qtqml-cppintegration-data.html) 那么对于我来说,它可以这样实现:var a = thingManager.things; x = a.length,其中things是例如QList<qreal>。 - fokhagyma

0
一个高度间接的实现方式是这样的:
i.) 在 QML 中创建一个模型。
ListModel 
{
     id: thingModel

     ListElement 
     {
         size: 10
         name: "Apple"
     }     
}

ii.) 然后提供一些JavaScript函数来修改此列表,例如。

function jAppendThing( newSize, newName )
{
    thingModel.append({"size": nameSize, "name": newName })
}

function jClearThing()
{
    thingModel.clear()
}

同样地,jDeleteThing等等...

iii.) 您可以阅读关于如何从C++调用qml函数的内容。

iv.) 在您的C++列表上运行循环,并调用qml的append函数将所有数据添加到qml列表中。

v.) 在C++侧列表中进行任何更新时,使用上述函数修改qml数据以保持其更新。


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