Binding C++ objects in std::vector to QML components
-
@ECEC said in Binding C++ objects in std::vector to QML components:
was thinking of using a Repeater for this, but it looks like that would require a model, which I don't think is appropriate in this case.
A
QList<QObject*>
is a valid model forRepeater
. From your description I would say it is the best solution for you. If you define your Room object properties as Q_PROPERTY they will be visible to QML. -
A
std::vector<Room*>
is also a valid model if Room inherits from QObject and is fine if no rooms gets added or removed. Or as a value type if Room is a Q_GADGET. QML_FOREIGN might be an option if you don't want to modify your Room type.You'll need to use Q_DECLARE_METATYPE to use the std vector in a Q_PROPERTY (or figure out how to use QML_SEQUENTIAL_CONTAINER).
-
@ECEC said in Binding C++ objects in std::vector to QML components:
Thanks for the suggestions, I think QList is probably the way to go.
If I do decide to extend the application such that "Room" objects can be added and removed from the QList, would these changes be reflected on the QML side?No, QList does not send any change signals. But you can manually replace the list on QML side, then it will work. Something like:
// QML code addNewRoom() model = null model = myModel
Or, another way to do it is to emit a change signal for your model property. For example:
// C++ code Q_PROPERTY(QList<Room*> myModel READ myModel NOTIFY myModelChanged) // In your model changing routine: _myModel.append(new Room); emit myModelChanged();
At some point, if you need more model functionality, it will be easier to just port the model to a standard QAbstractListModel subclass or something similar.
-
Use https://github.com/OlivierLDff/ObjectListModel if you need a dynamic model. It has the API of a QList but expose itself as a QAbstractListModel automagically.
-
Sorry to reopen this again!
I've decided to move to a QAbstractListModel subclass as adding/removing Rooms is something that needs to be supported. The Room classes' variables have been registered with Q_PROPERTY, however, they are accessed in the view via the model's roles.So, my question is, if these Q_PROPERTY variables are modified, will the view automatically update, or do I need to be responsible for that?
-
@ECEC said in Binding C++ objects in std::vector to QML components:
Sorry to reopen this again!
I've decided to move to a QAbstractListModel subclass as adding/removing Rooms is something that needs to be supported. The Room classes' variables have been registered with Q_PROPERTY, however, they are accessed in the view via the model's roles.So, my question is, if these Q_PROPERTY variables are modified, will the view automatically update, or do I need to be responsible for that?
If I understand the description, no, the property change signal will not trigger a re-read of model roles via QAbstractItemModel::data(). That requires emitting the QAIM:dataChanged() signal.
-
That seems to go against Qt's philosophy of declaratively binding UI elements to their C++ counterparts, but I'm going to assume that to return the entire Room object as a role isn't a good idea.
So I suppose I'll have to call datachanged() to update the view. Room's data comes externally via a NetworkProcessor class. Would you recommend making changes directly on the Room objects (and then calling datachanged() on the model), or performing all changes via the model? For example, NetworkProcessor could emit a roomUpdated() signal, passing a struct containing a Room's id and relevant data we want to update. And then connect this signal to a slot in the model and do the actual updating of the Room list's objects, etc, there.
-
@ECEC said in Binding C++ objects in std::vector to QML components:
That seems to go against Qt's philosophy of declaratively binding UI elements to their C++ counterparts, but I'm going to assume that to return the entire Room object as a role isn't a good idea.
I have seen that strategy work.
So I suppose I'll have to call datachanged() to update the view. Room's data comes externally via a NetworkProcessor class. Would you recommend making changes directly on the Room objects (and then calling datachanged() on the model), or performing all changes via the model? For example, NetworkProcessor could emit a roomUpdated() signal, passing a struct containing a Room's id and relevant data we want to update. And then connect this signal to a slot in the model and do the actual updating of the Room list's objects, etc, there.
Either way sounds fine. If the existing code base is large, I tend to vote for harmony.
-
Another concern is the maintainability of the model. I understand it's best practice to make changes to the underlying Room objects via the model. e.g. if Room has an updateConfig() method, the model should have an equivalent updateRoomConfig(id) method. However, this doesn't give much freedom to interact with the Room objects outside of the model. Would it be acceptable to expose the model's Room list to other areas of the system and perhaps have them emit a roomUpdated() signal (attached to a corresponding slot in the model that calls dataChanged()) whenever "displayable" changes are made? I'm really only wanting to use the model as a means to display the data, while leaving ownership to other classes.
-
Have you considered using QStandardItemModel? I shy away from it for heavy C++ use, but it's very convenient as a low effort, thin wrapper. I don't bother with the subclassing of QStandardItem that the examples usually demonstrate.
main.cpp:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QStandardItemModel> #include <QStandardItem> #include <QObject> int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QStandardItemModel model; QStandardItem item; QObject obj; obj.setObjectName("myObject"); item.setData(QVariant::fromValue(&obj), Qt::ItemDataRole::DisplayRole); model.appendRow(&item); QQmlApplicationEngine engine; engine.setInitialProperties({ {"myModel", QVariant::fromValue(&model)} }); const QUrl url(u"qrc:/untitled7/Main.qml"_qs); QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
main.qml:
import QtQuick import QtQuick.Window Window { id: root width: 640 height: 480 visible: true property var myModel ListView { anchors.fill: parent model: root.myModel delegate: Text { text: display.objectName } } }
-
I'm not too sure what benefit you are suggesting QStandardItemModel would have over subclassing QAbstractListModel.
I'm happy to use QAbstractListModel, and I think I should bring my previous question about whether to interact with objects via the model or directly, to a new thread.
Thanks for your help!
-
@ECEC said in Binding C++ objects in std::vector to QML components:
I'm not too sure what benefit you are suggesting QStandardItemModel would have over subclassing QAbstractListModel.
The only advantage is less stuff to write and maintain. I count 6 lines of code above, including headers, specific to the standard item model.