KMK
KMK

Reputation: 1509

How to expose list of custom objects with Q_PROPERTY

I have a very simple class with 2 properties; key and value:

KeyValue.h:

class KeyValue : public QObject
{
  Q_OBJECT
  Q_PROPERTY(QString key READ getKey WRITE setKey NOTIFY keyChanged)
  Q_PROPERTY(QString value READ getValue WRITE setValue NOTIFY valueChanged)

 public:
  KeyValue(const QString& key, const QString& value, QObject* parent = 0);  

 signals:
  void keyChanged();
  void valueChanged();

 private:
  QString _key;
  QString _value;

  QString getKey() const;
  QString getValue() const;

  void setKey(const QString& key);  
  void setValue(const QString& value);
};
Q_DECLARE_METATYPE(KeyValue)

In another class I would like a property containing a list of KeyValue objects, so I can use this list as a model in QML.

Controller.h

class Controller : public QObject
{
  Q_OBJECT
  Q_PROPERTY(QList<KeyValue*> items READ getItems NOTIFY itemsChanged)

 public:
  explicit Controller(QObject* parent = 0);

 signals:
  void itemsChanged(); 

 private:
  QList<KeyValue*> getItems() const;
};

I want to be able to use this in QML the following way:

import QtQuick 2.7
import customqml 1.0

Item{
  Controller{
    id: controller
  }
  Repeater{
    model: controller.items
    Text{
      text: modelData.key + ": " + modelData.value
    }
  }
}

Both classes are registered in my main.cpp file:

qmlRegisterType<KeyValue>("customqml", 1, 0, "KeyValue");
qmlRegisterType<Controller>("customqml", 1, 0, "Controller");

The above code does not work, bacause I apparently can't expose a QList to QML directly. I have tried using QAbstractItemModel and QQmlListProperty, but I was unable to get it to work. Can anyone point me in the right direction?

My primary issues are the type of the items property in the Controller class and the return value of the getItems method.

I'm using Qt 5.9 if that makes any difference.

Upvotes: 6

Views: 5175

Answers (2)

eyllanesc
eyllanesc

Reputation: 243955

Note:

  • The getters and setters are generally public except for exceptions so move it to the public part

  • The classes that inherit from QObject do not need QMetaType because when you want to transfer data of that class the pointers are used.

Not all data types are supported by QML through Q_PROPERTY, so a possible solution is to export through known classes such as

  • QList<QObject *>:

class Controller : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QList<QObject *> items READ getItems NOTIFY itemsChanged)
public:
    explicit Controller(QObject *parent = nullptr);
    QList<QObject *> getItems() const;
signals:
    void itemsChanged();
private:
    QList<KeyValue *>key_values_list;
};

...
QList<QObject *> Controller::getItems() const
{
    QObjectList l;
    for(auto e: key_values_list)
        l << e;
    return l;
}
  • QVariantList:

class Controller : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QVariantList items READ getItems NOTIFY itemsChanged)
public:
    explicit Controller(QObject *parent = nullptr);
    QVariantList getItems() const;
signals:
    void itemsChanged();
private:
    QList<KeyValue *>key_values_list;
};

...
QVariantList Controller::getItems() const
{
    QVariantList l;
    for(auto e: key_values_list)
        l.append(QVariant::fromValue(e));
    return l;
}

Other options is to implement a model, the following example shows only a read-only model:

keyvaluemodel.h

#ifndef KEYVALUEMODEL_H
#define KEYVALUEMODEL_H

#include "keyvalue.h"

#include <QAbstractListModel>

class KeyValueModel : public QAbstractListModel
{
    Q_OBJECT

public:
    explicit KeyValueModel(QObject *parent = nullptr)
        : QAbstractListModel(parent)
    {
        key_values_list = {new KeyValue{"k", "v"}, new KeyValue{"k2", "v2"}};
    }
    int rowCount(const QModelIndex &parent = QModelIndex()) const override
    {
        if (parent.isValid())
            return 0;
        return key_values_list.length();
    }
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
    {
        if (!index.isValid())
            return QVariant();
        if(index.row() >= 0 && index.row() < rowCount())
            return QVariant::fromValue(key_values_list[index.row()]);
        return QVariant();
    }
private:
    QList<KeyValue* >key_values_list;
};

#endif // KEYVALUEMODEL_H

class Controller : public QObject
{
    Q_OBJECT
    Q_PROPERTY(KeyValueModel* items READ getItems NOTIFY itemsChanged)
public:
    explicit Controller(QObject *parent = nullptr);
    KeyValueModel* getItems() const;
signals:
    void itemsChanged();
private:
    KeyValueModel *model;
};

...
Text{
    text: display.key + ": " + display.value
}
...

And in a similar way you can implement a QQmlListProperty, in the docs there are many examples.

Upvotes: 9

Jan Win
Jan Win

Reputation: 147

If you want a sophisticated model with adding/deleting objects and altering data you should look into subclassing QAbstractListModel.

As a simple but less flexible way you can use a QVariantList and make your Controller class a value type. You need:

  • The macro Q_DECLARE_METATYPE(Controller) at the end of the Controller header file.
  • A copy constructor for Controller
  • A default constructor for Controller

Q_PROPERTY Type and return value are then QVariantList.

Upvotes: 0

Related Questions