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:
- when new object is added to list, only this item will be refreshed
- when any of passed object is changed, Qml content have to be automatically refreshed
- 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;
}
roleNames()
method indata()
isn't executed at all – Ludek VodickaroleNames()
by getting property names withQMetaObject::property
andQMetaObject::propertyCount
: doc.qt.io/qt-5/qmetaobject.html#propertyCount . WithoutroleNames()
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 isQMetaProperty::notifySignal
that can be connected to something, But it's a lot of connections: each property of each object. – Velkan