QML 创建 TreeView 动态模型的正确方法是啥?

Posted

技术标签:

【中文标题】QML 创建 TreeView 动态模型的正确方法是啥?【英文标题】:QML what is the right way to create a TreeView dynamic model?QML 创建 TreeView 动态模型的正确方法是什么? 【发布时间】:2018-06-15 14:36:48 【问题描述】:

我是 QML 的新手,最近在集成 QML 和 C++ 时遇到了一些麻烦,现在我尝试正确地进行集成。

所以,我正在尝试使用动态模型创建 QML TreeView,并且我看到了创建 TreeView 模型的不同方法。

在文档中,示例与 TableViewColumn:

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

在互联网上的一些研究中,我发现: 用 C++ 创建:

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

Create Model for QML TreeView

那么,我的问题是,为 QML TreeView 创建模型的正确方法是什么? 该模型将是动态的,具有动态数据。

对于动态,我的意思是会有不固定数量的节点,但是信息是一样的,它遵循一个 json 和一个示例图像:

[
  
    "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"  
  
]

【问题讨论】:

您可以解释您的意思 * 该模型将是动态的,具有动态数据。 *,这个表达式很宽泛,可能无法回答,也许如果你建立一些真实的参数,我们可以给你一个实际的例子。 @eyllanesc 好的,我会的 由于TreeView 是依赖于模型的权利,唯一的方法是定义您的自定义模型,当然从QAbstractItemModel 派生。因此,您的模型应该是动态的,即如果我理解正确的话,这应该允许动态更改数据。在这种情况下,您应该查看insertRows/removeRows 等。 一个QAbstractItemModel 子类就可以了。甚至是普通的QStandardItemModel,只要它符合您的需要。两者都支持树模型。 【参考方案1】:

我做了类似的解决方案。有我的模型:

树项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;

模型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);
        
    

如果你需要插入、删除、移动功能,你必须实现这个功能。 带有可拖动行的完整程序,您可以下载here。

【讨论】:

以上是关于QML 创建 TreeView 动态模型的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

将数据添加到 QML TreeView

来自模型的 QML 树视图

QML TreeView 的 C++ 模型

这是 QML 中最小可行的 TreeView 模型吗?

QML TreeView 是不是支持模型发出的信号 layoutChanged?

更改模型后未正确绘制 QML 中继器