exposing entire C++ object to QML
-
@mzimmers is the question about how to access a model role from a delegate or how to access properties of an object?
@GrecKo more the latter. I'm trying to pass an instance from my model list from a delegate to another QML module, all in one line of code (if possible).
Right now, I only know how to access individual properties; I'm sure there's a way to refer to the entire object that the delegate is currently set on, but I don't know what that is.
EDIT: this is what I want to be able to do:
EDIT 2: simplified example (no need for the drawer)// space summary screen ListView { model: spaceModel delegate: SpaceCard { // MAGIC to pass the model element } // ... // SpaceCard.qml Item { id: spaceCard // MAGIC to reference the model element text: MAGIC.text uuid: MAGIC.uuid // etc. (imagine that the model element had 100 fields -
@sierdzio said in exposing entire C++ object to QML:
You can return Space object in your data() method of your model. Something like (pseudocode):
Great idea - I'm going to do this. It never occurred to me to use data() for the entire object as well as the individual roles.
But: again, how do I code the access to the entire object in my QML? What is the construct for accessing a complete object from within a delegate?
Thanks...
@mzimmers said in exposing entire C++ object to QML:
@sierdzio said in exposing entire C++ object to QML:
You can return Space object in your data() method of your model. Something like (pseudocode):
Great idea - I'm going to do this. It never occurred to me to use data() for the entire object as well as the individual roles.
But: again, how do I code the access to the entire object in my QML? What is the construct for accessing a complete object from within a delegate?
Thanks...
If you use
roleNames()and custom roles then it is entirely up to you.
If you just return a list of objects to act as a model, then you can access current element throughmodelData, for examplemodelData.uuid. -
@mzimmers said in exposing entire C++ object to QML:
@sierdzio said in exposing entire C++ object to QML:
You can return Space object in your data() method of your model. Something like (pseudocode):
Great idea - I'm going to do this. It never occurred to me to use data() for the entire object as well as the individual roles.
But: again, how do I code the access to the entire object in my QML? What is the construct for accessing a complete object from within a delegate?
Thanks...
If you use
roleNames()and custom roles then it is entirely up to you.
If you just return a list of objects to act as a model, then you can access current element throughmodelData, for examplemodelData.uuid.@sierdzio OK, I think I now understand what you're saying. I've done the following:
- modified my Space struct:
struct Space { Q_GADGET QML_VALUE_TYPE(space) // so "space" can be used in QML. public: QString m_name; Q_INVOKABLE QString name() { return m_name; }-
added a CompleteObjectRole to my SpaceModel roles
-
added to SpaceModel::data() for this role:
QVariant SpaceModel::data(const QModelIndex &index, int role) const { QVariant qv = QVariant(); do { Space item = m_list->at(index.row()); switch (role) { case CompleteObjectRole: qv = QVariant::fromValue(item)); break;- use this in my QML:
// space summary screen ListView { id: spaceCards model: spaceProxyModel delegate: SpaceCard { mySpace: completeObject // SpaceCard.qml Item { id: spaceCard property space mySpace property string titleText: mySpace.name()It works, and is what I was asking for. Is this what you expected/intended? Thanks...
EDIT:
I discovered a flaw in this approach: the property mySpace in SpaceCard.qml is evidently a static copy, and so its data isn't updated when the model changes. Is there a way to fix this?
Thanks...
-
@sierdzio OK, I think I now understand what you're saying. I've done the following:
- modified my Space struct:
struct Space { Q_GADGET QML_VALUE_TYPE(space) // so "space" can be used in QML. public: QString m_name; Q_INVOKABLE QString name() { return m_name; }-
added a CompleteObjectRole to my SpaceModel roles
-
added to SpaceModel::data() for this role:
QVariant SpaceModel::data(const QModelIndex &index, int role) const { QVariant qv = QVariant(); do { Space item = m_list->at(index.row()); switch (role) { case CompleteObjectRole: qv = QVariant::fromValue(item)); break;- use this in my QML:
// space summary screen ListView { id: spaceCards model: spaceProxyModel delegate: SpaceCard { mySpace: completeObject // SpaceCard.qml Item { id: spaceCard property space mySpace property string titleText: mySpace.name()It works, and is what I was asking for. Is this what you expected/intended? Thanks...
EDIT:
I discovered a flaw in this approach: the property mySpace in SpaceCard.qml is evidently a static copy, and so its data isn't updated when the model changes. Is there a way to fix this?
Thanks...
@mzimmers yup, that's exactly what I had in mind!
EDIT:
I discovered a flaw in this approach: the property mySpace in SpaceCard.qml is evidently a static copy, and so its data isn't updated when the model changes. Is there a way to fix this?
First, try adding a MEMBER
Q_PROPERTYto your gadget, it might work.struct Space { Q_GADGET QML_VALUE_TYPE(space) // so "space" can be used in QML. Q_PROPERTY(QString name MEMBER m_name) public: QString m_name; Q_INVOKABLE QString name() { return m_name; } // ... property string titleText: mySpace.nameIf not, then you'll have to change Space to Q_OBJECT which does support signals & slots so it will also dynamically update for sure.
-
@mzimmers yup, that's exactly what I had in mind!
EDIT:
I discovered a flaw in this approach: the property mySpace in SpaceCard.qml is evidently a static copy, and so its data isn't updated when the model changes. Is there a way to fix this?
First, try adding a MEMBER
Q_PROPERTYto your gadget, it might work.struct Space { Q_GADGET QML_VALUE_TYPE(space) // so "space" can be used in QML. Q_PROPERTY(QString name MEMBER m_name) public: QString m_name; Q_INVOKABLE QString name() { return m_name; } // ... property string titleText: mySpace.nameIf not, then you'll have to change Space to Q_OBJECT which does support signals & slots so it will also dynamically update for sure.
@sierdzio I'd already tried using Q_PROPERTY; sadly, no success. I guess I'm going to have to go through the effort of basing Space off of QObject...ugh.
I'll report back when I'm done.
EDIT:
@sierdzio is there really no other way to accomplish this? I ask because
- this is going to be a non-trivial effort, and
- the example in the docs doesn't do it this way.
Any alternative is welcome.
-
@mzimmers yup, that's exactly what I had in mind!
EDIT:
I discovered a flaw in this approach: the property mySpace in SpaceCard.qml is evidently a static copy, and so its data isn't updated when the model changes. Is there a way to fix this?
First, try adding a MEMBER
Q_PROPERTYto your gadget, it might work.struct Space { Q_GADGET QML_VALUE_TYPE(space) // so "space" can be used in QML. Q_PROPERTY(QString name MEMBER m_name) public: QString m_name; Q_INVOKABLE QString name() { return m_name; } // ... property string titleText: mySpace.nameIf not, then you'll have to change Space to Q_OBJECT which does support signals & slots so it will also dynamically update for sure.
But have you used the property (
namewithout()) instead of invokable method?property string titleText: mySpace.nameAlso, do you emit
dataChanged()in your model when a change occurs? -
But have you used the property (
namewithout()) instead of invokable method?property string titleText: mySpace.nameAlso, do you emit
dataChanged()in your model when a change occurs?@sierdzio said in exposing entire C++ object to QML:
But have you used the property (
namewithout()) instead of invokable method?property string titleText: mySpace.nameYes:
Item { // needed for the DropShadow. id: spaceCard property space spaceObject property string spaceName: spaceObject.nameAlso, do you emit
dataChanged()in your model when a change occurs?Yes. When I receive a change, my slot loops through the list of spaces, and updates the appropriate list items:
for (int spaceIndex = 0; spaceIndex < m_list->size(); spaceIndex++) { space = m_list->at(spaceIndex); if (space.containsEquipment(equipmentUuid)) { // update the temperature-related fields. QModelIndex qmi = index(spaceIndex, 0, QModelIndex()); space.m_temperature = temperature; space.m_temperatureTimestamp = temperatureTimestamp; space.m_temperatureUpdated = true; changedRoles << TemperatureRole << TemperatureTimestampRole << TimestampUpdatedRole; m_list->replace(spaceIndex, space); emit dataChanged(qmi, qmi, changedRoles); } }I've verified this behavior with telltales.
Rather strange, isn't it...
-
@sierdzio said in exposing entire C++ object to QML:
But have you used the property (
namewithout()) instead of invokable method?property string titleText: mySpace.nameYes:
Item { // needed for the DropShadow. id: spaceCard property space spaceObject property string spaceName: spaceObject.nameAlso, do you emit
dataChanged()in your model when a change occurs?Yes. When I receive a change, my slot loops through the list of spaces, and updates the appropriate list items:
for (int spaceIndex = 0; spaceIndex < m_list->size(); spaceIndex++) { space = m_list->at(spaceIndex); if (space.containsEquipment(equipmentUuid)) { // update the temperature-related fields. QModelIndex qmi = index(spaceIndex, 0, QModelIndex()); space.m_temperature = temperature; space.m_temperatureTimestamp = temperatureTimestamp; space.m_temperatureUpdated = true; changedRoles << TemperatureRole << TemperatureTimestampRole << TimestampUpdatedRole; m_list->replace(spaceIndex, space); emit dataChanged(qmi, qmi, changedRoles); } }I've verified this behavior with telltales.
Rather strange, isn't it...
-
@mzimmers What role are you using for returning your complete object? What roles do you emit dataChanged on?
@GrecKo I've created a separate role for the complete object:
enum SpaceRoleNames { UuidRole = Qt::UserRole, ... CompleteObjectRole, NbrRoles };and my data() function does this:
QVariant SpaceModel::data(const QModelIndex &index, int role) const { QVariant qv = QVariant(); do { if (!index.isValid() || m_list == nullptr) continue; Space item = m_list->at(index.row()); switch (role) { case UuidRole: qv = item.m_uuid; break; ... case CompleteObjectRole: qv = QVariant::fromValue(item); break;When any of the temperature related stuff changes, I perform the code in my last post.
-
If you want to purely show data inside SpaceCard.qml, you can bind it through QML. All the mentioned things below should work if the data() function has been implemented properly. (Note: sometimes you need to call dataChanged(index,index) inside the setData() function for it to chage it on QML side.
ListView { id: spaceCards model: spaceProxyModel delegate: SpaceCard { mySpace: completeObject // SpaceCard.qml Item { id: spaceCard property space mySpace: spaceCards.currentItem.mySpace property string titleText: mySpace.name }Also, make sure that inside your list view the currentIndex changes accordingly.
-
If you want to purely show data inside SpaceCard.qml, you can bind it through QML. All the mentioned things below should work if the data() function has been implemented properly. (Note: sometimes you need to call dataChanged(index,index) inside the setData() function for it to chage it on QML side.
ListView { id: spaceCards model: spaceProxyModel delegate: SpaceCard { mySpace: completeObject // SpaceCard.qml Item { id: spaceCard property space mySpace: spaceCards.currentItem.mySpace property string titleText: mySpace.name }Also, make sure that inside your list view the currentIndex changes accordingly.
@Marko-Stanke thanks for the suggestion. I still can't get it to work. Here's the QML:
// SpacesScreen.qml ListView { id: spaceCards model: spaceProxyModel delegate: SpaceCard { spaceObject: completeObject // SpaceCard.qml Item { // needed for the DropShadow. id: spaceCard property space thisSpaceObject: spaceCards.currentItem().spaceObject property real temperatureReading: thisSpaceObject.temperature()And the struct:
// space.h struct Space { Q_GADGET QML_VALUE_TYPE(space) Q_PROPERTY(float temperature MEMBER m_temperature) public: float m_temperature; Q_INVOKABLE float temperature () { return m_temperature; }Why would I need to call dataChanged() inside data()? That's a read-only function, right?
I'm also not sure what you meant about the currentIndex. The ListView should handle that for me, shouldn't it?
Thanks...
-
@mzimmers What role are you using for returning your complete object? What roles do you emit dataChanged on?
@GrecKo said in exposing entire C++ object to QML:
@mzimmers What role are you using for returning your complete object? What roles do you emit dataChanged on?
Sorry if this wasn't clear but that was meant to push you to find the correct solution.
You don't emit
dataChangedfor theCompleteObjectRole, so if you use this role and change the data, the view and delegates won't be aware of any change.Don't do what Marko suggested with
property space mySpace: spaceCards.currentItem().mySpace property string titleText: mySpace.name()or emitting
dataChangedin thedatafunction.You also don't need the Q_INVOKABLE macro on your getters, they are are already exposed with Q_PROPERTY. You don't even need the getters if you are using MEMBER in the Q_PROPERTY (which makes sense for a Q_GADGET).
-
@Marko-Stanke thanks for the suggestion. I still can't get it to work. Here's the QML:
// SpacesScreen.qml ListView { id: spaceCards model: spaceProxyModel delegate: SpaceCard { spaceObject: completeObject // SpaceCard.qml Item { // needed for the DropShadow. id: spaceCard property space thisSpaceObject: spaceCards.currentItem().spaceObject property real temperatureReading: thisSpaceObject.temperature()And the struct:
// space.h struct Space { Q_GADGET QML_VALUE_TYPE(space) Q_PROPERTY(float temperature MEMBER m_temperature) public: float m_temperature; Q_INVOKABLE float temperature () { return m_temperature; }Why would I need to call dataChanged() inside data()? That's a read-only function, right?
I'm also not sure what you meant about the currentIndex. The ListView should handle that for me, shouldn't it?
Thanks...
@mzimmers Sorry for the confusion. I edited my response.
The dataChanged signal is needed (sometimes) in the setData() method, not data().
Also, the currentItem,name are properties. No brackets () needed.ListView has a build in mechanism for handling currentIndex with keyboard navigation, but only if the ListView is in focus,
if you want to change the currentIndex by click, you need to implement a click() signal handler. (By using MouseArea, TapHandler), and changing the currentIndex of your list based on the index of the clicked item.As for the Q_INVOKABLE goes that @GrecKo mentioned, he's right. No Q_INVOKABLE needed if you're using MEMBER in Q_PROPERTY.
Note: the solution I posted is only viable if you want only to read data.
-
@GrecKo said in exposing entire C++ object to QML:
@mzimmers What role are you using for returning your complete object? What roles do you emit dataChanged on?
Sorry if this wasn't clear but that was meant to push you to find the correct solution.
You don't emit
dataChangedfor theCompleteObjectRole, so if you use this role and change the data, the view and delegates won't be aware of any change.Don't do what Marko suggested with
property space mySpace: spaceCards.currentItem().mySpace property string titleText: mySpace.name()or emitting
dataChangedin thedatafunction.You also don't need the Q_INVOKABLE macro on your getters, they are are already exposed with Q_PROPERTY. You don't even need the getters if you are using MEMBER in the Q_PROPERTY (which makes sense for a Q_GADGET).
@GrecKo said in exposing entire C++ object to QML:
You don't emit dataChanged for the CompleteObjectRole, so if you use this role and change the data, the view and delegates won't be aware of any change.
Aaaah...that makes sense, thank you. I'm not sure how to fix this, though -- can my idea of a complete object role co-exist with the individual roles? In other words, would it be OK to simply emit a dataChanged() on the complete object in my code above (instead of the three roles I'm currently using)?
-
@GrecKo said in exposing entire C++ object to QML:
You don't emit dataChanged for the CompleteObjectRole, so if you use this role and change the data, the view and delegates won't be aware of any change.
Aaaah...that makes sense, thank you. I'm not sure how to fix this, though -- can my idea of a complete object role co-exist with the individual roles? In other words, would it be OK to simply emit a dataChanged() on the complete object in my code above (instead of the three roles I'm currently using)?
-
@mzimmers emit data changed for everything that changes. The individual roles if you want to keep them and the role for your complete object.
@GrecKo I think I'm very close to a solution. I modified my updater routine as follows:
changedRoles << SpaceRoles::TemperatureRole << SpaceRoles::TemperatureTimestampRole << SpaceRoles::TimestampUpdatedRole << SpaceRoles::CompleteObjectRole; // <== THIS IS NEW m_list->replace(spaceIndex, space); emit dataChanged(qmi, qmi, changedRoles);And it seems to work.
So, for my education, do I correctly understand that the signal must "agree with" the QML property being used? And, since I'm referencing an entire object in SpaceCard, that's what role the dataChanged() signal must include?
-
B Bob64 referenced this topic on
-
@mzimmers said in exposing entire C++ object to QML:
@kshegunov @sierdzio I'm still missing something here -- I have access to the Space object from the model used by the QML code that invokes SpaceDetail.qml. I want to use that object (not create a new one) to pass to SpaceDetail. How do I do this?
You can return
Spaceobject in yourdata()method of your model. Something like (pseudocode):class SpaceModel: public QAbstractListModel { // ... QVartiant data(const QModelIndex &index, const int role) const { if (index is valid and role is correct etc.) { m_list.at(index.row()); } } }I'm also getting a startup error:
qt.qml.typeregistration: Invalid QML element name "Space"; value type names should begin with a lowercase letterwhich I assume comes from this line:
qmlRegisterType<Space>("Space", 1, 0, "Space");This is some recent change in Qt 6. Uppercase gadgets work fine with Qt 5 & 6 but Qt 6 warns against them. If you don't need Qt 5 support, just make it lower-case and it will be fine.
Ah, one more thing: since Q_GADGETs cannot be constructed in QML, you should use
qmlRegisterUncreatableTypeinstead.@sierdzio said in exposing entire C++ object to QML:
Ah, one more thing: since Q_GADGETs cannot be constructed in QML, you should use qmlRegisterUncreatableType instead.
You shouldn't. Manual registration is deprecated, you should use the proper macros to register it as value type (as shown above).
-
@sierdzio said in exposing entire C++ object to QML:
Ah, one more thing: since Q_GADGETs cannot be constructed in QML, you should use qmlRegisterUncreatableType instead.
You shouldn't. Manual registration is deprecated, you should use the proper macros to register it as value type (as shown above).
@kshegunov said in exposing entire C++ object to QML:
Manual registration is deprecated
It is? I found no mention of this in the documentation.
-
@kshegunov said in exposing entire C++ object to QML:
Manual registration is deprecated
It is? I found no mention of this in the documentation.
@mzimmers said in exposing entire C++ object to QML:
It is? I found no mention of this in the documentation.
As far as I remember the idea is to move it to the QML private code at some point (if it's not done already). Use the
qqmlregistrationheader and the appropriate macros to annotate your types instead.Notice the functions are not mentioned at all here: https://doc.qt.io/qt-6/qtqml-cppintegration-definetypes.html
-
M mzimmers has marked this topic as solved on
-
@sierdzio said in exposing entire C++ object to QML:
Ah, one more thing: since Q_GADGETs cannot be constructed in QML, you should use qmlRegisterUncreatableType instead.
You shouldn't. Manual registration is deprecated, you should use the proper macros to register it as value type (as shown above).
@kshegunov said in exposing entire C++ object to QML:
@sierdzio said in exposing entire C++ object to QML:
Ah, one more thing: since Q_GADGETs cannot be constructed in QML, you should use qmlRegisterUncreatableType instead.
You shouldn't. Manual registration is deprecated, you should use the proper macros to register it as value type (as shown above).
Ah, sorry, I still live in Qt 5 world :D
-
G GrecKo referenced this topic on