Reputation: 4833
Using Qt 5.5.1
on iOS 9
I'm trying to assign a dynamically created QAbstractListModel
to the model
property of a ListView
:
Window {
ListView {
model: api.model()
delegate: delegate
}
Component {
id: delegate
Text { text: "Test" }
}
}
api
is a C++
object assigned to the QML
context with setContextProperty
. The model
method is a Q_INVOKABLE
which returns a QAbstractListModel *
. This all works, my ListView
is populated with data.
The problem is when I start scrolling. Usually after the second full scroll (to the bottom, back up to the top and down again) my ListView
starts to clear itself out. The debugger is telling me the QAbstractListModel
is being destroyed.
I don't want to set CppOwnership
on the model. Is there another way to prevent the ListView
from destroying its model?
Upvotes: 3
Views: 1311
Reputation: 2613
I want to add something to ddriver's answer, which is more than a comment.
This same problem came up for me. basically, i wanted to create a dynamic list view model (QAbstractListModel, in fact). The usual way is to put your models up front in main (or somewhere) like this:
QQmlContext* ctxt = engine.rootContext();
ctxt->setContextProperty("myModel", &model);
I have one model per object in this case, so i needed a dynamic solution.
I have a QObject
which creates my model for a list. The model created derives from QAbstractListModel
. The model is created and given out by my QObject
host with a Q_INVOKABLE
.
First problem is that the type of the model so generated is not known and must be registered. The usual qmlRegisterType
does not work because QAbstractListModels cannot be copied. so you must register with qmlRegisterUncreatableType
.
That's the first bit. Now the model works BUT who destroys it?
Turns out both my C++ code and QML both try to destroy the object since ownership was implicitly given to QML as part of the Q_INVOKABLE
accessor.
BUT just letting QML clean up was bad. I tracked when this happened and it didn't happen at all in a timely manner. Basically it wouldn't clean up unless I did quite radial things like resize the window. presumably, it would eventually clean up (garbage etc.) but i really wanted these dynamic models to be cleaned up when their host QObject
goes out.
So ddriver's idea is the way. but also remember to register with qmlRegisterUncreatableType
.
eg,
inline MyModel* MyHostObject::getModel()
{
if (!_model)
{
_model = new MyModel(this);
// retain ownership of this object.
QQmlEngine::setObjectOwnership(_model, QQmlEngine::CppOwnership);
}
return _model;
}
Upvotes: 0
Reputation: 21
Just to say - I can confirm the same issue on both Linux x86_64 and Android ARMv7.
MyComponent {
property var model: api.createModel()
ListView {
model: model
delegate: delegate
[...]
}
Component { id: delegate [...] }
}
Seems to be enough if you don't mind the model being destroyed later in time.
Upvotes: 1
Reputation: 4833
Even though I've accepted ddriver
's answer I've found a solution that seems to better match what I wanted.
By dynamically loading my components and storing the model as a variable, I'm able to get QML
to keep my C++
models alive and to destroy them when required, for example:
MyComponent {
property var model: api.createModel()
ListView {
model: model
delegate: delegate
[...]
}
Component { id: delegate [...] }
Component.onDestruction: model.destroy()
}
Unfortunately the model.destroy()
call seems to be required. I was expecting the garbage collector to pick this up, but it doesn't seem to.
I've only tested this is toy examples so far, caveat lector.
Upvotes: 1
Reputation: 49329
QML seems kind of broken in this regard, I've experienced completely arbitrary deletions of objects still in use in multiple scenarios. Objects with parents and referenced by the JS engine are being deleted for no apparent reason while JS garbage still takes hundreds of megabytes of memory instead of being freed. This applies to both objects returned from C++ and objects created in QML. When an object is returned from a C++ function to QML, ownership is passed to the QML engine, which makes the object vulnerable to such arbitrary deletions.
The solution is to force CPP ownership and manage the object's lifetime manually - keep in mind destroy()
won't work on such objects, so you have to use a C++ function from QML to delete it.
qmlEngine.setObjectOwnership(obj, QQmlEngine::CppOwnership);
Also, as BaCaRoZzo mentioned, exposing the model as a property to api might be the appropriate form. It depends on whether the function is just an accessor to an existing object or it creates the object itself.
At any rate, keep in mind that QML object lifetime management at this point cannot and should not be trusted.
Upvotes: 5