A dynamic C++ model and a QML ListView - c++

I'm using a QML list view which displays one element at a time
ListView
{
model: cppobj.list
...
}
cppobj is a C++ object which can be modified, i.e. items can be removed, appended, etc. If an element is appended, the ListView goes back to the first element. What's more ListView.onRemove is not called. Any ideas how to cope with it?
Thanks
/edit: the append function of the C++ object looks like that:
void append (QString str) { m_list.append(str); emit listChanged(m_list); }

You need to use QAbstractListModel. See documentation here.

In case you want to have a ListModel for variant JSON data that you can use directly in QML, you can have a look at the JsonListModel. It can synchronize JSON data to a ListModel so you do not lose the current scroll position of the list. You can also apply transition animations and have the full ListView/ListModel features available.
ListView
{
model: JsonListModel {
source: myJsonData
keyField: "id"
}
...
}
You can find a detailed guide how to use the JsonListModel here:

Related

How are parameters passed from Qml to C++?

I would like to use call a C++ function from Qml in the following way:
Item{
id: qmlItem
function loadModel(dataModel)
{
// setup my listview
}
MyDataModel{
id: model
}
Button{
onClicked: {
cpp.addValues(model)
loadModel(model)
}
}
}
Now in C++ I have the following function:
void myCpp::addValues(MyDataModel* model)
{
// here I would like to add items to my data model
model->addItem();
}
Now this would be possible if the model was passed as a pointer, but I am not sure if this is the behaviour of C++/Qml interaction. Would what I wrote work or there is another approach I need to use?
EDIT: I have compiled the program and it works fine. However, I would like to know what is going under the hood. I mean does QML sends parameters by pointer? Would the same approach work with a JavaScript array (instead of MyDataModel)?
Actually, if you want to use model for ListView which is passed from C++ you should look at the QAbstractItemModel or QAbstractListModel(maybe better and easier for your case) class from which you inherit and create your own model to use in the qml. To subclass QAbstractListModel from you'll at least need to reimplement rowCount() , data() methods, also you can reimplement insertRows() and removeRows() methods to dynamically add/remove data from model (which will also update the view automatically).
You should look up the http://doc.qt.io/qt-4.8/qabstractlistmodel.html or http://doc.qt.io/qt-4.8/qabstractitemmodel.html
. Also, there were some easy examples in qtcreator on how to implement this kind of model

Filling QML ListView with data from std::map

I'm having trouble finding a good way to display the items of a std::map in QML. We're using a MVVM pattern in our application. The std::map contains file paths and is a member of a configuration class in the model.
Now I'm trying to show all entries of the map in QML, probably using a ListView item.
Currently we just have a couple of file paths in the configuration, so these are exposed as Q_PROPERTYies to the view model and then further to QML. But, of course, the number of paths can and will grow, thus my idea using a std::map for this. It won't be necessary to have it 'growable' at runtime, at least not in the forseeable future. But writing lots and lots of Q_PROPERTYies doesn't seem the right way for me.
Furhter question: How would I access/display the items of the map in a QML ListView - I can't figure it out and I can't find anything helpful online.
Another option than #ddriver suggested would be to create a list of QObject-derived types like:
class ConfigObject: public QObject
{
Q_PROPERTY(QString key READ key)
Q_PROPERTY(QString value READ value)
// ... getters and key and value members
};
fill a QList with them and provide them as a model for ListView either via
QVariantList configModel;
// ... fill it with ConfigObjects from std::map
engine.rootContext()->setContextProperty("configModel", &configModel); //QQmlApplicationEngine here
or a property of some class:
Q_PROPERTY(QVariant configModel READ configModel NOTIFY configModelChanged)
Then you should be able to use it via modelData.key and modelData.value in your ListView delegate.
ListView {
model: configModel // in case of using context
delegate: Item {
...
Text {
text: modelData.key
}
Text {
text: modelData.value
}
}
}
You should implement a model adapter for std::map by extending a QAbstractListModel, then you can use that as the model for a QML ListView. Implementing the right set of model roles will allow you to access the map element members without the need to use Q_PROPERTY which also requires a QObject derived datatype. Then you only need to expose the model to QML, as a context property for example.
Keep in mind the map is a sorted container, so if you modify the model you should properly reflect the index of insertion and deletion.
So you will have:
std::map -> YourCustomListModel -> ListView

Why is QML deleting my C++ ListView model?

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?
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.
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.
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.
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;
}

Is there a Dojo list container that sorts child widgets automatically?

I'd like a graphical container that I can add and remove my custom widgets to where I can set a sort function that is automatically applied when these operations take place.
Is there a suitable object in Dojo already that I've missed? Or maybe I'm not thinking about the problem correctly?
If not, are there any examples etc. of a minimal working custom container widget out there?
Dont think there is really - how would a standard component's sort functionality know, with which parameters it should weight the order, when containers can contain any widget type?
Using a layout widget extension would be your best option imho. They each have a function to add children, following this prototype:
addChild(/*Object*/ dijit, /*Integer?*/ insertIndex)
The dijit.layout.StackContainer is a good starting point, it inherits from dijit._Container (and dijit.layout._LayoutWidget). So you choose when to call the extension functionality of your override.
dojo.declare("my.Container", [dijit._Container], {
getSortOrder : function(newDijit) {
var newIndex = -1; ??
// something to work with
var currentChildren = this.getChildren();
var currentDescendants = this.getDescendants();
return newIndex;
},
addChild: function(dijit, index) {
// figure out index
arguments[1] = this.getSortOrder(dijit);
this.inherited(arguments);
}
});
But be aware, that layoutwidgets has more to it then choosing order, also positioning like with bordercontainer's region parameter.
Use SitePen's dgrid, then define a List widget with a column of type Editor. Send your custom widget to the Editor's parameter. dgrid's List widget should be able to sort as if it were a grid based on your data, and the Editor column should be able to display anything you want as part of a List's item's content.
If you need anything I'll be around. Luck,

Change C++ model with QML

I want to extend the example called "Object ListModel Example" from Qt documentation
(you can get it on http://qt-project.org/doc/qt-4.8/declarative-modelviews-objectlistmodel.html).
I am trying to add a simple GUI functionality: a menu item that changes the content
(i.e. name) of the first data item in the model. Something like this:
MenuItem {
text: "Item 123"
onClicked: {
myModel.setProperty(0,"name","Item 123") //this gives me error
}
}
I am able to create a menu in QML but I cannot find the correct way to make changes in the model.
Btw, what is a difference between setContextProperty and qmlRegisterType (only the first one is used in this example but many other examples include the second one).
That kind of model is really not suitable for modification. There is no way for the view to be notified of changes. A better option is to use a QAbstractItemModel: http://qt-project.org/doc/qt-4.8/declarative-modelviews-abstractitemmodel.html
A simpler way to use a QAbstractItemModel is via QStandardItemModel: http://qt-project.org/doc/qt-4.8/qstandarditemmodel.html
setContextProperty() adds a single named property to the context. qmlRegisterType() registers a QObject-derived type with the QML engine, allowing it to instantiate that type. For example, the QDeclarativeItem type is registered with the engine as "Item", which is how the engine knows what to create when Item {} appears in QML code.