Accessing C++ QLists from QML
After more experience with QML I've found the best way to have lists of things is with a QAbstractListModel
.
You make your Thing
derive from QObject
so it can be stored in a QVariant
(after registering it). Then you can return the actual Thing
as the model item. You can access it in a Repeater
as model.display.a_property_of_thing
. The list length is available as model.count
.
This has the following pros and cons:
- Fast - it doesn't copy the entire list to access one element.
- You can easily get animations for changes to the list (addition, rearrangement and removal of items).
- It's easy to use from QML.
- To enable the animations to work, whenever you change the list you have to do some slightly faffy bookkeeping (
beginInsertRows()
etc.)
...
class Things : public QObject
{
...
};
Q_DECLARE_METATYPE(Thing*)
class ThingList : public QAbstractListModel
{
Q_OBJECT
public:
explicit ThingList(QObject *parent = 0);
~ThingList();
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
public slots:
// Extra function to get the thing easily from outside Repeaters.
Thing* thing(int idx);
private:
QList<Thing*> mThings;
};
int ThingList::rowCount(const QModelIndex& parent) const
{
return mThings.size();
}
QVariant ThingList::data(const QModelIndex& index, int role) const
{
int i = index.row();
if (i < 0 || i >= mThings.size())
return QVariant(QVariant::Invalid);
return QVariant::fromValue(mThings[i]);
}
Thing* ThingList::thing(int idx)
{
if (idx < 0 || idx >= mThings.size())
return nullptr;
return mThings[idx];
}
How to pass QList from QML to C++/Qt?
You CAN'T use QDeclarativeListProperty (or QQmlListProperty in Qt5) with any other type than QObject derived ones. So int or QString will NEVER work.
If you need to exchange a QStringList or a QList or anything that is an array of one of the basic types supported by QML, the easiest way to do it is to use QVariant on the C++ side, like this :
#include <QObject>
#include <QList>
#include <QVariant>
class KeyboardContainer : public QObject {
Q_OBJECT
Q_PROPERTY(QVariant enableKey READ enableKey
WRITE setEnableKey
NOTIFY enableKeyChanged)
public:
// Your getter method must match the same return type :
QVariant enableKey() const {
return QVariant::fromValue(m_enableKey);
}
public slots:
// Your setter must put back the data from the QVariant to the QList<int>
void setEnableKey (QVariant arg) {
m_enableKey.clear();
foreach (QVariant item, arg.toList()) {
bool ok = false;
int key = item.toInt(&ok);
if (ok) {
m_enableKey.append(key);
}
}
emit enableKeyChanged ();
}
signals:
// you must have a signal named <property>Changed
void enableKeyChanged();
private:
// the private member can be QList<int> for convenience
QList<int> m_enableKey;
};
On the QML side, simply affect a JS array of Number, the QML engine will automatically convert it to QVariant to make it comprehensible to Qt :
KeyboardContainer.enableKeys = [12,48,26,49,10,3];
That's all !
How to access Qlist of structure elements in QML
use QVariantList instead of QVariant for the return type
code like below
.pro file
QT += quick qml
SOURCES += \
ctestforqml.cpp \
main.cpp
resources.files = main.qml
resources.prefix = /$${TARGET}
RESOURCES += resources \
Resources.qrc
CONFIG += qmltypes
QML_IMPORT_NAME = com.demo.cppobject
QML_IMPORT_MAJOR_VERSION = 1
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
ctestforqml.h
ctestforqml.h
#ifndef CTESTFORQML_H
#define CTESTFORQML_H
#include <QObject>
#include <QQmlEngine>
class CTestforQML : public QObject
{
Q_OBJECT
QML_ELEMENT
public:
Q_INVOKABLE QVariantList getData() const;
public:
explicit CTestforQML(QObject *parent = nullptr);
signals:
};
#endif // CTESTFORQML_H
ctestforqml.cpp
#include "ctestforqml.h"
#include <QJsonObject>
QVariantList CTestforQML::getData() const
{
QVariantList list;
QJsonObject json;
for (int i = 0; i < 10; i ++)
{
json.insert("name", "demo" + QString::number(i));
json.insert("value", QString::number(i));
list.append(json);
}
return list;
}
CTestforQML::CTestforQML(QObject *parent)
: QObject{parent}
{
}
main.cpp
import QtQuick
import com.demo.cppobject
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
CTestforQML {
id: testForQml
}
Component.onCompleted: {
let val = testForQml.getData()
for (let i = 0; i < val.length; i ++)
{
console.log(val[i].name, val[i].value);
}
}
}
Access an attribute c++ object from QML
You could do any of the following:
Add a public slot in API class:
API.cpp:
QString API::getUserName()
{
return user.getName();
}
main.qml:
Component.onCompleted: console.log( API.getUserName() )
Make User a Q_PROPERTY in API class:
API.h:
Q_PROPERTY( User* user READ user NOTIFY userChanged)
main.qml:
Component.onCompleted: console.log( API.user.getName() )
Register User with QML:
main.cpp:
qmlRegisterType<User>("MyUser", 1, 0, "MyUser");
main.qml:
Component.onCompleted: console.log( API.getUser().getName() )
QListQListQString passed into QML
QML does not inherently understand QLists, so in general it is not possible to pass in a QList of any type T and have QML able to access the items inside the list.
However, the QML engine does have built in support for a few specific types of QList:
QList<QObject *>
QList<QVariant>
QStringList
- (notQList<QString>
!!!)
Therefore if you can construct your list of lists using any combination of the 3 types above, then you can have a working solution. In your use case I would suggest the following construction:
QList<QVariant(QStringList)>
A final note before we try it... Just because this will work, it does not necessarily mean that it is a good idea. The QList contents are copied to Javascript arrays at runtime, and therefore any minor updates to any of the lists from the C++ will cause the entire list to be reconstructed as a new Javascript array, which could be expensive.
Now, let's try it...
myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QStringList>
#include <QVariant>
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<QVariant> variantList READ variantList NOTIFY variantListChanged)
public:
explicit MyClass(QObject *parent = nullptr) : QObject(parent),
m_variantList({
QStringList({ "apple", "banana", "coconut" }),
QStringList({ "alice", "bob", "charlie" }),
QStringList({ "alpha", "beta", "gamma" })
}) { }
QList<QVariant> variantList() const { return m_variantList; }
signals:
void variantListChanged();
public slots:
private:
QList<QVariant> m_variantList;
};
#endif // MYCLASS_H
main.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
visible: true
width: 640
height: 480
Column {
id: column
// will add the strings here from the handler below
}
Component.onCompleted: {
console.log("variantList length %1".arg(myClass.variantList.length))
for (var i = 0; i < myClass.variantList.length; i++) {
console.log("stringList %1 length %2".arg(i).arg(myClass.variantList[i].length))
for (var j = 0; j < myClass.variantList[i].length; j++) {
// print strings to the console
console.log("variantList i(%1), j(%2) = %3".arg(i).arg(j).arg(myClass.variantList[i][j]))
// add the strings to a visual list so we can see them in the user interface
Qt.createQmlObject('import QtQuick 2.7; Text { text: "i(%1), j(%2) = %3" }'.arg(i).arg(j).arg(myClass.variantList[i][j]), column)
}
}
}
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "myclass.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
MyClass myClass;
engine.rootContext()->setContextProperty("myClass", &myClass);
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
Runtime output
qml: variantList length 3
qml: stringList 0 length 3
qml: variantList i(0), j(0) = apple
qml: variantList i(0), j(1) = banana
qml: variantList i(0), j(2) = coconut
qml: stringList 1 length 3
qml: variantList i(1), j(0) = alice
qml: variantList i(1), j(1) = bob
qml: variantList i(1), j(2) = charlie
qml: stringList 2 length 3
qml: variantList i(2), j(0) = alpha
qml: variantList i(2), j(1) = beta
qml: variantList i(2), j(2) = gamma
... and it works :)
Access QListT from qml
You will need to Q_DECLARE_METATYPE(A *)
to be able to wrap it in a QVariant
for use in QML.
But that's just for referring to and passing it around QML.
If you want to use A
in QML as in C++, it will have to inherit QObject
and implement properties, slots and such.
You can see how to implement the QQmlListProperty
here: http://doc.qt.io/qt-5/qtqml-referenceexamples-properties-example.html
Also, if QObject
is too heavy for you and impractical to have a lot of them, you can always use a single QObject
derived to work as a controller for a non-QObject
but still registered as metatype type.
class A {
public:
int a;
};
Q_DECLARE_METATYPE(A *)
class AProxy : public QObject {
Q_OBJECT
public slots:
int a(QVariant aVar) {
return aVar.value<A *>()->a;
}
void setA(QVariant aVar, int v) {
aVar.value<A *>()->a = v;
}
};
This way you don't have the size overhead and limitations of QObject
for every object in the list, and can instead use a single controller to access the data, albeit at lower performance. In your case you could use B
to act as both container and controller proxy for A
.
Related Topics
C++ Objects: When Should I Use Pointer or Reference
"Roll-Back" or Undo Any Manipulators Applied to a Stream Without Knowing What the Manipulators Were
Why Do Linked Lists Use Pointers Instead of Storing Nodes Inside of Nodes
Generating One Class Member Per Variadic Template Argument
C++ Range/Xrange Equivalent in Stl or Boost
Is It Safe to Read an Integer Variable That's Being Concurrently Modified Without Locking
How to Get the Type of a Lambda Argument
Should C++ Eliminate Header Files
Why Does Int Main() {} Compile
Accessing Elements of a Cv::Mat with At<Float>(I, J). Is It (X,Y) or (Row,Col)
What's the Advantage of Using Std::Allocator Instead of New in C++
Calling Constructor of a Class Member in Constructor
Do I Really Have to Worry About Alignment When Using Placement New Operator