I'm asking this question after getting some insights on my first question on displaying C++ backend model in QML.
I am new to Qt6 and QML, and I think I understood how to link models to delegates in QML, but maybe I missed something...
I would like to have a QML ListView
delegate that works for both a QML list model and a C++ list model.
This requirement is needed to be able to have the designers to work in Qt Design Studio with a QML model mockup, to have a C++ mockup for unit tests, and to have the real C++ model loaded from the backend.
However, QML and C++ models seem to be accessed differently in the QML ListView
delegate:
- C++ model item properties are accessed through
modelData.propertyName
- QML model item properties are accessed through
required propertyName
(for an existing property in the delegate)
My question: is there an unified way to access properties for both C++ and QML models?
Here is a minimal working example to demonstrate my issue (see Main.qml
for the difference between QML and C++ access):
backend.h
#ifndef BACKEND_H#define BACKEND_H#include <QObject>// Contains the data I want to display for each elementclass Item : public QObject{ Q_OBJECT Q_PROPERTY(QString name MEMBER m_name NOTIFY onNameChanged)public: Item(QString name, QObject *parent = nullptr) : QObject{parent}, m_name(name) {}signals: void onNameChanged();private: QString m_name {"NULL"};};// This class contains the model to display in the ListView.// The data will be loaded before loading the QML file.class Backend : public QObject{ Q_OBJECT Q_PROPERTY(QList<Item*> model MEMBER m_model NOTIFY onModelChanged)public: explicit Backend(QObject *parent = nullptr) : QObject{parent} { m_model.append(new Item {"Cpp"}); m_model.append(new Item {"backend"}); m_model.append(new Item {"is"}); m_model.append(new Item {"great!"}); } virtual ~Backend() override { for (Item* item : m_model) delete item; }signals: void onModelChanged();private: QList<Item*> m_model;};#endif // BACKEND_H
main.cpp
#include <QGuiApplication>#include <QQmlApplicationEngine>#include <QQMLContext>#include "backend.h"int main(int argc, char *argv[]){ QGuiApplication app(argc, argv); QQmlApplicationEngine engine; // Exposing the backend to QML with the name "cppBackend" Backend backend; engine.rootContext()->setContextProperty("cppBackend", &backend); const QUrl url(u"qrc:/TestBackend/Main.qml"_qs); QObject::connect(&engine,&QQmlApplicationEngine::objectCreationFailed,&app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec();}
MyListItem.qml
import QtQuickItem { id: myItem property string name: "Default Name" width: label.width height: label.height Text { id: label text: myItem.name font.pointSize: 24 }}
Main.qml
import QtQuickWindow { width: 640 height: 480 visible: true title: qsTr("Hello World") ListModel { id: mockupList ListElement { name: "Hello" } ListElement { name: "World!" } ListElement { name: "How" } ListElement { name: "are" } ListElement { name: "you?" } } ListView { id: listView anchors.fill: parent anchors.margins: 20 spacing: 10 orientation: ListView.Vertical model: mockupList // Works only with 'required name' //model: cppBackend.model // Works only with 'name: modelData.name' delegate: MyListItem { required name // Works only with QML list model //name: modelData.name // Works only with C++ list model } }}
Here are the results I get when commenting in/out the model
and name
lines in the Main.qml
file:
- ✔
mockupList
withrequired name
: QML model items are properly displayed - ✘
mockupList
withname: modelData.name
: all items show the default name inMyListItem.qml
- ✘
cppBackend.model
withrequired name
: No item was created and throws the warningRequired property name was not initialized
- ✔
cppBackend.model
withname: modelData.name
: C++ model items are properly displayed