I would like to know if there is any macro or way how to register Qt model as property of QObject.
For example, I have AnimalModel
(http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel).
I Know I can pass it to root context of QuickView
QuickView view;
view.rootContext()->setContextProperty(\"myModel\", &model);
In case I have QObject registered via Qml macros, I can pass this object to view too:
view.rootContext()->setContextProperty(\"obj\", pDataObject);
But what If I want to have QObject which holds model of any data?
For example:
class DataObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString color READ color WRITE setColor NOTIFY colorChanged)
...
AnimalModel m_modelAnimals;
//Is this possible in any way?
//Q_PROPERTY(AnimalModel modelAnimals READ modelAnimals NOTIFY modelAnimalsChanged)
};
Every example I found until now shows how to pass QAbstractListModel
to root context. But none how to use it as QObject property.
(I know there is QQmlListProperty
but QQmlListProperty
doesn\'t support partial refresh. It\'s always necessary to rebuild all Qml objects)
//Is this possible in any way?
//Q_PROPERTY(AnimalModel modelAnimals READ modelAnimals NOTIFY modelAnimalsChanged)
Yes it is, didn\'t you try? Of course, it will not be a AnimalModel
but a AnimalModel *
, but as long as the model inherits QAbstractListModel
, that\'s all you need. You don\'t even need the NOTIFY
part, as changes, internal to the model will be automatically reflected anyway. modelAnimalsChanged
only makes sense when you replace the entire model with a different model, and naturally, to shut up QML\'s warnings about using a property without a notify signal. A cleaner way to do the latter when the model object doesn\'t change is to just return a AnimalModel *
from a slot or a Q_INVOKABLE
.
If you want a truly flexible model, you can make one that stores QObject *
, then from QML you can create arbitrary objects with arbitrary properties, and add to the model. Then from the model you have a single object
role which returns the object, and you can query and use the object to retrieve the properties it holds. Whereas a \"classical\" list model implementation will define a model with a static, fixed schema, using this approach allows to have \"amorphous\" objects in the model with different properties.
Naturally, this requires some type safety, for example have a property int type
for each object in such a model, and based on it you can determine the available properties for the object. My usual approach is to have a Loader
for a delegate, and have it pass the object as a data source to different QML UI implementations visualizing that object type that it instantiates. This way you have both different objects in the model, and different QML items as view delegates.
The last step to making the ultimate \"jack of all trades\" list/model object is to implement QQmlListProperty
and Q_CLASSINFO(\"DefaultProperty\", \"container\")
for it, allowing you to both compose the list/model dynamically, or using QML\'s declarative syntax. Also note that with this solution, you can add to or remove from such a model, even remove declaratively instantiated objects.
Also, depending on your usage scenario, you may have to either qmlRegisterType()
or qmlRegisterUncreatableType()
for the model.
OK, on a second glance, it looks like by \"model of any data\" you didn\'t mean schema-less models but simply different schema models. In that case, instead of returning an AnimalModel *
, you can use a QAbstractListModel *
or even a QObject *
- it will work in QML anyway, as it employs dynamism through the meta system. But at any rate, schema-less models are that much more powerful and flexible, and they don\'t need C++ code to be defined, it can all work from QML alone.
class List : public QAbstractListModel {
Q_OBJECT
QList<QObject *> _data;
Q_PROPERTY(int size READ size NOTIFY sizeChanged)
Q_PROPERTY(QQmlListProperty<QObject> content READ content)
Q_PROPERTY(QObject * parent READ parent WRITE setParent)
Q_CLASSINFO(\"DefaultProperty\", \"content\")
public:
List(QObject *parent = 0) : QAbstractListModel(parent) { }
int rowCount(const QModelIndex &p) const { Q_UNUSED(p) return _data.size(); }
QVariant data(const QModelIndex &index, int role) const {
Q_UNUSED(role)
return QVariant::fromValue(_data[index.row()]);
}
QHash<int, QByteArray> roleNames() const {
static QHash<int, QByteArray> * pHash;
if (!pHash) {
pHash = new QHash<int, QByteArray>;
(*pHash)[Qt::UserRole + 1] = \"object\";
}
return *pHash;
}
int size() const { return _data.size(); }
QQmlListProperty<QObject> content() { return QQmlListProperty<QObject>(this, _data); }
public slots:
void add(QObject * o) {
int i = _data.size();
beginInsertRows(QModelIndex(), i, i);
_data.append(o);
o->setParent(this);
sizeChanged();
endInsertRows();
}
void insert(QObject * o, int i) {
beginInsertRows(QModelIndex(), i, i);
_data.insert(i, o);
o->setParent(this);
sizeChanged();
endInsertRows();
}
QObject * take(int i) {
if ((i > -1) && (i < _data.size())) {
beginRemoveRows(QModelIndex(), i, i);
QObject * o = _data.takeAt(i);
o->setParent(0);
sizeChanged();
endRemoveRows();
return o;
} else qDebug() << \"ERROR: take() failed - object out of bounds!\";
return 0;
}
QObject * get(int i) {
if ((i > -1) && (i < _data.size())) return _data[i];
else qDebug() << \"ERROR: get() failed - object out of bounds!\";
return 0;
}
signals:
void sizeChanged();
};
Then, after you qmlRegisterType<List>(\"Core\", 1, 0, \"List\");
you can use it pretty much any way you want to - it will hold any QObject
or derived, naturally including QMLs QtObject
It can directly be used as a model to drive a ListView
. You can populate it dynamically using the slots or declarative, like this:
List {
QtObject { ... }
QtObject { ... }
List {
QtObject { ... }
QtObject { ... }
}
}
It will also handle object ownership, and you can easily nest it, producing in essence a compartmentalized tree model - note that you can\'t declaratively do that with QML\'s ListModel
. You may want to add a parentChanged
signal and implement a setter that emits it if you want to bind against a changing parent, it was not necessary in my case.
As of how to use it with a view, you can either use the objectName
property or an int type
property, and use a Loader
for the delegate:
Loader {
width: childrenRect.width
height: childrenRect.height
}
If you use the object name, you make the loader create a name.qml
file, if you use an int, you can create an array of Component
s and use the appropriate index as a source component. You can either expose the object
as a property of the Loader
and have the actual object UI reference it parent.object.prop
, or you can use setSource(name + \".qml\", {\"object\": object})
and have the object property directly into that item, however setSource
will only work with external sources, not with inline Component
s. Note that in the case of an external source, object
will be accessible even without doing anything to forward it, however for some reason it doesn\'t work with inline components, with such components the only possible way is to expose it as a property of the loader.