custom model isn't completely working
-
Hi all -
I've been beating my head against this for days -- I'm sure it's something fairly obvious that I'm overlooking.
I've implemented a custom model based on QAbstractListModel. I used the tutorial found here; it's admittedly rather old, but is still referenced in the Qt 6.5 documentation. I thought I had it working, and in many respects it does - I can retrieve data from a back end and display it. But I just discovered that the model isn't completely functional - when a change is made in the back end (and relayed into the model), the GUI doesn't update.
In working on this, I discovered that my model functions setData() and roleNames() are never called. Any ideas what I might have missed?
Here's some code (pared down), though I don't know how helpful it's going to be. I'm willing to tear this to the ground and start over if it will fix the issue. Thanks for looking...
class EquipmentModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(EquipmentList *list READ list WRITE setList NOTIFY listChanged) Q_PROPERTY(equipmentUuidList uuidList READ uuidList /*WRITE setUuidList*/ NOTIFY uuidListChanged) enum EquipmentRoles { UuidRole = Qt::UserRole, NameRole, FwVersionRole, ModelIdRole, StateRole, NbrRoles }; private: EquipmentList *m_list; // a list of EquipmentItem objects public: explicit EquipmentModel(QObject *parent = nullptr, MessageMgr *messageMgr = nullptr); 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; virtual QHash<int, QByteArray> roleNames() const override; }
-
Hi all -
I've been beating my head against this for days -- I'm sure it's something fairly obvious that I'm overlooking.
I've implemented a custom model based on QAbstractListModel. I used the tutorial found here; it's admittedly rather old, but is still referenced in the Qt 6.5 documentation. I thought I had it working, and in many respects it does - I can retrieve data from a back end and display it. But I just discovered that the model isn't completely functional - when a change is made in the back end (and relayed into the model), the GUI doesn't update.
In working on this, I discovered that my model functions setData() and roleNames() are never called. Any ideas what I might have missed?
Here's some code (pared down), though I don't know how helpful it's going to be. I'm willing to tear this to the ground and start over if it will fix the issue. Thanks for looking...
class EquipmentModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(EquipmentList *list READ list WRITE setList NOTIFY listChanged) Q_PROPERTY(equipmentUuidList uuidList READ uuidList /*WRITE setUuidList*/ NOTIFY uuidListChanged) enum EquipmentRoles { UuidRole = Qt::UserRole, NameRole, FwVersionRole, ModelIdRole, StateRole, NbrRoles }; private: EquipmentList *m_list; // a list of EquipmentItem objects public: explicit EquipmentModel(QObject *parent = nullptr, MessageMgr *messageMgr = nullptr); 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; virtual QHash<int, QByteArray> roleNames() const override; }
-
@jsulm I left that out because they're not getting called, so for now, what they (would) do seemed irrelevant. The first line in both is a qDebug() to let me know the function is called.
Here's an example of the qml that doesn't get updated properly. It was a real Switch but for debugging, I replaced it because the Switch was reflecting changes to itself, not to the model (if that makes sense), so I just created a "fake" Switch for testing.
Rectangle { id: fakeSwitch height: 30; width: 30 property bool switchState: equipmentModel.getEquipmentItem(uuid).powerState color: switchState ? 'green' : 'gray' MouseArea { anchors.fill: parent onClicked: { fakeSwitch.switchState = !fakeSwitch.switchState equipmentModel.sendSwitchChange(card.uuid, fakeSwitch.switchState) } } }
When I click on this switch, all the right things happen: I send a change request to the back end, and when the response is received, the color updates, etc. BUT: if I have two of these on different screens, when I make the change request in one screen, and go to the other, the change isn't reflected there. I suspect this is because my setData() isn't getting called, so my dataChanged() signal isn't getting sent.
-
Hi all -
I've been beating my head against this for days -- I'm sure it's something fairly obvious that I'm overlooking.
I've implemented a custom model based on QAbstractListModel. I used the tutorial found here; it's admittedly rather old, but is still referenced in the Qt 6.5 documentation. I thought I had it working, and in many respects it does - I can retrieve data from a back end and display it. But I just discovered that the model isn't completely functional - when a change is made in the back end (and relayed into the model), the GUI doesn't update.
In working on this, I discovered that my model functions setData() and roleNames() are never called. Any ideas what I might have missed?
Here's some code (pared down), though I don't know how helpful it's going to be. I'm willing to tear this to the ground and start over if it will fix the issue. Thanks for looking...
class EquipmentModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(EquipmentList *list READ list WRITE setList NOTIFY listChanged) Q_PROPERTY(equipmentUuidList uuidList READ uuidList /*WRITE setUuidList*/ NOTIFY uuidListChanged) enum EquipmentRoles { UuidRole = Qt::UserRole, NameRole, FwVersionRole, ModelIdRole, StateRole, NbrRoles }; private: EquipmentList *m_list; // a list of EquipmentItem objects public: explicit EquipmentModel(QObject *parent = nullptr, MessageMgr *messageMgr = nullptr); 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; virtual QHash<int, QByteArray> roleNames() const override; }
@mzimmers well, you will only see a change in your qml delegates, if your EquipmentModel emits the "dataChanged" signal. Are you sure thats emitted, when you change the data via cpp ?
Otherwise the QML part will think it still has the actual data and will not fetch the new set from the model.
-
@mzimmers well, you will only see a change in your qml delegates, if your EquipmentModel emits the "dataChanged" signal. Are you sure thats emitted, when you change the data via cpp ?
Otherwise the QML part will think it still has the actual data and will not fetch the new set from the model.
@J-Hilk said in custom model isn't completely working:
Are you sure thats emitted, when you change the data via cpp ?
Yes. In my routine that handles the response from the backend, I have another qDebug() that shows dataChanged() being emitted (and it is). Somehow, though, that signal isn't causing my setData() to be called.
-
@J-Hilk said in custom model isn't completely working:
Are you sure thats emitted, when you change the data via cpp ?
Yes. In my routine that handles the response from the backend, I have another qDebug() that shows dataChanged() being emitted (and it is). Somehow, though, that signal isn't causing my setData() to be called.
@mzimmers said in custom model isn't completely working:
shows dataChanged() being emitted (and it is). Somehow, though, that signal isn't causing my setData() to be called.
Remember I cannot help if this is a QML/display delegate side. But just from the C++ model side, what do you mean by this?
setData()
should emitdataChanged()
signal, butdataChanged()
should not causesetData()
to be called (unless you have wired something up). -
@J-Hilk said in custom model isn't completely working:
Are you sure thats emitted, when you change the data via cpp ?
Yes. In my routine that handles the response from the backend, I have another qDebug() that shows dataChanged() being emitted (and it is). Somehow, though, that signal isn't causing my setData() to be called.
-
@J-Hilk said in custom model isn't completely working:
setData is only called, "automatically" if you change something in by the delegate via the Qt::EditRole
OK, so in my case, where should that happen? I don't want my app to change the Switch setting; I only want it to send a request to the back end to make the change. Then, when I get a (successful) response, I change the value here:
void EquipmentModel::processPostResponse() { QUuid uuid; // code for setting this has been removed. int i; i = m_list->getIndex(uuid); EquipmentItem itemFromList = m_list->getEquipmentItem(uuid); if (itemFromList.powerState() == Equipment::POWER_OFF) { itemFromList.setPowerState(Equipment::POWER_ON); } else { itemFromList.setPowerState(Equipment::POWER_OFF); } if (m_list->setItemAt(i, itemFromList)) { QModelIndex qmi = index(i, 0, QModelIndex()); qDebug() << __PRETTY_FUNCTION__ << "emitting dataChanged(); qmi is" << qmi; emit dataChanged(qmi, qmi); } }
-
@J-Hilk said in custom model isn't completely working:
setData is only called, "automatically" if you change something in by the delegate via the Qt::EditRole
OK, so in my case, where should that happen? I don't want my app to change the Switch setting; I only want it to send a request to the back end to make the change. Then, when I get a (successful) response, I change the value here:
void EquipmentModel::processPostResponse() { QUuid uuid; // code for setting this has been removed. int i; i = m_list->getIndex(uuid); EquipmentItem itemFromList = m_list->getEquipmentItem(uuid); if (itemFromList.powerState() == Equipment::POWER_OFF) { itemFromList.setPowerState(Equipment::POWER_ON); } else { itemFromList.setPowerState(Equipment::POWER_OFF); } if (m_list->setItemAt(i, itemFromList)) { QModelIndex qmi = index(i, 0, QModelIndex()); qDebug() << __PRETTY_FUNCTION__ << "emitting dataChanged(); qmi is" << qmi; emit dataChanged(qmi, qmi); } }
-
@mzimmers
In itself this seems OK to me. So what's with you complaining aboutsetData()
not getting called? Does your override ofsetData()
do much the same as yoursetItemAt()
code here? It doesn't do something else "special" you are relying on?@JonB based on what you said above, I replaced the line:
emit dataChanged(qmi, qmi);
with:
setData(qmi, itemFromList.powerState(), StateRole);
and now setData is indeed being called. Here's my setData() function:
bool EquipmentModel::setData(const QModelIndex &index, const QVariant &value, int role) { bool rc = false; qDebug() << __PRETTY_FUNCTION__ << "role is" << role; do { if (m_list == nullptr) { continue; } EquipmentItem item = m_list->equipment().at(index.row()); switch (role) { case UuidRole: item.setUuid(value.toUuid()); break; case NameRole: item.setName(value.toString()); break; case FwVersionRole: item.setFwVersion(value.toString()); break; case ModelIdRole: item.setModelId(value.toString()); break; case StateRole: item.setPowerState(Equipment::PowerState(value.toInt())); qDebug() << __PRETTY_FUNCTION__ << "power state set to" << value.toInt(); break; } if (m_list->setItemAt(index.row(), item)) { qDebug() << __PRETTY_FUNCTION__ << "setItemAt() returned successfully; role is" << role; emit dataChanged(index, index, QVector<int>() << role); rc = true; } else { qWarning() << __PRETTY_FUNCTION__ << "error from setItemAt."; } } while (false); return rc; }
The qDebug statements suggest that dataChanged is being emitted, so I guess I'm missing something downstream from this.
-
@JonB based on what you said above, I replaced the line:
emit dataChanged(qmi, qmi);
with:
setData(qmi, itemFromList.powerState(), StateRole);
and now setData is indeed being called. Here's my setData() function:
bool EquipmentModel::setData(const QModelIndex &index, const QVariant &value, int role) { bool rc = false; qDebug() << __PRETTY_FUNCTION__ << "role is" << role; do { if (m_list == nullptr) { continue; } EquipmentItem item = m_list->equipment().at(index.row()); switch (role) { case UuidRole: item.setUuid(value.toUuid()); break; case NameRole: item.setName(value.toString()); break; case FwVersionRole: item.setFwVersion(value.toString()); break; case ModelIdRole: item.setModelId(value.toString()); break; case StateRole: item.setPowerState(Equipment::PowerState(value.toInt())); qDebug() << __PRETTY_FUNCTION__ << "power state set to" << value.toInt(); break; } if (m_list->setItemAt(index.row(), item)) { qDebug() << __PRETTY_FUNCTION__ << "setItemAt() returned successfully; role is" << role; emit dataChanged(index, index, QVector<int>() << role); rc = true; } else { qWarning() << __PRETTY_FUNCTION__ << "error from setItemAt."; } } while (false); return rc; }
The qDebug statements suggest that dataChanged is being emitted, so I guess I'm missing something downstream from this.
@mzimmers
This looks OK.Except that you call
m_list->setItemAt(index.row(), item)
unconditionally without looking at passed inrole
.Now I know QML uses special roles, and I have no idea whether it would do this, but if your
setData()
is called with, say,role == Qt::ForegroundRole
you set the item in the list of values nonetheless. Usually you would test for theEditRole
role. From QML you may need to include your explicitUuidRole
etc. as well, I don't know. If QML never callssetData()
with the various roles listed in https://doc.qt.io/qt-6/qt.html#ItemDataRole-enum this may not matter. -
@mzimmers
This looks OK.Except that you call
m_list->setItemAt(index.row(), item)
unconditionally without looking at passed inrole
.Now I know QML uses special roles, and I have no idea whether it would do this, but if your
setData()
is called with, say,role == Qt::ForegroundRole
you set the item in the list of values nonetheless. Usually you would test for theEditRole
role. From QML you may need to include your explicitUuidRole
etc. as well, I don't know. If QML never callssetData()
with the various roles listed in https://doc.qt.io/qt-6/qt.html#ItemDataRole-enum this may not matter.@JonB my setData() is patterned after the example I mentioned in my first post. I see what you're saying (I think), and I should probably fix that, but it doesn't seem to be influencing the code execution (according to the telltales).
Nowhere in my qml am I calling setData(); remember, I don't want my app to change anything (like the power state); merely to send a request to the back end for that change, and properly handle the response.
Thanks...
-
@JonB my setData() is patterned after the example I mentioned in my first post. I see what you're saying (I think), and I should probably fix that, but it doesn't seem to be influencing the code execution (according to the telltales).
Nowhere in my qml am I calling setData(); remember, I don't want my app to change anything (like the power state); merely to send a request to the back end for that change, and properly handle the response.
Thanks...
-
@JoeCFD hmm...I think you're on to something.
I created a setList() function (also patterned after the example) that contains those calls:
void EquipmentModel::setList(EquipmentList *list) { beginResetModel(); if (m_list != nullptr) { m_list->disconnect(this); } m_list = list; if (m_list != nullptr) { connect(m_list, &EquipmentList::preItemAppended, this, [=]() { const int index = m_list->equipment().size(); beginInsertRows(QModelIndex(), index, index); }); connect(m_list, &EquipmentList::postItemAppended, this, [=]() { endInsertRows(); }); connect(m_list, &EquipmentList::preItemRemoved, this, [=](int index) { beginRemoveRows(QModelIndex(), index, index); }); connect(m_list, &EquipmentList::postItemRemoved, this, [=]() { endRemoveRows(); }); } endResetModel(); }
But...I never call it. I can try to modify my setData() to use it, though it will be tricky, as my list is a QObject, so I can't copy it. What do you think?
EDIT:
I modified this portion of my setData() as follows; still not getting the changes in the displays:
beginResetModel(); if (m_list->setItemAt(index.row(), item)) { qDebug() << __PRETTY_FUNCTION__ << "setItemAt() returned successfully; role is" << role; emit dataChanged(index, index, QVector<int>() << role); rc = true; } else { qWarning() << __PRETTY_FUNCTION__ << "error from setItemAt."; } endResetModel();
I could move the begin/endResetModel() calls to the setItemAt() function, but I think the results would be the same, right?
-
@JoeCFD hmm...I think you're on to something.
I created a setList() function (also patterned after the example) that contains those calls:
void EquipmentModel::setList(EquipmentList *list) { beginResetModel(); if (m_list != nullptr) { m_list->disconnect(this); } m_list = list; if (m_list != nullptr) { connect(m_list, &EquipmentList::preItemAppended, this, [=]() { const int index = m_list->equipment().size(); beginInsertRows(QModelIndex(), index, index); }); connect(m_list, &EquipmentList::postItemAppended, this, [=]() { endInsertRows(); }); connect(m_list, &EquipmentList::preItemRemoved, this, [=](int index) { beginRemoveRows(QModelIndex(), index, index); }); connect(m_list, &EquipmentList::postItemRemoved, this, [=]() { endRemoveRows(); }); } endResetModel(); }
But...I never call it. I can try to modify my setData() to use it, though it will be tricky, as my list is a QObject, so I can't copy it. What do you think?
EDIT:
I modified this portion of my setData() as follows; still not getting the changes in the displays:
beginResetModel(); if (m_list->setItemAt(index.row(), item)) { qDebug() << __PRETTY_FUNCTION__ << "setItemAt() returned successfully; role is" << role; emit dataChanged(index, index, QVector<int>() << role); rc = true; } else { qWarning() << __PRETTY_FUNCTION__ << "error from setItemAt."; } endResetModel();
I could move the begin/endResetModel() calls to the setItemAt() function, but I think the results would be the same, right?
@mzimmers
https://doc.qt.io/qt-6/qabstractitemmodel.html#beginResetModel
When a model is reset it means that any previous data reported from the model is now invalid and has to be queried for again. This also means that the current item and any selected items will become invalid.When a model radically changes its data it can sometimes be easier to just call this function rather than emit dataChanged() to inform other components when the underlying data source, or its structure, has changed.
You must call this function before resetting any internal data structures in your model or proxy model.
-
@mzimmers
https://doc.qt.io/qt-6/qabstractitemmodel.html#beginResetModel
When a model is reset it means that any previous data reported from the model is now invalid and has to be queried for again. This also means that the current item and any selected items will become invalid.When a model radically changes its data it can sometimes be easier to just call this function rather than emit dataChanged() to inform other components when the underlying data source, or its structure, has changed.
You must call this function before resetting any internal data structures in your model or proxy model.
@JoeCFD it's as though the QML property that reflects the state just isn't getting the message to update. I wonder if there's something wrong with how I'm setting that property:
Rectangle { id: fakeSwitch height: 30; width: 30 property bool switchState: equipmentModel.getEquipmentItem(pumpUuid).powerState // does this look OK? color: switchState ? 'green' : 'gray' MouseArea { anchors.fill: parent onClicked: { fakeSwitch.switchState = !fakeSwitch.switchState equipmentModel.sendSwitchChange(pumpUuid, fakeSwitch.switchState) } } }
-
@JoeCFD it's as though the QML property that reflects the state just isn't getting the message to update. I wonder if there's something wrong with how I'm setting that property:
Rectangle { id: fakeSwitch height: 30; width: 30 property bool switchState: equipmentModel.getEquipmentItem(pumpUuid).powerState // does this look OK? color: switchState ? 'green' : 'gray' MouseArea { anchors.fill: parent onClicked: { fakeSwitch.switchState = !fakeSwitch.switchState equipmentModel.sendSwitchChange(pumpUuid, fakeSwitch.switchState) } } }
@mzimmers said in custom model isn't completely working:
property bool switchState: equipmentModel.getEquipmentItem(pumpUuid).powerState // does this look OK?
One thing. Your C++ code shows this is not a
bool
property, you test/setpowerState()
againstEquipment::POWER_OFF
/POWER_ON
. Do these values convert correctly tobool
? -
@JoeCFD it's as though the QML property that reflects the state just isn't getting the message to update. I wonder if there's something wrong with how I'm setting that property:
Rectangle { id: fakeSwitch height: 30; width: 30 property bool switchState: equipmentModel.getEquipmentItem(pumpUuid).powerState // does this look OK? color: switchState ? 'green' : 'gray' MouseArea { anchors.fill: parent onClicked: { fakeSwitch.switchState = !fakeSwitch.switchState equipmentModel.sendSwitchChange(pumpUuid, fakeSwitch.switchState) } } }
Another data point in this little mystery: I have 3 locations in my GUI that show the value of this model element. Two are in ordinary screens, and they're not updating correctly. The third is in a drawer, and it's always correct. The Switch code is virtually identical. Does the act of opening a drawer force an update to its contents? Is that why that one is always right?
-
@mzimmers said in custom model isn't completely working:
property bool switchState: equipmentModel.getEquipmentItem(pumpUuid).powerState // does this look OK?
One thing. Your C++ code shows this is not a
bool
property, you test/setpowerState()
againstEquipment::POWER_OFF
/POWER_ON
. Do these values convert correctly tobool
?@JonB I think it is. I started using that FakeSwitch because I couldn't tell whether the real Switch was self-updating, and giving me a red herring. Here's the code from the real Switch, and its behavior is essentially the same (nothing in the custom switch affects this issue):
Switch_custom { id: cardSwitch visible: card.switchable checked: equipmentModel.getEquipmentItem(uuid).powerState === 1 Layout.alignment: Qt.AlignRight onClicked: { console.log("EquipmentCard.qml: switch clicked; value is " + cardSwitch.checked) equipmentModel.sendSwitchChange(card.uuid, cardSwitch.checked) } }
-
Another data point in this little mystery: I have 3 locations in my GUI that show the value of this model element. Two are in ordinary screens, and they're not updating correctly. The third is in a drawer, and it's always correct. The Switch code is virtually identical. Does the act of opening a drawer force an update to its contents? Is that why that one is always right?