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

Posted

技术标签:

【中文标题】这是 QML 中最小可行的 TreeView 模型吗?【英文标题】:Is this the Minimum Viable TreeView Model in QML? 【发布时间】:2019-05-22 19:50:30 【问题描述】:

我正在制作一个包含三个项目的折叠列表:“嘿”、“Whats”和“Up?”。我想把它放到树视图中。我知道这个列表将只包含这三个项目。因此,我想知道如何将这些项目“嵌套”在一起。

我知道有一些敏捷系统的实现支持添加和删除父/子对象、查找索引……强大的模型。但是,我实际上只需要在可展开/可折叠的视图中显示这些项目。以下是我阅读过的与 C++ 和 QAbstractItemModels 相关的内容:

QML Treeview QML QAbstractItemModel This Question by My_Cat_Jessica This question by kavaliero 基于: This 'working' example 由 Qt 自己编写(实际上不适用于 TreeView。但适用于 QTreeView!)

这里是用模型实现树视图的最简单可行的代码:

import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4


Window 
    id: mywindow
    visible: true
    width: 640
    height: 480

    TreeView 
        id: treeview
        anchors.fill: parent

        TableViewColumn 
            title: "Phrase"
            role: "phrase"
        
        model: phraseModel
    

    ListModel 
        id: phraseModel
        ListElement  phrase: "Hey"; 
        ListElement  phrase: "What's"; 
        ListElement  phrase: "Up?"; 
    

我希望输出产生这样的嵌套堆栈:

Hey
    What's
        Up?

但我将所有内容都放在一个列中,彼此对齐:

Hey
What's
Up?

我知道我没有分配父母,我不完全确定如何做到这一点 - 但我什至不确定这是否是需要对此代码执行的操作。所以我的问题是:将这三个元素堆叠成可展开/可折叠视图的最后一步是什么?

【问题讨论】:

在这里您可以找到 QML 中包含 TreeModel 和 TreeView 的完整工作示例:github.com/Daguerreo/QMLTreeView 【参考方案1】:

没有可以使用 TreeView 的原生 QML 模型,所以我实现了一个尝试通用的模型:

树元素

// treeelement.h
#ifndef TreeElement_H
#define TreeElement_H

#include <QObject>
#include <QQmlListProperty>

class TreeElement : public QObject

    Q_OBJECT
public:
    Q_PROPERTY(QQmlListProperty<TreeElement> items READ items)
    Q_CLASSINFO("DefaultProperty", "items")
    TreeElement(QObject *parent = Q_NULLPTR);

    Q_INVOKABLE TreeElement *parentItem() const;
    bool insertItem(TreeElement *item, int pos = -1);
    QQmlListProperty<TreeElement> items();

    TreeElement *child(int index) const;
    void clear();

    Q_INVOKABLE int pos() const;
    Q_INVOKABLE int count() const;

private:
    static void appendElement(QQmlListProperty<TreeElement> *property, TreeElement *value);
    static int countElement(QQmlListProperty<TreeElement> *property);
    static void clearElement(QQmlListProperty<TreeElement> *property);
    static TreeElement *atElement(QQmlListProperty<TreeElement> *property, int index);

    QList<TreeElement *> m_childs;
    TreeElement *m_parent;
;
#endif // TreeElement_H

// treeelement.cpp
#include "treeelement.h"

TreeElement::TreeElement(QObject *parent) :
    QObject(parent),
    m_parent(nullptr) 

TreeElement *TreeElement::parentItem() const
    return m_parent;



QQmlListProperty<TreeElement> TreeElement::items()
    return  QQmlListProperty<TreeElement> (this,
                                           this,
                                           &TreeElement::appendElement,
                                           &TreeElement::countElement,
                                           &TreeElement::atElement,
                                           &TreeElement::clearElement);


TreeElement *TreeElement::child(int index) const
    if(index < 0 || index >= m_childs.length())
        return nullptr;
    return m_childs.at(index);


void TreeElement::clear()
    qDeleteAll(m_childs);
    m_childs.clear();


bool TreeElement::insertItem(TreeElement *item, int pos)
    if(pos > m_childs.count())
        return false;
    if(pos < 0)
        pos = m_childs.count();
    item->m_parent = this;
    item->setParent(this);
    m_childs.insert(pos, item);
    return true;


int TreeElement::pos() const
    TreeElement *parent = parentItem();
    if(parent)
        return parent->m_childs.indexOf(const_cast<TreeElement *>(this));
    return 0;


int TreeElement::count() const
    return m_childs.size();


void TreeElement::appendElement(QQmlListProperty<TreeElement> *property, TreeElement *value)
    TreeElement *parent = qobject_cast<TreeElement *>(property->object);
    parent->insertItem(value);


int TreeElement::countElement(QQmlListProperty<TreeElement> *property)
    TreeElement *parent = qobject_cast<TreeElement *>(property->object);
    return parent->count();


void TreeElement::clearElement(QQmlListProperty<TreeElement> *property)
    TreeElement *parent = qobject_cast<TreeElement *>(property->object);
    parent->clear();


TreeElement *TreeElement::atElement(QQmlListProperty<TreeElement> *property, int index)
    TreeElement *parent = qobject_cast<TreeElement *>(property->object);
    if(index < 0 || index >= parent->count())
        return nullptr;
    return parent->child(index);

树模型

// treemodel.h
#ifndef TreeModel_H
#define TreeModel_H

#include <QAbstractItemModel>
#include <QQmlListProperty>

class TreeElement;

class TreeModel : public QAbstractItemModel

    Q_OBJECT
public:
    Q_PROPERTY(QQmlListProperty<TreeElement> items READ items)
    Q_PROPERTY(QVariantList roles READ roles WRITE setRoles NOTIFY rolesChanged)
    Q_CLASSINFO("DefaultProperty", "items")

    TreeModel(QObject *parent = Q_NULLPTR);
    ~TreeModel() override;

    QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
    QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
    Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE;
    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
    QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE;
    int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
    int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
    QQmlListProperty<TreeElement> items();

    QVariantList roles() const;
    void setRoles(const QVariantList &roles);

    Q_INVOKABLE QModelIndex indexFromElement(TreeElement *item);
    Q_INVOKABLE bool insertElement(TreeElement *item, const QModelIndex &parent = QModelIndex(), int pos = -1);

    TreeElement *elementFromIndex(const QModelIndex &index) const;

private:
    TreeElement *m_root;
    QHash<int, QByteArray> m_roles;

signals:
    void rolesChanged();
;

#endif // TreeModel_H

// treemodel.cpp
#include "treemodel.h"
#include "treeelement.h"

TreeModel::TreeModel(QObject *parent) :
    QAbstractItemModel(parent)
    m_root = new TreeElement;

TreeModel::~TreeModel()
    delete m_root;


QHash<int, QByteArray> TreeModel::roleNames() const
    return m_roles;


QVariant TreeModel::data(const QModelIndex &index, int role) const
    if (!index.isValid())
        return QVariant();
    TreeElement *item = static_cast<TreeElement*>(index.internalPointer());
    QByteArray roleName = m_roles[role];
    QVariant name = item->property(roleName.data());
    return name;


Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
    if (!index.isValid())
        return nullptr;
    return QAbstractItemModel::flags(index);


QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
    if (!hasIndex(row, column, parent))
        return QModelIndex();
    TreeElement *parentItem = elementFromIndex(parent);
    TreeElement *childItem = parentItem->child(row);
    if (childItem)
        return createIndex(row, column, childItem);
    else
        return QModelIndex();


QModelIndex TreeModel::parent(const QModelIndex &index) const
    if (!index.isValid())
        return QModelIndex();
    TreeElement *childItem = static_cast<TreeElement*>(index.internalPointer());
    TreeElement *parentItem = static_cast<TreeElement *>(childItem->parentItem());
    if (parentItem == m_root)
        return QModelIndex();
    return createIndex(parentItem->pos(), 0, parentItem);


int TreeModel::rowCount(const QModelIndex &parent) const
    if (parent.column() > 0)
        return 0;
    TreeElement *parentItem = elementFromIndex(parent);
    return parentItem->count();


int TreeModel::columnCount(const QModelIndex &parent) const
    Q_UNUSED(parent)
    return 1;


QQmlListProperty<TreeElement> TreeModel::items()
    return m_root->items();


QVariantList TreeModel::roles() const
    QVariantList list;
    QHashIterator<int, QByteArray> i(m_roles);
    while (i.hasNext()) 
        i.next();
        list.append(i.value());
    

    return list;


void TreeModel::setRoles(const QVariantList &roles)
    static int nextRole = Qt::UserRole + 1;
    for(QVariant role : roles) 
        m_roles.insert(nextRole, role.toByteArray());
        nextRole ++;
    
    emit rolesChanged();


QModelIndex TreeModel::indexFromElement(TreeElement *item)
    QVector<int> positions;
    QModelIndex result;
    if(item) 
        do
            int pos = item->pos();
            positions.append(pos);
            item = item->parentItem();
         while(item != nullptr);

        for (int i = positions.size() - 2; i >= 0 ; i--)
            result = index(positions[i], 0, result);
    
    return result;



bool TreeModel::insertElement(TreeElement *item, const QModelIndex &parent, int pos)
    TreeElement *parentElement = elementFromIndex(parent);
    if(pos >= parentElement->count())
        return false;
    if(pos < 0)
        pos = parentElement->count();
    beginInsertRows(parent, pos, pos);
    bool retValue = parentElement->insertItem(item, pos);
    endInsertRows();
    return retValue;


TreeElement *TreeModel::elementFromIndex(const QModelIndex &index) const
    if(index.isValid())
        return static_cast<TreeElement *>(index.internalPointer());
    return m_root;

ma​​in.cpp

#include "treemodel.h"
#include "treeelement.h"

#include <QGuiApplication>
#include <QQmlApplicationEngine>

static void registertypes()
    qmlRegisterType<TreeElement>("foo", 1, 0, "TreeElement");
    qmlRegisterType<TreeModel>("foo", 1, 0, "TreeModel");


Q_COREAPP_STARTUP_FUNCTION(registertypes)

int main(int argc, char *argv[])

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);    
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) 
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    , Qt::QueuedConnection);
    engine.load(url);

    return app.exec();

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.4

import foo 1.0

Window 
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    TreeModel 
        id: treemodel
        roles: ["phrase"]
        TreeElement
            property string phrase: "Hey"
            TreeElement
                property string phrase: "What's"
                TreeElement
                    property string phrase: "Up?"
                
            
        
    
    TreeView 
        anchors.fill: parent
        model: treemodel
        TableViewColumn 
            title: "Name"
            role: "phrase"
            width: 200
        
    

输出:

您找到的完整示例here

【讨论】:

> 没有可以使用 TreeView 的原生 QML 模型 很高兴知道,谢谢! TreeView 仅在 QtQuick.Controls 1.x 中可用,如果我想使用最新的 QtQuick.Controls 怎么办?【参考方案2】:

我在 QML 中创建了一个可折叠的框架(一个带有标题和内容的组合框)。如果您确定永远不会更改结构,则可以将其用于您的目的:

我通过删除无用部分(动画、装饰等)来简化代码。所以下面的代码可以改进。但是,我保留了这个想法:

// CollapsibleGroupBox.qml
Item 
    property alias contentItem: content.contentItem;
    property string title: ""
    Item 
        id: titleBar
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.right: parent.right
        height: 30
        Row 
            anchors.fill: parent
            CheckBox 
                Layout.alignment: Qt.AlignLeft
                id: expand
                checked: true;
            
            Text 
                Layout.alignment: Qt.AlignLeft
                text: title
            
        
    
    Pane 
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.top: titleBar.bottom
        anchors.bottom: parent.bottom
        topPadding: 0
        visible: expand.checked
        id: content
    

// Main.qml
Item 
    height: 500
    width: 500
    CollapsibleGroupBox 
        anchors.fill: parent
        title: "Hey!"
        contentItem: CollapsibleGroupBox 
            title: "What's"
            contentItem: CollapsibleGroupBox 
                title: "up?"
            
        
    

你会得到:

您也可以将复选框替换为MouseArea

【讨论】:

谢谢!这正是我想要的。【参考方案3】:

我还创建了一个仅使用 QML 组件的模型:

import QtQuick 2.9
import QtQuick.Window 2.2
import UISettings 1.0
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.4 as SV



Window 
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    Flickable 
        id: flick
        anchors.fill: parent
        clip: true
        contentHeight: col.implicitHeight
        property var mymodel: 
            "animals": 
                "big": 
                    "land": "elephants",
                    "water": "whales"
                ,
                "small": 
                    "land": "mice",
                    "water": "fish"
                
            ,
            "plants": 
                "trees": "evergreens"
            
        

        Column 
            id: col
            Component.onCompleted: componentListView.createObject(this, "objmodel":flick.mymodel);
        

        Component 
            id: componentListView
            Repeater 
                id: repeater
                property var objmodel: ()
                model: Object.keys(objmodel)

                ColumnLayout 
                    Layout.leftMargin: 50
                    Button 
                        property var sprite: null
                        text: modelData
                        onClicked: 
                            if(sprite === null) 
                                if(typeof objmodel[modelData] === 'object')
                                sprite = componentListView.createObject(parent, "objmodel":objmodel[modelData]);
                            
                            else
                                sprite.destroy()

                        
                    
                
            
        
    


【讨论】:

以上是关于这是 QML 中最小可行的 TreeView 模型吗?的主要内容,如果未能解决你的问题,请参考以下文章

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

为 QML TreeView 创建模型

QML TreeView 的 C++ 模型

将数据添加到 QML TreeView

来自模型的 QML 树视图

QML TreeView按级别或自定义委托显示节点