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:
modelData.propertyName
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 element
class 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 QtQuick
Item {
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 QtQuick
Window {
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
with required name
: QML model items are properly displayedmockupList
with name: modelData.name
: all items show the default name in MyListItem.qml
cppBackend.model
with required name
: No item was created and throws the warning Required property name was not initialized
cppBackend.model
with name: modelData.name
: C++ model items are properly displayedThe answer of @iam_peter in my linked question solved this question too.
TL;DR:
There was 3 elements missing in my example code:
QML_ELEMENT
in Item
class.import TestBackend
(the URI of the project defined in the CMake file) in Main.qml
Main.qml
to store the backend model outside the list view like that:Window {
id: root
// ...
property list<Item> itemList: cppBackend.model
ListView {
// ...
model: root.itemList
// ...
}
}
With these minimal changes to my example code, both the QML model and the C++ model can be accessed with the delegate's required name
line in Main.qml
.
The answer of @smr on my linked question solves also this question and requires even less code changes.
The only change to the example code is to replace the Q_PROPERTY
of the model by QQmlListProperty<Item>
like that:
class Backend : public QObject
{
Q_OBJECT
Q_PROPERTY(QQmlListProperty<Item> model READ model NOTIFY onModelChanged)
public:
// ...
QQmlListProperty<Item> model() { return {this, &m_model}; }
//...
}