将 QObject 列表传递给 QML 上下文的可能方法有哪些?

Which are the possibile ways to pass a list of QObject to QML context?

我正在寻找一种方法,如何将更大量的 QObjects 作为模型传递给 QML,而不是使用 Repeater {} 来绘制它们。此解决方案必须满足以下条件:

  1. 当新对象添加到列表时,只有此项会被刷新
  2. 当任何传递的对象发生变化时,Qml 内容必须自动刷新
  3. 列表必须是通用的,没有硬编码的 属性 名称

解决方案 1.

我知道我可以使用 QQmlListProperty<QObject>。不幸的是,如果该对象是 added/removed,所有其他对象都会在 Qml 中刷新。如果有更多 complex/larger 个对象,这将非常笨拙。

这个解决方案满足2)和3)。当对象通过 setter 更新并调用 notifyChange() 时,qml 自动更新内容并且可以在任何 QObject

上使用它

解决方案 2.

QAbstractList 具有文档中描述的已实现角色 (http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel)。

此解决方案满足 1) 但不满足 2) 和 3)。添加新对象并调用 beginInsertRows/endInsertRows 时,只有一项会正确刷新。但是需要为每个QObject准备Model对象,当QObject改变时必须手动更新Model

解决方案 3.

我尝试实现 QAbstractListModel,它在内部保存 QObjects 指针列表:QList<boost::shared_ptr<AnimalObject>> m_animals;

roleNames() 方法未实现时,Qml 根本不会通过 data() 方法查询数据。所以似乎不可能使用默认 Qt::DisplayRole 角色从 QAbstractList

返回 QObject

当 roleNames() 方法以单个角色实现时,例如 "object" 和 data() returns 内部 QObject 作为 QVariant,可以从 QML 访问它:

QVariant AnimalModel2::data(const QModelIndex & index, int role) const {
    if ( index.row() < 0 || index.row() >= m_animals.count() )
        return QVariant();

    auto ptrAnimal = m_animals[index.row()];
    if ( role == ObjectRole )
    return qVariantFromValue(ptrAnimal.get());
}

QHash<int, QByteArray> AnimalModel2::roleNames() const {
    QHash<int, QByteArray> roles;
    roles[ObjectRole] = "object";
    return roles;
}

这是从 QML 访问 QObject 的方法

Repeater {
  model: myModel
  Text {
    text: "[" + model.object.name + "]";
  }
}

此解决方案满足所有要求。不幸的是,必须使用 model.object.property 而不是更直接的 model.property 来访问此 QObject。虽然这没什么大不了的,但能直接访问对象就好了。

问题:

我的问题是。这三种方法是解决此问题的唯一可能方法,还是我完全错过了其他任何方法?

是否有任何干净的方法来创建 QObject 列表,将其传递给 QML,完全支持来自 C++ 的 adding/removing/updating 对象,并直接在 QML 中更新它们?

PS:我在这里描述了所有这些方法,因为我相信这对很多人有用 others.It 我花了几个小时才弄清楚如何完成所有这些。

编辑

建议的解决方案 1.

正如@Velkan 所建议的,可以使用 QMetaObject::propertyQMetaObject::propertyCount 动态扩展对象属性的 QAbstractListModel。不幸的是,这意味着要通过 QDynamicPropertyChangeEvent 信号为每个 object/property 实现单独刷新。

不幸的是,对于大量对象和属性,此解决方案可能效率很低。对于任何对此解决方案感兴趣的人,这里有一个带有 QMetaObject::property 测试的片段:

QVariant AnimalModel4::data(const QModelIndex & index, int role) const {
    if ( index.row() < 0 || index.row() >= m_animals.count() )
        return QVariant();

    auto ptrAnimal = m_animals[index.row()];

    const QMetaObject * pMeta = ptrAnimal->metaObject();
    int propertyNo= role - (Qt::UserRole + 1);

    QMetaProperty propMeta = pMeta->property(propertyNo);
    QVariant value = propMeta.read(ptrAnimal.get());
    return value;
}

QHash<int, QByteArray> AnimalModel4::roleNames() const {

    QHash<int, QByteArray> roles;
    if ( m_animals.size() == 0 )
        return roles;

    int role= Qt::UserRole + 1;
    const QMetaObject * pMeta = m_animals.front()->metaObject();
    for ( int propertyNo= 0; propertyNo< pMeta->propertyCount(); propertyNo++ )
    {
        QMetaProperty propMeta = pMeta->property(propertyNo);
        roles[role++] = propMeta.name();
    }
    return roles;
}

This solution meets all requirements. Unfortunately it's necessary to access this QObject with model.object.property instead of more straightforward model.property. Although it's not a big deal, would be great to have direct object access.

如果您的问题出在 model.object.property 上,而您的解决方案较短 model.property,那么同样好的解决方案是 object.property,它完全可用。您可以直接在委托对象中使用 roleName

至于通用 list/model class,我已经在 中解决了。