qml自定义ItemModel

111 阅读5分钟
treeitem.h
#pragma once
#include <QVariant>

class TreeItem
{
public:
    explicit TreeItem(const QString &folderName, const QString &folderPath, bool newFolder, TreeItem *parent = nullptr);
    ~TreeItem();

    void appendChild(TreeItem *child);

    TreeItem *child(int row);
    int childCount() const;
    int columnCount() const;
    QVariant data(int column) const;
    int row() const;
    TreeItem *parentItem();

    QList<TreeItem *> m_childItems;
    QString m_folderName;
    QString m_folderPath;
    bool m_newFolder;
    TreeItem *m_parentItem;
};
treeitem.cpp
#include "treeitem.h"

TreeItem::TreeItem(const QString &folderName, const QString &folderPath, bool newFolder, TreeItem *parent)
        : m_folderName{folderName}, m_folderPath{folderPath}, m_newFolder{newFolder}, m_parentItem{parent}
{

}

TreeItem::~TreeItem()
{
    qDeleteAll(m_childItems);
}

void TreeItem::appendChild(TreeItem *child)
{
    m_childItems.append(child);
}

TreeItem *TreeItem::child(int row)
{
    if (row < 0 || row >= m_childItems.count()) {
        return nullptr;
    }
    return m_childItems.at(row);
}

int TreeItem::childCount() const
{
    return m_childItems.count();
}

int TreeItem::columnCount() const
{
    return 1;
}

QVariant TreeItem::data(int column) const
{
    switch (column) {
        case 0:
            return m_folderName;
        case 1:
            return m_folderPath;
        case 3:
            return m_newFolder;
        default:
            return QVariant();
    }
}

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

TreeItem *TreeItem::parentItem()
{
    return m_parentItem;
}
treemodel.h
#pragma once

#include <QAbstractItemModel>
#include "treeitem.h"

class TreeModel : public QAbstractItemModel
{
Q_OBJECT

public:
    explicit TreeModel(QObject *parent = nullptr);
    ~TreeModel();

    // Basic functionality:
    QModelIndex index(int row, int column,
                      const QModelIndex &parent = QModelIndex()) const override;
    QModelIndex parent(const QModelIndex &index) const override;

    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;

    QHash<int, QByteArray> roleNames() const override; //重新实现roleNames()
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

public slots:
    Q_INVOKABLE void showQModelIndex(const QModelIndex &index) const;
    Q_INVOKABLE void createFolder(const QModelIndex &index);
    Q_INVOKABLE void removeFolder(const QModelIndex &index);
    Q_INVOKABLE void saveFolder(const QModelIndex &index, const QString folderName);
    Q_INVOKABLE void delAllChild(const QModelIndex &index);
    Q_INVOKABLE void delAllChildAndSelf(const QModelIndex &index);
    Q_INVOKABLE void queryAllChild(const QModelIndex &index);

private:
    TreeItem *m_rootItem;

    enum Roles {
        FolderNameRole = Qt::UserRole + 1,
        FolderPathRole,
        NewFolderRole,
        ChildCountRole,
    };
};
treemodel.cpp
#include "treemodel.h"

TreeModel::TreeModel(QObject *parent)
        : QAbstractItemModel(parent)
{
    m_rootItem = new TreeItem("", "", false);
    for (int i = 0; i < 10; i++) {
        QString fileName = QString("Home222333333%1=").arg(i);
        m_rootItem->appendChild(new TreeItem(fileName, m_rootItem->m_folderPath + "/" + fileName, false, m_rootItem));
    }

    TreeItem *item_4 = m_rootItem->child(4);

    //插入数据
    for (int i = 0; i < 5; i++) {

        QString fileName = QString("Item6666666666%1=").arg(i);
        item_4->appendChild(new TreeItem(fileName, item_4->m_folderPath + "/" + fileName, false, item_4));
    }

    item_4->m_childItems.at(0)->appendChild(new TreeItem("====================", item_4->m_childItems.at(0)->m_folderPath + "/" , false, item_4->m_childItems.at(0)));
}

TreeModel::~TreeModel()
{
    delete m_rootItem;
}

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

    TreeItem *parentItem;

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

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

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

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

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

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

int TreeModel::rowCount(const QModelIndex &parent) const
{
    if (!parent.isValid()) {
        return m_rootItem->m_childItems.count();
    }

    TreeItem *parentItem = static_cast<TreeItem *>(parent.internalPointer());
    return parentItem->childCount();
}

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

QHash<int, QByteArray> TreeModel::roleNames() const {
    QHash<int, QByteArray> roleNames;
    roleNames.insert(TreeModel::FolderNameRole, QByteArray("folderName"));
    roleNames.insert(TreeModel::FolderPathRole, QByteArray("folderPath"));
    roleNames.insert(TreeModel::NewFolderRole, QByteArray("newFolder"));
    roleNames.insert(TreeModel::ChildCountRole, QByteArray("childCount"));
    return roleNames;
}

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

    TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
    switch (role) {
        case FolderNameRole:
            return item->m_folderName;
        case FolderPathRole:
            return item->m_folderPath;
        case NewFolderRole:
            return item->m_newFolder;
        case ChildCountRole:
            return item->childCount();
        default:
            return QVariant();
    }
}

void TreeModel::showQModelIndex(const QModelIndex &index) const
{
    TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
    qDebug() << "Show QModelIndex " << index << " internal pointer " << index.internalPointer()
             << " name = " << item->m_folderName << ", path = " + item->m_folderPath;
}

void TreeModel::createFolder(const QModelIndex &index)
{
    TreeItem *item = static_cast<TreeItem *>(index.internalPointer());

    beginInsertRows(index, 0, 0);
    item->appendChild(new TreeItem("New Folder", item->m_folderPath + "/" + "New Folder", true, item));
    endInsertRows();

//    item->m_folderName = item->m_folderName + "0";
//    qDebug() << "---> new = " << item->m_folderName;

    dataChanged(index, index);

//    beginResetModel();
//    endResetModel();

//    beginInsertRows(QModelIndex(), rowCount(), rowCount());
//    m_rootItem->appendChild(new TreeItem("11", m_rootItem->m_folderPath + "/" + "11", m_rootItem));
//    endInsertRows();

//    qDebug() << "==================== update ===" << chooseFolderPath << ",size = " << m_rootItem->childCount();
}

void TreeModel::removeFolder(const QModelIndex &index) {
    TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
    beginRemoveRows(index, 0, 0);
    item->m_childItems.remove(0);
    endRemoveRows();

    dataChanged(index, index);

//    beginResetModel();
//    endResetModel();
}

void TreeModel::saveFolder(const QModelIndex &index, const QString folderName) {
    TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
    item->m_folderName = folderName;
    item->m_newFolder = false;

    dataChanged(index, index);
}

void TreeModel::delAllChild(const QModelIndex &index) {
    qDebug() << "index.row() = " << index.row() << ", index.column() = " << index.column();
    TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
    if (item->childCount() > 0) {
        beginRemoveRows(index, 0, item->childCount() - 1);
        item->m_childItems.clear();
        endRemoveRows();
        dataChanged(index, index); //更新自己
    }
}

void TreeModel::delAllChildAndSelf(const QModelIndex &index) {
    qDebug() << "index.row() = " << index.row() << ", index.column() = " << index.column();
    TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
//    if (item->childCount() > 0) {
//        beginRemoveRows(index, 0, item->childCount() - 1);
//        item->m_childItems.clear();
//        endRemoveRows();
//        dataChanged(index, index); //更新自己
//    }
    if (item->m_parentItem) {
        qDebug() << "parent delete row = " << item->row();
        beginRemoveRows(index.parent(), item->row(), item->row());
        item->m_parentItem->m_childItems.removeAt(item->row());
        endRemoveRows();
    }
}

void TreeModel::queryAllChild(const QModelIndex &parentIndex) {
    TreeItem *item = static_cast<TreeItem *>(parentIndex.internalPointer());

    int count = rowCount(parentIndex);
    qDebug() << "parentName = " << item->m_folderName  << ", child count = " << count;
    for (int i = 0; i < count; i++) {
        QModelIndex childIndex = index(i, 0, parentIndex);
        TreeItem *childItem = static_cast<TreeItem *>(childIndex.internalPointer());
        qDebug() << "i = " << i << ".name = " << childItem->m_folderName << ",path = " << childItem->m_folderPath;

        childItem->m_folderName = "hello";
        dataChanged(childIndex, childIndex);
    }
}
main.cpp
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "treemodel.h"

int main(int argc, char *argv[]) {
//    qputenv("QT_QPA_PLATFORM", "xcb");
    QApplication a(argc, argv);
    TreeModel *model = new TreeModel();
    QQmlApplicationEngine engine;
    QQmlContext* context = engine.rootContext();
    context->setContextProperty("_model", model);
    engine.load(QUrl(QStringLiteral("../main.qml")));
    return QApplication::exec();
}
main.qml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls

Window {
    width: 600
    height: 400
    visible: true

    Rectangle {
        width: childrenRect.width
        height: childrenRect.height
        border.color: "red"
        TreeView {
            width: 400
            height: 400
            id: _view

            property string chooseFolder
            property int chooseRow

            property int maxWidth: 0

            boundsBehavior: Flickable.StopAtBounds

            // The model needs to be a QAbstractItemModel
            model: _model
            delegate: Item {
                onImplicitWidthChanged: {
                    // console.log("width ===========" + implicitWidth)
                    _view.maxWidth = Math.max(_view.maxWidth, implicitWidth)
                }

                id: treeDelegate
                implicitWidth: (arrow.width + fileIcon.width + 4 + 20 * treeDelegate.depth) + aaa.implicitWidth
                implicitHeight: 32

                readonly property real indent: 20
                readonly property real padding: 5
                // Assigned to by TreeView:
                required property TreeView treeView
                required property bool isTreeNode
                required property bool expanded
                required property int hasChildren
                required property int depth

                Rectangle {
                    y: 10
                    width: parent.implicitWidth
                    height: 1
                    color: "red"
                }

                Rectangle {
                    y: 30
                    width: aaa.width
                    height: 1
                    color: "green"
                    Text {
                        text: aaa.width
                    }
                }

                Rectangle {
                    y: 40
                    width: bbb.width
                    height: 1
                    color: "blue"
                    Text {
                        text: bbb.width
                    }
                }
                //
                // Rectangle {
                //     y: 40
                //     width: 20 * treeDelegate.depth
                //     height: 1
                //     color: "black"
                //     Text {
                //         text: 20 * treeDelegate.depth
                //     }
                // }

                Rectangle {
                    // width: arrow.width + fileIcon.width + 4 + aaa.width + bbb.width + 20 * treeDelegate.depth + 600
                    width: _view.maxWidth <= 400 ? 400 : _view.maxWidth
                    // width: _view.maxWidth
                    height: childrenRect.height
                    color: _view.chooseFolder === model.folderPath ? "yellow" : "transparent"
                    RowLayout {

                        spacing: 0
                        height: 32

                        Image {
                            id: arrow
                            source: "arrow_right.png"
                            opacity: treeDelegate.isTreeNode && treeDelegate.hasChildren ? 1 : 0
                            rotation: treeDelegate.expanded ? 90 : 0
                            Layout.leftMargin: 20 * treeDelegate.depth
                            MouseArea {
                                anchors.fill: parent
                                onClicked: treeView.toggleExpanded(row)
                            }
                        }

                        Image {
                            id: fileIcon
                            source: "file.png"
                            Layout.leftMargin: 4
                            MouseArea {
                                anchors.fill: parent
                                onClicked: {
                                    // _view.model.delAllChildAndSelf(_view.index(row, 0)) //删除当前节点数据
                                    // console.log(_view.model.rowCount(_view.index(row, 0))) //查询当前节点孩子数量

                                    /*
                                        遍历当前index下的所有孩子index
                                        var parentIndex = _view.index(row, 0)
                                        var childCount = _view.model.rowCount(parentIndex)
                                        console.log("childCount = " + childCount)
                                        for (var i = 0; i < childCount; i++) {
                                            var childIndex = _view.model.index(i, 0, parentIndex);
                                            _view.model.showQModelIndex(childIndex)
                                        }
                                     */
                                    console.log("cur r = " + row);
                                    var parentIndex = _view.index(row, 0) //注意是_view.index, 不是_view.model.index
                                    _view.model.queryAllChild(parentIndex)
                                }
                            }
                        }

                        Text {
                            id: aaa
                            text:  model.folderName + "(" + model.childCount + ")"  + ",r = " + row + ".column" + column + ",index = " + index
                            visible: model.newFolder === false
                            Layout.rightMargin: 100
                            MouseArea {
                                anchors.fill: parent
                                onClicked: {
                                    _view.chooseFolder = model.folderPath
                                    _view.chooseRow = row
                                }
                                onDoubleClicked: {
                                    var item = _view.index(row, 0)
                                    _view.model.createFolder(item)
                                    if (!expanded) {
                                        treeView.expand(row);
                                    }
                                }
                            }
                        }

                        TextInput {
                            id: bbb
                            text: model.folderName
                            color: "#222222";
                            // font.pixelSize: 14
                            visible: model.newFolder === true

                            width: model.newFolder === true ? implicitWidth : 0

                            onAccepted: {
                                console.log("User pressed Enter key.");
                                _view.model.saveFolder(_view.index(row, 0), text)

                                treeView.toggleExpanded(row - 1)
                                treeView.toggleExpanded(row - 1)
                            }
                        }
                    }
                }

                // Rectangle {
                //     width: rowLayout.width
                //     height: 1
                //     color: "red"
                // }

                // property int childrenCount: _view.model.queryChildrenCount(_view.index(row, column))
                // property bool isChoose: _view.model.getChooseFolderPath() === model.folderPath

                // RowLayout {
                //     id: rowLayout
                //     spacing: 0
                //     height: parent.height
                //
                //     Image {
                //         id: indicator
                //         source: "arrow_right.png"
                //         opacity: treeDelegate.isTreeNode && treeDelegate.hasChildren ? 1 : 0
                //         rotation: treeDelegate.expanded ? 90 : 0
                //         Layout.leftMargin: 20 * treeDelegate.depth
                //         MouseArea {
                //             anchors.fill: parent
                //             onClicked: {
                //                 //1111
                //                 var item = _view.index(row, 0)
                //                 _view.model.showQModelIndex(item)
                //                 //222
                //                 console.log("onClicked, row = " + row + ",column = " + column + ",count = " + model.childCount)
                //                 // console.log(_view.model.data(item, 0) + ", " + _view.model.data(item, 1) + ", " + _view.model.data(item, 2))
                //                 // _view.model.updateChooseFolderPath(item)
                //
                //
                //                 treeView.toggleExpanded(row) //展开或者收起
                //             }
                //         }
                //     }
                //
                //     Image {
                //         source: "file.png"
                //         Layout.leftMargin: 4
                //     }
                //
                //     Text {
                //         text: model.folderName + "(" + model.childCount + ")" /* + ",r = " + row + ".column" + column + ",index = " + index*/
                //         color: _view.chooseFolder === model.folderPath ? "red" : "#222222";
                //         font.pixelSize: 14
                //         Layout.leftMargin: 4
                //         visible: model.newFolder === false
                //         MouseArea {
                //             anchors.fill: parent
                //             onClicked: {
                //                 _view.chooseFolder = model.folderPath
                //                 _view.chooseRow = row
                //             }
                //             onDoubleClicked: {
                //                 var item = _view.index(_view.chooseRow, 0)
                //                 _view.model.createFolder(item)
                //                 if (!expanded) {
                //                     treeView.expand(row);
                //                 }
                //             }
                //         }
                //     }
                //
                //     Item {
                //         implicitWidth: model.newFolder === true ? edit.width + 8 : 0
                //         implicitHeight: model.newFolder === true? edit.height + 8 : 0
                //         visible: model.newFolder === true
                //         Layout.leftMargin: 4
                //         Rectangle {
                //             anchors.fill: parent
                //             border.color: "#0473FF"
                //             TextInput {
                //                 id: edit
                //                 anchors.centerIn: parent
                //                 text: model.folderName
                //                 color: "#222222";
                //                 font.pixelSize: 14
                //                 onAccepted: {
                //                     console.log("User pressed Enter key.");
                //                     _view.model.saveFolder(_view.index(row, 0), text)
                //                 }
                //             }
                //         }
                //     }
                //
                //     // Row {
                //     //     visible: model.newFolder === false
                //     //     Button {
                //     //         text: "add"
                //     //         onClicked: {
                //     //             var item = _view.index(row, 0)
                //     //             _view.model.createFolder(item)
                //     //             // _view.model.showQModelIndex(item)
                //     //             if (!expanded) {
                //     //                 treeView.expand(row);
                //     //             }
                //     //             // treeView.toggleExpanded(row)
                //     //         }
                //     //     }
                //     //
                //     //     Button {
                //     //         text: "remove"
                //     //         onClicked: {
                //     //             var item = _view.index(row, 0)
                //     //             _view.model.removeFolder(item)
                //     //             // treeView.collapse(row)
                //     //             // if (model.childCount === 0) {
                //     //             //
                //     //             // }
                //     //             // _view.model.showQModelIndex(item)
                //     //         }
                //     //     }
                //     // }
                // }
            }
        }
    }
}
运行效果

image.png