1
votes

I´m new on QML, and recently I had some troubles in integration QML and C++, and now i trying to do integration correctly.

So, i´m trying to create QML TreeView, with dynamic model, and i saw diferent ways to do a TreeView Model.

In documentation, the sample is with TableViewColumn:

https://doc-snapshots.qt.io/qt5-5.9/qml-qtquick-controls-treeview.html

In some researches on internet i found: Create with C++:

https://forum.qt.io/topic/56497/request-treeview-c-model-to-qml-example/4

Create Model for QML TreeView

So, my question is, what is the right way to create a model for QML TreeView? That model will be dynamic, with the dynamic data.

With dynamic, I mean that there will be an unfixed number of nodes, however the information will be the same, it follows a json and an example image:

[
  {
    "description": "screen1",
    "source": "qrc/screen1.qml",
    "popups": 
    [
      {
        "description": "screen1popup1",
        "source": "qrc/screen1popup1.qml"
      },
      {
        "description": "screen1popup2",
        "source": "qrc/screen1popup2.qml"
      }
    ]
  },

  {
    "description": "screen2",
    "source": "qrc/screen2.qml",
    "popups": 
    [
      {
        "description": "screen2popup1",
        "source": "qrc/screen2popup1.qml",
        "subs": [
          {
            "description": "screen2popup1sub1",
            "source": "qrc/screen2popup1sub1.qml"
          }  
        ]
      }
    ]
  },

  {
    "description": "screen3",
    "source": "qrc/screen3.qml"  
  }
]

treeviewExample

1
You could explain what you mean by * That model will be dynamic, with the dynamic data. *, that expression is very broad and could be impossible to answer, maybe if you establish some real parameters we could give you a practical example.eyllanesc
@eyllanesc ok, i willJhonny Pinheiro
Since the TreeView is depends on a model the right and the only way is to define your custom model, certainly derived from QAbstractItemModel. Therefore, your model should be dynamic ie this should allow data to be changed on the fly, if I understood correctly. In that case, you should look at insertRows/removeRows etc.folibis
A QAbstractItemModel subclass will do. Or even a regular QStandardItemModel as long as it fits your needs. Both support tree models.dtech

1 Answers

1
votes

I made similar solution. There is my model:

Tree Item
treeitem.h

#ifndef TREEITEM_H
#define TREEITEM_H    
#include <QList>
#include <QJsonArray>
#include <QJsonValue>
#include <QJsonObject>
#include <QJsonDocument>

class TreeItem
{

public:
    explicit TreeItem(const QJsonValue &data, const QString childrenPath, TreeItem *parentItem = 0);
    ~TreeItem() { qDeleteAll(m_childItems); }

    bool insertChild(int i, TreeItem *child);

    TreeItem *child(int row) { return m_childItems.at(row); }
    int childCount() const { return m_childItems.count(); }
    int columnCount() const { return m_itemData.toObject().count(); }
    QVariant data(const QString &roleName) const;
    bool setParentItem(TreeItem *item);
    int row() const;
    TreeItem *parentItem() { return m_parentItem; }
    QJsonValue jsonValue() const;

    bool removeChild(int row, int count);
    bool isTheIdExist(QString id);
    bool isParent(TreeItem *item);

private:
    QVector<TreeItem*> m_childItems;
    QJsonValue m_itemData;
    TreeItem *m_parentItem;
    QString childrenPath;
};

#endif // TREEITEM_H

treeitem.cpp

#include <QDateTime>

#include "treeitem.h"

TreeItem::TreeItem(const QJsonValue &data, const QString childrenPath, TreeItem *parentItem) :
    m_parentItem(parentItem), childrenPath(childrenPath)
{
    QJsonObject jObject = data.toObject();
    jObject.remove(childrenPath);

    m_itemData = QJsonValue(jObject);
}

bool TreeItem::insertChild(int i, TreeItem *child)
{
    if (i >= m_childItems.count())
        m_childItems.append(child);
    else
        m_childItems.insert(i, child);

    return true;
}

QVariant TreeItem::data(const QString &roleName) const
{
    QJsonValue val = m_itemData.toObject().value(roleName);

    if (val.type() == QJsonValue::String) {
        QString strVal = val.toString();
        QDateTime dtVal = QDateTime::fromString(strVal, Qt::ISODate);
        if (dtVal.isValid())
            return dtVal;
    }
    else if (val.type() == QJsonValue::Double) {
        int intVal = val.toInt(0);
        if (intVal != 0)
            return intVal;
    }
    else if (val.type() == QJsonValue::Bool) {
        return val.toBool();
    }

    return val.toVariant();
}

bool TreeItem::setParentItem(TreeItem *item)
{
    if (item == this)
        return false;

    foreach (TreeItem *i, m_childItems) {
        if (item->isParent(i)) {
            return false;
        }
    }

    m_parentItem = item;
    return true;
}

int TreeItem::row() const
{
    if (m_parentItem)
        return m_parentItem->m_childItems.indexOf(const_cast<TreeItem*>(this));

    return 0;
}

QJsonValue TreeItem::jsonValue() const {

    QJsonObject jObj = m_itemData.toObject();
    QJsonArray jArray;

    if (m_childItems.count() > 0) {
        foreach (TreeItem *i, m_childItems)
            jArray.append(i->jsonValue());
        jObj.insert(childrenPath, jArray);
    }

    if (m_itemData.toObject().empty()) {
        return QJsonValue(jArray);
    }
    else {
        return QJsonValue(jObj);
    }
}

bool TreeItem::removeChild(int row, int count)
{
    if (row > -1 && row+count <= m_childItems.count()) {
        for (int i = count; i > 0; i--)
            m_childItems.removeAt(row + i - 1);
        return true;
    }
    return false;
}

bool TreeItem::isTheIdExist(QString id)
{
    if (m_itemData.toObject().value("id").toInt() == id.toInt())
        return true;

    foreach (TreeItem *item, m_childItems) {
        if (item->isTheIdExist(id))
            return true;
    }

    return false;
}

bool TreeItem::isParent(TreeItem *item)
{
    bool result = false;

    if (parentItem() != Q_NULLPTR)
        if (parentItem()->isParent(item)) result = true;

    if (parentItem() == item) result = true;

    return result;
}

Model
treejsonmodel.h

    #ifndef TREEJSONMODEL_H
    #define TREEJSONMODEL_H

    #include <QAbstractItemModel>
    #include <QFile>
    #include <QJSValue>
    #include <QDebug>

    #include "treeitem.h"

    class TreeJsonModel : public QAbstractItemModel
    {
        Q_OBJECT

    public:
        explicit TreeJsonModel(QObject *parent = 0);
        ~TreeJsonModel();

        Q_PROPERTY(bool hasChanges READ hasChanges NOTIFY hasChangesChanged)

        Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
        QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
        QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE;
        Q_INVOKABLE int rowCount(const QModelIndex &parent) const Q_DECL_OVERRIDE;
        int columnCount(const QModelIndex &) const Q_DECL_OVERRIDE { return _columns.count(); }

        bool hasChildren(const QModelIndex &parent) const Q_DECL_OVERRIDE;

        QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;

        QModelIndex index(int row, int column, const QModelIndex &parent) const Q_DECL_OVERRIDE;
        QModelIndex parent(const QModelIndex &child) const Q_DECL_OVERRIDE;

        void registerColumn(const QString &name);
        void setUrl(const QString &url) { _fileUrl = url; }

        bool hasChanges() const { return _hasChanges; }
        Q_INVOKABLE bool submit() Q_DECL_OVERRIDE;
        Q_INVOKABLE void refresh();

    signals:
        void dataReady();
        void hasChangesChanged();

    private:
        QString _fileUrl;
        QString _childrenPath = "parents"; // this is the name of path with children
        QStringList _columns;
        TreeItem *rootItem = Q_NULLPTR;

        bool _hasChanges = false;

        void addNewItem(const QJsonValue &data, TreeItem *parent = nullptr);
        void addNewItem(const QJsonValue &data, int row, TreeItem *parent = nullptr);
    };

    #endif // TREEJSONMODEL_H

*treejsonmodel.cpp*

#include "treejsonmodel.h"

TreeJsonModel::TreeJsonModel(QObject *parent) :
    QAbstractItemModel(parent)
{
}

TreeJsonModel::~TreeJsonModel() {
    submit();
    delete(rootItem);
}

Qt::ItemFlags TreeJsonModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
            return 0;

    return QAbstractItemModel::flags(index);
}

QVariant TreeJsonModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
    return item->data(roleNames().value(role));
}

QVariant TreeJsonModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
        return rootItem->data(roleNames().value(section));

    return QVariant();
}

int TreeJsonModel::rowCount(const QModelIndex &parent) const
{
    TreeItem *parentItem;
    if (parent.column() > 0 || rootItem == Q_NULLPTR)
        return 0;

    if (!parent.isValid())
        parentItem = rootItem;
    else
        parentItem = static_cast<TreeItem*>(parent.internalPointer());

    return parentItem->childCount();
}

bool TreeJsonModel::hasChildren(const QModelIndex &parent) const
{
    return rowCount(parent) > 0;
}

QHash<int, QByteArray> TreeJsonModel::roleNames() const
{
    QHash<int, QByteArray> result = QAbstractItemModel::roleNames();

    for (int i = 0; i < _columns.count(); i++) {
        int id = Qt::UserRole + 1 + i;
        QByteArray byte = _columns.at(i).toUtf8();
        result.insert(id, byte);
    }
    return result;
}

QModelIndex TreeJsonModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    TreeItem *parentItem;

    if (!parent.isValid())
        parentItem = rootItem;
    else
        parentItem = static_cast<TreeItem*>(parent.internalPointer());

    TreeItem *childItem = parentItem->child(row);
    if (childItem)
        return createIndex(row, column, childItem);
    else
        return QModelIndex();
}

QModelIndex TreeJsonModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return QModelIndex();

    TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer());
    TreeItem *parentItem = childItem->parentItem();

    if (parentItem == rootItem)
        return QModelIndex();

    return createIndex(parentItem->row(), 0, parentItem);
}

void TreeJsonModel::registerColumn(const QString &name)
{
    if (!_columns.contains(name))
        _columns.append(name);
}

void TreeJsonModel::refresh()
{
    QFile file(_fileUrl);

    if (!file.open(QIODevice::ReadOnly)) {
        qDebug() << "Can't open file" << _fileUrl;
        return;
    }

    beginResetModel();

    QJsonDocument jDoc = QJsonDocument::fromJson(file.readAll());

    rootItem = new TreeItem(QJsonValue(), _childrenPath);

    foreach (QJsonValue item, jDoc.array()) {
        addNewItem(item, rootItem);
    }

    _hasChanges = false;
    hasChangesChanged();

    emit endResetModel();
    emit dataReady();
    file.close();
}

bool TreeJsonModel::submit()
{
    QFile file(_fileUrl);

    if (!file.open(QIODevice::WriteOnly)) {
        qDebug() << "Can't open file";
        return false;
    }

    QJsonValue jValue = rootItem->jsonValue();

    QJsonDocument jDoc = QJsonDocument(jValue.toArray());

    file.write(jDoc.toJson());
    file.close();

    _hasChanges = false;
    hasChangesChanged();

    emit dataReady();

    return true;
}

void TreeJsonModel::addNewItem(const QJsonValue &data, TreeItem *parent)
{
    addNewItem(data, parent->childCount(), parent);
}

void TreeJsonModel::addNewItem(const QJsonValue &data, int row, TreeItem *parent)
{
    auto *item = new TreeItem(data, _childrenPath, parent);
    parent->insertChild(row, item);

    QJsonValue v = data.toObject().value(_childrenPath);
    if (v != QJsonValue::Undefined && v.isArray()) {
        foreach (QJsonValue val, v.toArray()) {
            addNewItem(val, item);
        }
    }
}

If you need insert, remove, move functions, you must implement this function.
Complite program with draggable rows you can download here.