You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Qt 6.8的QML自定义TreeView组件功能扩展问询

自定义TreeView组件实现展开/收起与批量复选功能

需求背景

基于Qt 6.8、QML和Python的项目中,需用扁平结构的ListModel(包含itemId/itemName/parentId)实现TreeView展示,原生TreeView依赖后端模型限制较大,已实现层级缩进,需补充:

  • 节点展开/收起按钮
  • 复选框功能:叶子节点选中单个条目,父节点选中所有子项

修改后的代码

main.qml

import QtQuick 6.8
import QtQuick.Window 6.8
import QtQuick.Layouts 1.3
import QtQuick.Controls 6.8

Window {
    width: 300
    height: 500
    visible: true
    title: "Custom Tree View"

    TreeViewCustom {
        id: treeView
        anchors.fill: parent
        // 监听选中列表变化,可对接业务逻辑
        onSelectedItemsChanged: console.log("已选中ID:", selectedItems)
    }
}

TreeViewCustom.qml

import QtQuick 6.8
import QtQuick.Controls 6.8
import QtQuick.Layouts 1.3

ColumnLayout {
    // 带状态属性的扁平数据模型
    property var dataModel: ListModel {
        ListElement { itemId: 1; itemName: "Parent 1"; parentId: null; expanded: true; checked: false }
        ListElement { itemId: 2; itemName: "Child 1"; parentId: 1; expanded: true; checked: false }
        ListElement { itemId: 3; itemName: "Child 2"; parentId: 1; expanded: true; checked: false }
        ListElement { itemId: 4; itemName: "Parent 2"; parentId: null; expanded: true; checked: false }
        ListElement { itemId: 5; itemName: "Parent 3"; parentId: 4; expanded: true; checked: false }
        ListElement { itemId: 6; itemName: "Child 3"; parentId: 5; expanded: true; checked: false }
    }
    // 存储选中的itemId列表
    property var selectedItems: []

    spacing: 0

    // 获取节点缩进层级
    function getIndentationLevel(itemId) {
        let level = -1
        let currentItemId = itemId
        while (true) {
            let parentFound = false
            for (let i = 0; i < dataModel.count; i++) {
                const element = dataModel.get(i)
                if (element.itemId === currentItemId) {
                    if (element.parentId === null) {
                        return level
                    } else {
                        currentItemId = element.parentId
                        level++
                        parentFound = true
                        break
                    }
                }
            }
            if (!parentFound) break
        }
        return level
    }

    // 判断节点是否存在子项
    function hasChildren(itemId) {
        for (let i = 0; i < dataModel.count; i++) {
            if (dataModel.get(i).parentId === itemId) {
                return true
            }
        }
        return false
    }

    // 递归获取节点所有后代
    function getAllDescendants(itemId) {
        let descendants = []
        function findChildren(parentId) {
            for (let i = 0; i < dataModel.count; i++) {
                const elem = dataModel.get(i)
                if (elem.parentId === parentId) {
                    descendants.push(elem)
                    findChildren(elem.itemId)
                }
            }
        }
        findChildren(itemId)
        return descendants
    }

    // 切换节点展开/收起状态
    function toggleExpand(itemId) {
        const targetIdx = dataModel.indexOf(itemId, "itemId")
        if (targetIdx !== -1) {
            const elem = dataModel.get(targetIdx)
            elem.expanded = !elem.expanded
        }
    }

    // 切换节点选中状态,同步所有子节点
    function toggleCheck(itemId) {
        const targetIdx = dataModel.indexOf(itemId, "itemId")
        if (targetIdx === -1) return

        const targetItem = dataModel.get(targetIdx)
        const newState = !targetItem.checked
        targetItem.checked = newState

        // 同步所有后代节点的选中状态
        getAllDescendants(itemId).forEach(elem => {
            elem.checked = newState
        })

        // 更新选中列表
        updateSelectedItems()
    }

    // 更新选中的itemId列表
    function updateSelectedItems() {
        selectedItems = []
        for (let i = 0; i < dataModel.count; i++) {
            const elem = dataModel.get(i)
            if (elem.checked) {
                selectedItems.push(elem.itemId)
            }
        }
    }

    // 判断节点是否可见(父节点展开则显示)
    function isItemVisible(itemId) {
        let currentId = itemId
        while (true) {
            const currentIdx = dataModel.indexOf(currentId, "itemId")
            if (currentIdx === -1) return true

            const currentItem = dataModel.get(currentIdx)
            // 根节点直接返回自身展开状态
            if (currentItem.parentId === null) {
                return currentItem.expanded
            }

            // 检查父节点是否展开
            const parentIdx = dataModel.indexOf(currentItem.parentId, "itemId")
            if (parentIdx === -1) return true

            const parentItem = dataModel.get(parentIdx)
            if (!parentItem.expanded) {
                return false
            }

            currentId = currentItem.parentId
        }
    }

    Repeater {
        model: dataModel
        delegate: Item {
            width: parent.width
            height: isItemVisible(model.itemId) ? 40 : 0
            visible: isItemVisible(model.itemId)

            RowLayout {
                spacing: 8
                anchors.fill: parent
                anchors.leftMargin: 20 * getIndentationLevel(model.itemId)

                // 展开/收起按钮(仅有子项的节点显示)
                Button {
                    visible: hasChildren(model.itemId)
                    width: 20
                    height: 20
                    contentItem: Text {
                        text: model.expanded ? "▼" : "▶"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                    onClicked: toggleExpand(model.itemId)
                }

                // 复选框
                CheckBox {
                    checked: model.checked
                    onClicked: toggleCheck(model.itemId)
                }

                // 节点文本标签
                Label {
                    text: model.itemId + " - " + model.itemName
                    Layout.fillWidth: true
                    verticalAlignment: Text.AlignVCenter
                }
            }
        }
    }
}

关键功能说明

  1. 展开/收起逻辑

    • 给每个ListElement添加expanded属性控制展开状态
    • 仅存在子项的节点显示展开/收起按钮
    • 通过isItemVisible递归检查父节点展开状态,控制子节点的可见性与高度
  2. 批量复选逻辑

    • 给每个ListElement添加checked属性记录选中状态
    • 点击父节点复选框时,递归同步所有后代节点的选中状态
    • 维护selectedItems列表,方便直接获取所有选中的itemId
  3. 层级缩进

    • 保留原有getIndentationLevel函数计算层级,通过锚点左边距实现缩进效果

内容的提问来源于stack exchange,提问作者mayadev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 22:47:03