QML receives undefined for roles from dataChanged signal
-
I have a QAbstractListModel that works ok but updates are not very efficient, I want to know which role's data has changed.
I wish to use the roles parameter from the dataChanged signal but QML always gets undefined for the array data.
I am using a model that extends QAbstractListModel.I am emitting from c++ like this:
QVector<int> roles; roles.push_back(ModeRole); roles.push_back(StatusRole); emit dataChanged(index(0), index(barrierCount - 1), roles);
QML receiving code:
onDataChanged: console.log("dataChanged", topLeft, bottomRight, roles, roles.length)
Console output:
qml: dataChanged QModelIndex(1,0,0x0,BarrierModel(0x13f86d720)) QModelIndex(1,0,0x0,BarrierModel(0x13f86d720)) QVariant(QVector<int>) undefined
I found this unresolved thread where someone else appears to be having the same issue:
Re: How to emit dataChanged to a single role of QAbstractListModel?Im using Qt5.6.1 on Windows 7 x64, but that shouldnt matter.
-
@KiNgFrUiT
I think it isundefined
because it doesnot understand whatQVector
is and thus it cant convert thatQVariant
into corresponding data type(perhaps JS array) on QML side and hence doesnot understand whatlength
is.Now its fine that you emit
dataChanged
with roles as its parameter. IMO there is no use on listening to that on QML side i.e you can't do anything with those roles there. Once you emitdataChanged
its the resposibilty of the view to update to new changes. We dont have to explicitly set them again in QML. If they are not updated properly then there's fair chance that proper index's have not been supplied todatachanged
.
Also is index your custom method? Because the pure virtual one inQAbstractItemModel
requires atleast 2 parameters. -
Thanks for your reply.
According to the following page, QML automatically treats QVector as a Javascript array.
http://doc.qt.io/qt-5/qtqml-cppintegration-data.htmlThe index function is just the one from QAbstractListModel. QAbstractItemListModel was a typo.
QModelIndex QAbstractListModel::index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const
-
As said here:
There are two ways in which such sequences can be exposed to QML: as a Q_PROPERTY of the given sequence type; or as the return type of a Q_INVOKABLE method.
emit dataChanged(index(0), index(barrierCount - 1), roles);
How did you manage to pass just single argument to index ? Didn't the compiler complain ?
so even if we get pass this my question is what will you do in the
onDataChanged
handler in QML ?
All I wanted to say is that if we pass appropriate data to dataChanged the view should update appropriately. -
QML function and connection code is as follows:
property var mode: 0 property var status: 1 function barrierDataChanged(topLeft, bottomRight, roles) { console.log("dataChanged", topLeft, bottomRight, roles, roles.length) var modelIndex = barrierModel.getBarrierIndex(barrierId) if(topLeft.row == modelIndex) { if(roles.contains("mode")) { // contains function is just a guess for now mode = barrierModel.get(modelIndex).mode } else if(roles.contains("status")) { // contains function is just a guess for now status = barrierModel.get(modelIndex).status } } } Connections { target: barrierModel onDataChanged: barrierDataChanged(topLeft, bottomRight, roles) }
-
Alright. What are you going to do once you get those values in
barrierDataChanged
?
I think you should probably rephrase your original question. I dont see any connection between your questionI have a QAbstractItemListModel QAbstractListModel that works ok but updates are not very efficient, not all roles need to be updated.
and the code you posted. -
Currently I need to update mode and status every time dataChanged is emitted, as I cannot tell which role has changed.
If I can get the role parameter to contain useful data, instead of undefined, then the desired implementation will be more efficient as only the variable associated with the changed role needs updating.
Existing Code:
property var mode: 0 property var status: 1 function barrierDataChanged(topLeft, bottomRight) { var modelIndex = barrierModel.getBarrierIndex(barrierId) if(topLeft.row == modelIndex) { mode = barrierModel.get(modelIndex).mode status = barrierModel.get(modelIndex).status } } Connections { target: barrierModel onDataChanged: barrierDataChanged(topLeft, bottomRight) }
Desired Code:
property var mode: 0 property var status: 1 function barrierDataChanged(topLeft, bottomRight, roles) { console.log("dataChanged", topLeft, bottomRight, roles, roles.length) var modelIndex = barrierModel.getBarrierIndex(barrierId) if(topLeft.row == modelIndex) { if(roles.contains("mode")) { // contains function is just a guess for now mode = barrierModel.get(modelIndex).mode } else if(roles.contains("status")) { // contains function is just a guess for now status = barrierModel.get(modelIndex).status } } } Connections { target: barrierModel onDataChanged: barrierDataChanged(topLeft, bottomRight, roles) }
-
@KiNgFrUiT Are you talking something as shown in the following example ?
http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel-subclass -
Yes, in that example the Animal roles are type and size.
In my application the roles are mode and status etc, I have simplified my code for this discussion.enum BarrierRole { BarrierIdRole = Qt::UserRole + 1, NameRole, ModeRole, StatusRole, BulbCountRole, BulbFaultsRole, InductionLoopRole };
-
@KiNgFrUiT The only possibility left according to me(or try finding a way to make QVector available to QML, may be Q_DECLARE_METATYPE?? not sure) here is to create a
Q_INVOKABLE
in C++ model and call it from QML. This function will take arguments as bothQModelIndex
and theQVector<int>
of roles. Then this function will resolve the values and return the data to QML.
For eg:
C++QVariant MyModel::getValueByRole(QModelIndex t, QModelIndex b, QVector<int> role) { qDebug() << t.data(role[0]); }
QML
myModel.getValueByRole(topLeft, bottomRight, roles)
-
@KiNgFrUiT It works in Qt 5.7. I just went through the documentation on the web and the installed one. I was using Qt 5.6 earlier. There were differences. It seems that they have made available
QVector<int>
to QML in the latest version. If you are still looking for this issue try upgrading Qt to the latest version. -
@p3c0 But as expected it returns an
int
and since as per your code you require a string to compare you will need some additional code on CPP side to get role value from the key. For eg:QString MyModel::getRoleValue(int key) { return roleNames().value(key); }
This will return rolename string using the key that you receive in
onDataChanged
handler'sroles
parameter. eg.roles[0]
,roles[1]
...Or instead you can directly get the value using
data()
which requires aQModelIndex
and the role.var str = myModel.data(topLeft, roles[0])
-
@KiNgFrUiT You're Welcome :) Happy Coding.