I have a QList<QObject*>
C++ model containing custom objects and exposed to QML.
My custom object looks like this:
class CustomObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ getName NOTIFY nameChanged)
Q_PROPERTY(QQmlListProperty<CustomObject READ getChildren NOTIFY childrenChanged)
[...]
}
My model is exposed to QML like this:
qmlEngine->rootContext()->setContextProperty("internalModel", QVariant::fromValue(m_internalModel));
So far so good. I can use a view, display all my elements and recursively also display their children.
The problem is that QList has no way to notify QML that the model changed. As noted in the documentation about QObjectList-based model:
Note: There is no way for the view to know that the contents of a QList has changed. If the QList changes, it is necessary to reset the model by calling QQmlContext::setContextProperty() again.
So everytime I add or remove an item, I call:
qmlEngine->rootContext()->setContextProperty("internalModel", QVariant::fromValue(m_internalModel));
And this is extremely slow.
If I understood correctly, I need to use QAbstractItemModel
instead.
So, is it possible to migrate from QList<QObject*>
to QAbstractItemModel
without changing the QML part? In particular, should I migrate all Q_PROPERTY
from CustomObject to roles or can "reuse them"?
Yes this is possible, you only need to change QML slightly
C++ model class
#pragma once
#include <QAbstractListModel>
#include <QVector>
class Model : public QAbstractListModel {
Q_OBJECT
public:
int rowCount(const QModelIndex&) const override;
QVariant data(const QModelIndex& index, int role) const override;
public slots:
void insert(QObject* item);
void remove(QObject* item);
protected:
QHash<int, QByteArray> roleNames() const override;
private:
QVector<QObject*> mItems;
};
It will work with any types that inherit from QObject
. You can insert and remove items by using insert()
and remove()
which works both from C++ and QML. The implementation is pretty simple
#include "model.h"
int Model::rowCount(const QModelIndex&) const {
return mItems.size();
}
QVariant Model::data(const QModelIndex& index, int /*role*/) const {
QObject* item = mItems.at(index.row());
return QVariant::fromValue(item);
}
void Model::insert(QObject* item) {
beginInsertRows(QModelIndex(), 0, 0);
mItems.push_front(item);
endInsertRows();
}
void Model::remove(QObject* item) {
for (int i = 0; i < mItems.size(); ++i) {
if (mItems.at(i) == item) {
beginRemoveRows(QModelIndex(), i, i);
mItems.remove(i);
endRemoveRows();
break;
}
}
}
QHash<int, QByteArray> Model::roleNames() const {
QHash<int, QByteArray> roles;
roles[Qt::UserRole + 1] = "item";
return roles;
}
Note the use of roleNames()
and "item"
role. We will use it in QML. You do not need to migrate your CustomObject
properties to roles, just use the single role and return QObject*
pointer from Model::data()
.
Model registration
Model internalModel;
qmlEngine->rootContext()->setContextProperty("internalModel", &internalModel);
Finally
ListView {
model: internalModel
delegate: Text {
text: model.item.name
}
}
The only thing you need to do in your QML is replace name
and getChildren
with model.item.name
and model.item.getChildren
. Sounds simple?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With