Accessing C++ Qlists from Qml

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:

  1. Fast - it doesn't copy the entire list to access one element.
  2. You can easily get animations for changes to the list (addition, rearrangement and removal of items).
  3. It's easy to use from QML.
  4. 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 - (not QList<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

visual output

... 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



Leave a reply



Submit