基于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 } } } } }
关键功能说明
展开/收起逻辑
- 给每个ListElement添加
expanded属性控制展开状态 - 仅存在子项的节点显示展开/收起按钮
- 通过
isItemVisible递归检查父节点展开状态,控制子节点的可见性与高度
- 给每个ListElement添加
批量复选逻辑
- 给每个ListElement添加
checked属性记录选中状态 - 点击父节点复选框时,递归同步所有后代节点的选中状态
- 维护
selectedItems列表,方便直接获取所有选中的itemId
- 给每个ListElement添加
层级缩进
- 保留原有
getIndentationLevel函数计算层级,通过锚点左边距实现缩进效果
- 保留原有
内容的提问来源于stack exchange,提问作者mayadev
相关产品推荐
相关产品推荐

