Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making QList<QObject*> C++ model dynamic in QML

Tags:

c++

qt

qml

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"?

like image 352
Korchkidu Avatar asked Sep 08 '16 08:09

Korchkidu


1 Answers

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?

like image 55
psyched Avatar answered Oct 13 '22 00:10

psyched