3

I'm a looking for a way how to pass larger amount of QObjects to QML as Model and than use Repeater {} to draw them. This solution have to meet following criteria:

  1. when new object is added to list, only this item will be refreshed
  2. when any of passed object is changed, Qml content have to be automatically refreshed
  3. list have to be universal, no hard-coded property names

Solution 1.

I know I can use QQmlListProperty<QObject>. Unfortunately in case that object is added/removed all other objects are refreshed in Qml. In case of more complex/larger amount of objects this is very unwieldy.

This solution meets 2) and 3). When object is updated via setter and called notifyChange(), qml automatically update the content and it's possible to use it on any QObject

Solution 2.

QAbstractList with implemented roles as described in documentation (http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel).

This solution meets 1) but not 2) and 3). When new object is added and beginInsertRows/endInsertRows is called, only one item is correctly refreshed. But it's necessary to prepare Model object for each QObject and Model have to be manually updated when QObject changed

Solution 3.

I tried to implement QAbstractListModel which internally holds list of QObjects pointers: QList<boost::shared_ptr<AnimalObject>> m_animals;.

When roleNames() method isn't implemented Qml doesn't query for data via data() method at all. So it seems that it's not possible to use default Qt::DisplayRole role for returning QObject from QAbstractList

When roleNames() method is implemented with single role, for example "object" and data() returns inner QObject as QVariant, it's possible to access it from 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;
}

and this is how to access QObject from QML

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

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.

Question:

My Question is. Are these three methods the only possible solutions for this problem, or are there any other method I completely missed?

Is there any clean way how to create list of QObjects, pass it to QML with full support of adding/removing/updating objects from C++ and directly updating them in QML?

PS: I described all these methods here because I believe this can be useful for many others.It took me a few hours to figure out how to do all of this.

Edit

Suggested solution 1.

As suggested @Velkan, it's possible to use QMetaObject::property and QMetaObject::propertyCount to dynamically extend QAbstractListModel for object's properties. Unfortunately this means to implement also individual refresh for each object/property via QDynamicPropertyChangeEvent signals.

Unfortunately for large number of objects and properties this solution probably would be very inefficient. For anyone interested in this solution, here is a snippet with QMetaObject::property test:

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;
}
BaCaRoZzo
  • 7,502
  • 6
  • 51
  • 82
Ludek Vodicka
  • 1,610
  • 1
  • 18
  • 33
  • 1
    "When roleNames() method isn't implemented Qml doesn't query for data via data() method at all." That's not true. The default role names are here: http://doc.qt.io/qt-5/qabstractitemmodel.html#roleNames – Mitch Jan 28 '16 at 21:58
  • Also, I'm not sure what this means: "When new object is added and beginInsertRows/endInsertRows is called, only one item is correctly refreshed. But it's necessary to prepare Model object for each QObject and Model have to be manually updated when QObject changed" An actual, physical example would help out a lot here, as always. – Mitch Jan 28 '16 at 22:00
  • @Mitch: default role names aren't used from QML Model. To be able to access model.propertyName, it's necessary to implement "propertyName" role. Default roles are useless here. Without overriding and implementing `roleNames()` method in `data() ` isn't executed at all – Ludek Vodicka Jan 29 '16 at 07:06
  • @Mitch: to your second post. Example is in attached Qt doc. What do you need to see more? When objects are modified after are already added to AnimalModel, there is no automatic refresh of AnimalModel. You can try it by copy&pasting mentioned example – Ludek Vodicka Jan 29 '16 at 07:10
  • Probably you can implement `roleNames()` by getting property names with `QMetaObject::property` and `QMetaObject::propertyCount`: http://doc.qt.io/qt-5/qmetaobject.html#propertyCount . Without `roleNames()` only the standard roles (like 'display' or 'edit') work. Automatic individual refresh you can do on dynamic properties, because they generate a QDynamicPropertyChangeEvent event when changed. For static properties, there is `QMetaProperty::notifySignal` that can be connected to something, But it's a lot of connections: each property of each object. – Velkan Jan 29 '16 at 07:45
  • @Velkan: Thanks for reply. I tried idea with QMetaObject::property and works ok, but it's exactly like you wrote. Without signal on each object+property there is no automatic refresh. Unfortunately I need this solution for large number of objects where so many connections/events probably would be very problematic. – Ludek Vodicka Jan 29 '16 at 08:03
  • "Large number of objects", "being generic", "being efficient" -- pick 2. I don't see the point of being *so* generic, how do you plan to show the data in your delegates if you don't even know what properties you're exposing (as roles) from C++? Besides, this number of connections would also be required by your solution #3 -- except that you "don't see" the connections, they're made by the QML engine. – peppe Jan 29 '16 at 10:20
  • @peppe: I'm trying to implement/find generic QObject list class which meets all my three criteria. This list will be than used in our app in several classes. It's not ordinary app where QML is used for UI but we're trying to implementing new diagram component for our tool (www.skipper18.com). For each diagram object we want QObject class which represent inner data. For example "Entity" object has name,caption,comment and list. "Field" object has "name,type,size,..." properties. After that such QObject is passed to QmlEngine where is displayed by Qml decoration rules. – Ludek Vodicka Jan 29 '16 at 13:30
  • @peppe: As you can see, it's not necessary to know exact object type on list<> level because this is done from Qml where visual objects are rendered from QObject data. Also you can probably imagine that we can't afford to refresh all diagram objects only because one field property was updated. Because of that it's necessary to have lists-of-lists of objects with valid refresh behavior. – Ludek Vodicka Jan 29 '16 at 13:32

1 Answers1

0

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.

If your problem is with model.object.property and your solution is a shorter model.property, then an equally good solution would be object.property which is entirely functional. You can directly use roleName from within the delegate object.

As for the generic list/model class, I already addressed that in that other question.

Community
  • 1
  • 1
dtech
  • 47,916
  • 17
  • 112
  • 190