6

I have a QList<QObject*> C++ model containing custom objects and exposed to QML.

My custom object looks like this:

class CustomObject : public QObject
{
  Q_OBJECT

    Q_PROPERTY(QString name READ getName NOTIFY nameChanged)
    Q_PROPERTY(QQmlListProperty<CustomObject READ getChildren NOTIFY childrenChanged)

    [...]

}

My model is exposed to QML like this:

qmlEngine->rootContext()->setContextProperty("internalModel", QVariant::fromValue(m_internalModel));

So far so good. I can use a view, display all my elements and recursively also display their children.

The problem is that QList has no way to notify QML that the model changed. As noted in the documentation about QObjectList-based model:

Note: There is no way for the view to know that the contents of a QList has changed. If the QList changes, it is necessary to reset the model by calling QQmlContext::setContextProperty() again.

So everytime I add or remove an item, I call:

qmlEngine->rootContext()->setContextProperty("internalModel", QVariant::fromValue(m_internalModel));

And this is extremely slow.

If I understood correctly, I need to use QAbstractItemModel instead.

So, is it possible to migrate from QList<QObject*> to QAbstractItemModel without changing the QML part? In particular, should I migrate all Q_PROPERTY from CustomObject to roles or can "reuse them"?

Korchkidu
  • 4,908
  • 8
  • 49
  • 69
  • I haven't worked with QML. But migrating the Qlist into an Model is easy. there is also `QAbstractListModel` which you should use. You have to change some things though probably because the list should be hidden from the QML and only accessed by the model. But I guess Qt has some trivial system to set the model of a QML list view. Seems like a standard usecase. Also: any specific reason why you use a list and not a vector? – Hayt Sep 08 '16 at 09:02
  • @Hayt: you have to use a QList if you want to use it as a model in QML. – Korchkidu Sep 08 '16 at 09:11
  • The thing is. You really want to use a "Model" class as the model and have the containers hidden inside the model (then you can use whatever you want). You have probalby already seen it but here is an example with a model class: http://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html further below in the documentation. – Hayt Sep 08 '16 at 09:16

1 Answers1

11

Yes this is possible, you only need to change QML slightly

C++ model class

#pragma once
#include <QAbstractListModel>
#include <QVector>

class Model : public QAbstractListModel {
  Q_OBJECT
public:
  int rowCount(const QModelIndex&) const override;
  QVariant data(const QModelIndex& index, int role) const override;

public slots:
  void insert(QObject* item);
  void remove(QObject* item);

protected:
  QHash<int, QByteArray> roleNames() const override;

private:
  QVector<QObject*> mItems;
};

It will work with any types that inherit from QObject. You can insert and remove items by using insert() and remove() which works both from C++ and QML. The implementation is pretty simple

#include "model.h"

int Model::rowCount(const QModelIndex&) const {
  return mItems.size();
}

QVariant Model::data(const QModelIndex& index, int /*role*/) const {
  QObject* item = mItems.at(index.row());
  return QVariant::fromValue(item);
}

void Model::insert(QObject* item) {
  beginInsertRows(QModelIndex(), 0, 0);
  mItems.push_front(item);
  endInsertRows();
}

void Model::remove(QObject* item) {
  for (int i = 0; i < mItems.size(); ++i) {
    if (mItems.at(i) == item) {
      beginRemoveRows(QModelIndex(), i, i);
      mItems.remove(i);
      endRemoveRows();
      break;
    }
  }
}

QHash<int, QByteArray> Model::roleNames() const {
  QHash<int, QByteArray> roles;
  roles[Qt::UserRole + 1] = "item";
  return roles;
}

Note the use of roleNames() and "item" role. We will use it in QML. You do not need to migrate your CustomObject properties to roles, just use the single role and return QObject* pointer from Model::data().

Model registration

Model internalModel;
qmlEngine->rootContext()->setContextProperty("internalModel", &internalModel);

Finally

ListView {
  model: internalModel
  delegate: Text {
    text: model.item.name
  }
}

The only thing you need to do in your QML is replace name and getChildren with model.item.name and model.item.getChildren. Sounds simple?

psyched
  • 1,859
  • 1
  • 21
  • 28
  • 1
    I didn't even think about something that simple. This is great, thanks! – Korchkidu Sep 08 '16 at 14:56
  • Do you have any idea why such a model is not included in Qt directly? It seems so useful. Is there a problem with it e.g. regarding memory management? http://stackoverflow.com/q/43809751 – Georg Schölly May 05 '17 at 16:30
  • thx, but how to change my specific value of model like [list model](https://doc.qt.io/qt-5/qml-qtqml-models-listmodel.html#set-method) – S At Dec 21 '21 at 01:44