PDA

View Full Version : QQmlPropertyMap, model



sBoff
26th August 2013, 05:15
Hi all,

I’m trying to get my head around this and after much research and testing I feel I must be missing something simple.

I have a QQmlPropertyMap derived object that I am adding to in my C++ code. I am then binding to the data in my QML – this is all working great. When I update the value on the C++ side my UI also updates.

C++ side:

// Set the 'runtime' Context Property, this is my QQmlPropertyMap derived object
Runtime rt;
rt.GetData()->insert("abc", 120);
rt.GetData()->insert("xyz", 65);
view.rootContext()->setContextProperty("runtime", &rt);


QML side:

MyQmlObject
{
value: runtime.data.abc
}

This next part is where I’m getting stuck/confused.. I want to have a QML table that displays all the key/values in the property map and automatically updates. How do I go about this? I cant find any examples that use the QQmlPropertyMap as a model for a TableView or any other view.


TableView
{
anchors.fill: parent
model: runtime.model // ????
}


Surely there must be a simple way I’m not aware of? How can I use a QQmlPropertyMap as the model for the QML TableView?

Thanks

wysota
26th August 2013, 07:53
Why don't you just use a regular model instead of a property map?

sBoff
26th August 2013, 10:18
Hi wysota,

Thanks for your reply. Do you mean derive my own model object from say QAbstractTableModel or similar?

I originally started with QQmlPropertyMap as I need a way to easily map key/value pairs and to use this data to bind to QML objects - for that purpose QQmlPropertyMap seemed like the ideal choice. Maybe now that I am needing to display the data in a tableview that approach is no longer the best way?

Regards

wysota
26th August 2013, 10:28
Thanks for your reply. Do you mean derive my own model object from say QAbstractTableModel or similar?
More likely QAbstractListModel. But in general, yes.


I originally started with QQmlPropertyMap as I need a way to easily map key/value pairs and to use this data to bind to QML objects - for that purpose QQmlPropertyMap seemed like the ideal choice. Maybe now that I am needing to display the data in a tableview that approach is no longer the best way?

Maybe :)

sBoff
26th August 2013, 10:44
More likely QAbstractListModel. But in general, yes.

Thanks for your input :) - Ill try this out and post up if I have any issues with it. I had thought about doing this but I was sure there must have been an 'easy' way (it seems like a common use case to me?), but thankyou again; its nice to have another brain out there provide some clarity!

Regards

sBoff
26th August 2013, 23:48
It looks like I've come full circle. :)

I originally started this project a long time ago but then with my daughters birth had put things on hold. I'm now managing to scrape some time here and there so thought I would start it up again.

wysota, I've now created a QAbstractListModel derived object which stores my data in an internal QVector. This is working beautifully in the QML side and I have a TableView that shows all entries and updates correctly when the internal data updates.



class DataEngine : public QAbstractListModel
{
private:
QVector<Data> m_Data;
};


This leads me onto the second issue which was what I originally faced so long ago - Data is added to my 'data engine' dynamically at runtime, I dont know prior to runtime what data will be available. I need to be able to bind a QML object's value to a data entry and have the QML object also update when the data changes.



MyQmlObject
{
value: runtime.abc // <-- abc is a dynamic, we dont know until runtime that there is a property called abc
}


As far as I'm aware there is no mechanism exposed in Qt to allow for dynamic properties (other than using QQmlPropertyMap, QQmlListProperty etc)? If this is correct do I go about binding a UI element's value to a data entry? I guess I could always use a Q_INVOKABLE member to return the data however, how would I then get the QML object to upadte when the value has changed?

I'm currently thinking I'll have to use a QQmlListProperty as my internal data storage for my QAbstractListModel derived object. The problem is this leads back to the original issue - I now have to either:

- Make sure a data entry can only ever be modified through the QAbstractListModel derived object (to keep the update notifications of QAbstractListModel and the QQmlPropertyList in sync)
OR
- Find a way to detect a change in a data entry and inform the QAbstractListModel that it also needs to update.

I hope that makes sense? Any thoughts/suggestions?

wysota
27th August 2013, 07:40
how would I then get the QML object to upadte when the value has changed?
Bind the object's property to a function call that will return the data or invoke some imperative code when contents of the model change.


I'm currently thinking I'll have to use a QQmlListProperty as my internal data storage for my QAbstractListModel derived object.
No, that's not a good idea.


- Make sure a data entry can only ever be modified through the QAbstractListModel derived object
That's always the proper approach.

sBoff
27th August 2013, 10:33
Bind the object's property to a function call that will return the data or invoke some imperative code when contents of the model change.

This is fine, I have the QML object's property bound to the a function (I can see it hitting the breakpoint where I return a value) however, this only gets called once while the QML object is loading. From that point on, it never updates despite the data changing (this the issue QQmlPropertyMap and the likes resolved). I guess the question then boils down to, how can I update the QML object when I know data that it references has been updated?

Edit: I've had a bit of a play with this and the only solution I can come up with is binding the QML object's value a C++ object's property and provide a NOTIFY signal


C++:
class Runtime : public QObject
{
Q_OBJECT
Q_PROPERTY(RuntimeData* data READ GetData NOTIFY modelChanged)
public:


The RuntimeData object is my QAbstractListModel derived object. In this object I've then created a Q_INVOKABLE member that retuns the data value base on an index (for now).


class RuntimeData : public QAbstractListModel
{
Q_OBJECT
public:
Q_INVOKABLE int GetData(int index)
{
if (index >= 0 && index < m_Data.count())
return m_Data[index].Value().toInt();
return 0;
}


Finally, its all working but there is a remaining issue. With this implementaion, anytime I have to update any data, I also have to emit the modelChanged signal to inform the QML to update. Hypothetically if I have 1000 data entries and 1000 QML objects bound to these, if one data item changes all QML objects will update.

Again, I feel like I must be missing something? Why is this so complicated?