Unsolved Q_INVOKABLE from object used by QAbstractListModel
-
Hello!
I want to display a single item and it's roles in the GUI from a QAbstractItemModel. In the manual I can only find how to display the whole model.
The model emits dataChanged() in the setData() function. But the model has hundrets of items that are changed very often and very fast.(hundrets per second)
I need a signal per item, but I can't get the item in the model to work when it's derived from Qobject
Object in Model:
#ifndef DATASOURCEOBJECT_H #define DATASOURCEOBJECT_H #include <QString> #include <QVariantMap> class DataSourceObject { public: DataSourceObject(const int &id=0, const QString &name="", const QString &displayname="", const double &value=0.0); DataSourceObject(const QJsonObject &obj); int id() const; void setId(int id); QString name() const; void setName(const QString &name); QString unit() const; void setUnit(const QString &unit); double value() const; void setValue(double value); QVariantMap toMap() const; private: int m_id; QString m_name; QString m_unit; double m_value; }; #endif // DATASOURCEOBJECT_H Model Header: #ifndef DATASOURCEMODEL_H #define DATASOURCEMODEL_H #include "datasourceobject.h" #include <QAbstractListModel> class DataSourceModel : public QAbstractListModel { Q_OBJECT public: enum datasourceRoles { idRole = Qt::UserRole , nameRole, unitRole, valueRole }; explicit DataSourceModel(QObject *parent = nullptr); void addDataSourceObject(const DataSourceObject &dataSourceObject); Q_INVOKABLE QVariantMap get(int row) const; int rowCount(const QModelIndex &parent = QModelIndex()) const override; Q_INVOKABLE QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; QHash<int, QByteArray> roleNames() const override; //bool checkIndex(const QModelIndex &index) const; private: QList<DataSourceObject> m_DataSourceObjects; }; #endif // DATASOURCEMODEL_H
Model cpp:
#include "datasourcemodel.h" DataSourceModel::DataSourceModel(QObject *parent) : QAbstractListModel(parent) { } QVariantMap DataSourceModel::get(int row) const { return m_DataSourceObjects[row].toMap(); } void DataSourceModel::addDataSourceObject(const DataSourceObject &dataSourceObject) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_DataSourceObjects << dataSourceObject; endInsertRows(); } int DataSourceModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return m_DataSourceObjects.count(); } QVariant DataSourceModel::data(const QModelIndex &index, int role) const { if(index.row() < 0 || index.row() >= m_DataSourceObjects.count() || !index.isValid()) return QVariant(); const DataSourceObject &dataSourceObject = m_DataSourceObjects[index.row()]; if (role == idRole) return dataSourceObject.id(); else if (role == nameRole) return dataSourceObject.name(); else if (role == unitRole) { return dataSourceObject.unit(); } else if (role == valueRole) return dataSourceObject.value(); return QVariant(); } bool DataSourceModel::setData(const QModelIndex &index, const QVariant &value, int role) { DataSourceObject &dataSourceObject = m_DataSourceObjects[index.row()]; if (data(index, role) != value) { if(role == idRole) dataSourceObject.setId(value.toInt()); else if(role == nameRole) dataSourceObject.setName(value.toString()); else if(role == unitRole) dataSourceObject.setUnit(value.toString()); else if(role == valueRole) dataSourceObject.setValue(value.toDouble()); emit dataChanged(index, index, QVector<int>() << role); return true; } return false; } Qt::ItemFlags DataSourceModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; return Qt::ItemIsEditable; // FIXME: Implement me! } QHash<int, QByteArray> DataSourceModel::roleNames() const { QHash<int, QByteArray> roles; roles[idRole] = "id"; roles[nameRole] = "name"; roles[unitRole] = "unit"; roles[valueRole] = "value"; return roles; }
I tried to derive the object inside the model from QObject, to use Q_INVOKABLE, but when derived from QOBject, I can't compile it.
C:\Qt\5.10.0\mingw53_32\include\QtCore\qlist.h:435: Fehler: use of deleted function 'DataSourceObject::DataSourceObject(const DataSourceObject&)' if (QTypeInfo::isLarge || QTypeInfo::isStatic) n->v = new T(t);
-
You can't copy QObject classes. So either don't derive from QObject or use pointers
-
Hello Christian,
thank you, I changed it to pointers and now it's derived from QObject.
But now I have the problem that I don't know how to access items from the model directly to use their Q_PROPERTYs
Any ideas? -
Hi,
Do you mean replace:
dataSourceObject.setId(value.toInt());
by
dataSourceObject->setId(value.toInt());
?
-
I changed my model to this:
Header:#ifndef DATASOURCEMODEL_H #define DATASOURCEMODEL_H #include "datasourceobject.h" #include <QAbstractListModel> class DataSourceModel : public QAbstractListModel { Q_OBJECT public: enum datasourceRoles { idRole = Qt::UserRole , nameRole, unitRole, valueRole }; explicit DataSourceModel(QObject *parent = nullptr); void addDataSourceObject(DataSourceObject *dataSourceObject); Q_INVOKABLE QVariantMap get(int row) const; int rowCount(const QModelIndex &parent = QModelIndex()) const override; Q_INVOKABLE QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Q_INVOKABLE DataSourceObject *dataPointer(const QModelIndex &index); Qt::ItemFlags flags(const QModelIndex& index) const override; QHash<int, QByteArray> roleNames() const override; //bool checkIndex(const QModelIndex &index) const; private: QList<DataSourceObject*> m_DataSourceObjects; }; #endif // DATASOURCEMODEL_H
Cpp:
#include "datasourcemodel.h" DataSourceModel::DataSourceModel(QObject *parent) : QAbstractListModel(parent) { } QVariantMap DataSourceModel::get(int row) const { return m_DataSourceObjects[row]->toMap(); } void DataSourceModel::addDataSourceObject(DataSourceObject *dataSourceObject) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_DataSourceObjects << dataSourceObject; endInsertRows(); } int DataSourceModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return m_DataSourceObjects.count(); } QVariant DataSourceModel::data(const QModelIndex &index, int role) const { if(index.row() < 0 || index.row() >= m_DataSourceObjects.count() || !index.isValid()) return QVariant(); DataSourceObject *dataSourceObject = m_DataSourceObjects[index.row()]; if (role == idRole) return dataSourceObject->id(); else if (role == nameRole) { return dataSourceObject->name(); } else if (role == unitRole) { return dataSourceObject->unit(); } else if (role == valueRole) return dataSourceObject->value(); return QVariant(); } bool DataSourceModel::setData(const QModelIndex &index, const QVariant &value, int role) { DataSourceObject *dataSourceObject = m_DataSourceObjects[index.row()]; if (data(index, role) != value) { if(role == idRole) dataSourceObject->setId(value.toInt()); else if(role == nameRole) dataSourceObject->setName(value.toString()); else if(role == unitRole) dataSourceObject->setUnit(value.toString()); else if(role == valueRole) dataSourceObject->setValue(value.toDouble()); emit dataChanged(index, index, QVector<int>() << role); return true; } return false; } DataSourceObject *DataSourceModel::dataPointer(const QModelIndex &index) { return m_DataSourceObjects[index.row()]; } Qt::ItemFlags DataSourceModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; return Qt::ItemIsEditable; // FIXME: Implement me! } QHash<int, QByteArray> DataSourceModel::roleNames() const { QHash<int, QByteArray> roles; roles[idRole] = "id"; roles[nameRole] = "name"; roles[unitRole] = "unit"; roles[valueRole] = "value"; return roles; }
I can access now the objects from QML, but it stops working after about 40 changes and the used memory raises:
dataSourceModel.dataPointer(dataSourceModel.index(row,0)).value
Why does ist stop working after some changes of the "value" of the object
When I debug the access in QML I see the address of the object in the console, but after some changes to it's value the function dataPointer returns NULL
Console.log
DataSourceObject(0x1fcc44a8) DataSourceObject(0x1fcc44a8) DataSourceObject(0x1fcc44a8) DataSourceObject(0x1fcc44a8) DataSourceObject(0x1fcc44a8) DataSourceObject(0x1fcc44a8) DataSourceObject(0x1fcc44a8) DataSourceObject(0x1fcc44a8) DataSourceObject(0x1fcc44a8) DataSourceObject(0x1fcc44a8) DataSourceObject(0x1fcc44a8) qrc:/qml/gauges/RPMBar1.qml:13: TypeError: Cannot read property 'value' of null null null null null null
If I understand it right, the Models are changing the addresses of it's items during runtime. I need a workaround for that
-
Can you provide a minimal compilable example that shows that behaviour ?
-
@SGaist
you can download a running example here:
https://www.dropbox.com/s/nbhsx8nkvc73jqz/powertune-2.zip?dl=0
Please copy the datasources.json file into the build folder before startingNormally the data is supplied over network but I changed it to make a running example
-
property int rpm : dataSourceModel.dataPointer(dataSourceModel.index(88,0)).value
What should this do? Do you want to print the pointer to the object? Since it's a QObject it gets deleted after it gets out of scope I would guess - at least this it what it looks like when running it with valgrind
-
@Christian-Ehrlicher
It returns the property "value" of an item of the model that I want to display in QML using Q_PROPERTY. I'm open for better ways to do that. In the Qt manuals is no way described to show single items of a model and get the GUI updatet automatically.
If you run my program you can see that the GUI prints the property "value" of the item with ID "88" from the model.
What do you mean with it gets out of scope? It is still part of the model and still visible when I display the whole model in a ListView in QML. -
This is what I mean and why your object gets destroyed: https://doc.qt.io/qt-5/qtqml-cppintegration-data.html#data-ownership
I don't see a reason why this function should be Q_INVOKEABLE and even no need to access the plain data instead going through the model. -
@Christian-Ehrlicher
Then can you give me a way how to do it with the model? How can I display a single item in the QML? The data() function is of no use to me. I need "realtime" updated values in the QML. -
What should be the difference to call data() or the property value which also only calls the function value() ?
-
@Christian-Ehrlicher
When I call in QML e.g. text: mymodel.data(index,role); this only shows the momentary value of this item, but it's not updating the GUI automatically when value changes.
This is my problem. I need the GUI updated when the item changes. -
@Slash200
I only know about Qt widgets, not QML. In Qt widgets when model gets updated a signal likedataChanged
(and others) is emitted, and a linked view will receive that and update itself as necessary. Are you supposed to do something like this from QML? Or is it just supposed to work automatically? Examples tend to use, say,ListView
; I don't know whether you using it for atext
means it needs to be told to update on data change. -
@Slash200 said in Q_INVOKABLE from object used by QAbstractListModel:
but it's not updating the GUI automatically when value changes.
so why not connect to the dataChanged signal then?
e.g. here: https://forum.qt.io/topic/71392/qml-receives-undefined-for-roles-from-datachanged-signal -
The dataChanged Signal is fired for every item and I've got about 200 items which are updated hundrets times per seconds, which are minimum 20.000 signals per second. (I'm receiving realtime data from a motorsports Engine control unit via CANBUS)
This is a dashboarding software which runs on a embedded device with not very much CPU power. I want only to update the QML items when the specific item is changed. otherwise the CPU is exhausted, because every gauge will update, even if a different item is changed. I hope I've explained my problem in a way that's reasonably understandable. -
So define a custom signal in your model and connect to this.
-
@Christian-Ehrlicher
yes this works. I've got a signal: void itemValueChanged(double value, int id);
The qml item can now see by the ID if it's it's value that has to be updatet,
But this means that all items need a if to check wether if it's it's id or not and will cause also a lot of load.
Like that:Connections { target: dataSourceModel onItemValueChanged: { if(myId === id){ text = value; } } }
Is this the way you meant to do it?
-
@Slash200 said in Q_INVOKABLE from object used by QAbstractListModel:
Is this the way you meant to do it?
More or less, but you can also emit a signal for the specific entry as you seem to know the row number (88?)