First of all, I'm new to Qt6 and QML, so maybe I am missing something obvious.
I'm trying to link a C++ model to a ListView in QML from a QObject property.
From this doc I should be able to use a List<QObject*> as a static model in a QML view.
However, in that example, the QList<QObject*> is passed directly to a QQuickView.
I would like to access the object list from a property of a QObject I can already access in QML.
But when I try to do that, nothing is shown in the list view, and I don't know what I am doing wrong...
Also, QML reports me a warning (see below my example code).
Here a working minimal example of what I am trying to achieve:
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 I want to display.
// The data will be loaded before loading the QML file.
// It can be switched between a mockup and a real backend depending on the context.
class Backend : public QObject
{
Q_OBJECT
Q_PROPERTY(QString header MEMBER m_header NOTIFY onHeaderChanged)
Q_PROPERTY(QList<QObject*> model MEMBER m_model NOTIFY onModelChanged)
public:
explicit Backend(QObject *parent = nullptr)
: QObject{parent}
{
m_header = "Cpp Backend";
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 (QObject* item : m_model)
delete item;
}
signals:
void onHeaderChanged();
void onModelChanged();
private:
QString m_header;
QList<QObject*> 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();
}
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 // this works as expected
model: cppBackend.model // this doesn't show anything in the listview
delegate: Item {
id: myItem
required property string name
width: label.width
height: label.height
Text {
id: label
text: myItem.name
font.pointSize: 24
}
}
header: Text {
width: parent.width
horizontalAlignment: Text.AlignHCenter
font.pointSize: 48
font.bold: true
text: cppBackend.header // This works as expected
}
}
}
When I use te mockupList instead of the C++ backend, the items are displayed as expected.
However, when using the cppBackend I'm getting this warning:
qrc:/TestBackend/Main.qml:30:13: Required property name was not initialized qrc:/TestBackend/Main.qml: 30
It seems that the property cppBackend.model is accessed, but the items inside do not provide access to their properties as it seems it should to be done in the Qt doc...
I've made you an example that uses
QML_SINGLETONto register the backend in QML and definesDataObject(Item) as aQML_ELEMENTso it will be made visible in QML and the properties are being exposed.I changed from
setContextPropertyto a singleton due to the reasons explained in this article.The important thing to make
DataObjectknown inMain.qmlis to import the URI of the module theDataObjectsources were added to (import Demo77967427).The only weird part is that without "casting" the
Backend.modelto alist<DataObject>it doesn't work.And then continue to use
myModelas the model that gets bound to theListView.main.cpp
dataobject.h
backend.h
Main.qml
Have a look at the complete application here.
That said, the best way to expose models from C++ to QML is to derive from
QAbstractListModel.You shouldn't prefix your notifier with
on, have a look here.