Accessing C++ QLists from QML
Asked Answered
C

9

32

If I've got a list of things in C++, how do I expose that to QML (in Qt5 / QtQuick 2)? It seems like QML can only understand QObject-derived classes, which is an issue because QObjects can't be put in a QList or copied. How do I do this:

struct Thing
{
    int size;
    QString name;
};

class ThingManager : public QObject
{
    Q_OBJECT

    // These macros support QtQuick, in case we one day want to use it to make a slick
    // interface (when QML desktop components are released).
    Q_PROPERTY(QList<Thing> things READ things NOTIFY thingssChanged)

public:
    // ...
    QList<Thing> things() const;

    // ...

};

So that I can do something like this in QML:?

var a = thingManager.things[0].name;
Chickabiddy answered 11/1, 2013 at 21:52 Comment(2)
A shot in the dark, but perhaps a Q_DECLARE_METATYPE() and/or qRegisterMetaType() will help?Sow
As a note technically QMl works with anything that derives from QDeclarativeObject.Hardpressed
C
12

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];
}
Chickabiddy answered 11/12, 2014 at 8:54 Comment(1)
It really is the best way, even if it's not that easy to set up - at least for the first time.Cantabile
C
27

Alternatively, You can use QVariantList (QList<QVariant>), it will automatically change to JavaScript array when passed to QML, and it is read and write-able from C++ and QML

Callis answered 14/1, 2013 at 16:9 Comment(4)
But QVariant can't hold compound values can it?Chickabiddy
QVariant can hold almost "everything", including another QVariantList or QVariantMap.Callis
Ah interesting, I didn't know that!Chickabiddy
In general QList of many of templated types is supported: QList C++ sequence types are supported transparently in QML as JavaScript Array types qt-project.org/doc/qt-5/qtqml-cppintegration-data.htmlHasseman
R
26

I came across this question while trying to fix a similar problem, where I wanted to use C++ code as a model source in QML. The answer given by TheBootroo pointed me in the right direction, but did not work fully for me. I do not have enough reputation to answer him directly (but I did upvote his answer).

I am using Qt 5.0.0 I found this link very helpful

The definition of ThingManager should be changed as follows

class ThingManager : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QList<QObject*> things READ getThings NOTIFY thingsChanged)

public:
    QList<QObject*> getThings () const { return m_things; }

signals:
    void thingsChanged ();

private:
    QList<QObject*> m_things;
};

Note that I changed the return type of getThings to a QList<QObject*>. Without this change, Qt warns that it is "Unable to handle unregistered datatype 'QList<Thing*>'".

In the QML code, the properties of Thing can be accessed through the model as model.modelData.size and model.modelData.name.

Rifleman answered 5/4, 2013 at 9:49 Comment(3)
qt5 version of docs: qt-project.org/doc/qt-5.0/qtquick/…Phosphorescence
In general QList of many of templated types is supported: QList C++ sequence types are supported transparently in QML as JavaScript Array types qt-project.org/doc/qt-5/qtqml-cppintegration-data.htmlHasseman
you can register type with qmlRegisterType<Thing>("com.yourapp",1,0,"Thing") function and you don't need to use QObject. Your "Thing" must be declared as a Class.Palladin
C
12

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];
}
Chickabiddy answered 11/12, 2014 at 8:54 Comment(1)
It really is the best way, even if it's not that easy to set up - at least for the first time.Cantabile
C
5

Ah I found the answer (I think, not tested): QQmlListProperty

There's a few uses in the examples, e.g. at qtdeclarative/examples/quick/tutorials/gettingStartedQml/filedialog/directory.*:

Unfortunately you can only have read-only lists at the moment.

Chickabiddy answered 11/1, 2013 at 23:39 Comment(1)
In general QList of many of templated types is supported: QList C++ sequence types are supported transparently in QML as JavaScript Array types qt-project.org/doc/qt-5/qtqml-cppintegration-data.html This includes updates to the QList, not just read-onlyHasseman
B
3

you are quite wrong about QObject, they can be given to a QList, simply in the form of a pointer, as following works perfectly :

class Thing : public QObject
{
    Q_OBJECT

    Q_PROPERTY (int     size READ getSize CONSTANT)
    Q_PROPERTY (QString name READ getName CONSTANT)

public:
    Thing(QObject * parent = NULL) : QObject(parent) {}

    int     getSize () const { return m_size; }
    QString getName () const { return m_name; }

private:
    int     m_size;
    QString m_name;
};

class ThingManager : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QList<Thing*> things READ getThings NOTIFY thingsChanged)

public:
    QList<Thing*> getThings () const { return m_things; }

signals:
    void thingsChanged ();

private:
    QList<Things*> m_things;
};
Bonacci answered 29/1, 2013 at 8:48 Comment(3)
better use "QList<QObject*> getThings()".Phyfe
I cant get it working error: Unable to handle unregistered datatype 'QList<Thing*>' for property... It works fine with QObject*Gereron
But can it be exposed for writing ?Seward
T
2

best way use QQmlListProperty .see this simple sample , i hope help you.
Object and List Property Types Example

Tolbert answered 21/1, 2015 at 13:38 Comment(0)
N
1

The answer given by eatyourgreens is correct. By implementing your class in that way you can access as many descendants as you want. One more helpful tip I found useful is to create an alias for our model inside the qml delegate element.

ListView {
   anchors.fill: parent
   model: thing_manager.things
   delegate: ItemDelagate {}
   clip: true
   spacing: 10
}

And then in the ItemDelegate.qml you can create alias for the model to not use all the time the model.modelData

Item{
    width: 600
    height: 200
    property var thing: model.modelData
    Rectangle {
        anchors.fill: parent
        color: "red"
        Text {
            text: thing.name // or any other field
        }
    }
}
Nobby answered 10/12, 2014 at 12:29 Comment(0)
D
0

One highly indirect way of achieving it is this:

i.) Make a model in qml

ListModel 
{
     id: thingModel

     ListElement 
     {
         size: 10
         name: "Apple"
     }     
}

ii.) Then provide a couple javascript functions to modify this list eg.

function jAppendThing( newSize, newName )
{
    thingModel.append({"size": nameSize, "name": newName })
}

function jClearThing()
{
    thingModel.clear()
}

similarly jDeleteThing etc..

iii.) You can read about how to call qml functions from c++

iv.) Run a loop on your C++ list and call the append function of qml to add all that data into qml list as well.

v.) On any update in C++ side list, modify the qml data as well using the above function to keep it updated.

Deci answered 11/1, 2013 at 23:14 Comment(0)
P
0

Exist a good, but doesn't mentioned solution:

class ThingManager : public QObject
{
Q_OBJECT

// These macros support QtQuick, in case we one day want to use it to make a slick
// interface (when QML desktop components are released).
Q_PROPERTY(QList<Thing> things MEMBER m_things NOTIFY thingssChanged)

// ...
private:
// ...
QList<Thing> m_things;
// ...

};

Read and write are applicable. No expensive function call and data copy. Just direct access to the class members in QML:

var a = thingManager.things[0].name;

For more info, see the doc: https://doc-snapshots.qt.io/qt5-dev/properties.html

Prakash answered 23/1, 2018 at 10:46 Comment(2)
It's not that simple. Use above method, in QML, I cannot access methods like count() and since the QList<Thing> is not actually Javascript Array, the length property cannot be used too.Mushro
If data type conversion between QML and C++ is offically supported (https://doc.qt.io/qt-5/qtqml-cppintegration-data.html) with a data type, then for me it worked in that way: var a = thingManager.things; x = a.length where things is e.g. QList<qreal>.Prakash

© 2022 - 2024 — McMap. All rights reserved.