QT:TreeView刷新后记忆展开,可多层嵌套

文章介绍了如何在Qt的QTreeView中实现刷新功能,同时保持节点的展开状态。使用NodeExpand结构存储节点及其子节点的状态,通过递归方法在初始化、刷新和数据更新时记录和恢复展开状态。

逻辑结构

界面上有一个”刷新“,点击后刷新列表,并按刷新前的情况展开。


具体代码

我的treeview是ui提升来的,_treeViewModel是treeview对应的model。

////.h

typedef struct _NodeP
{
    int nRowNo;
    std::vector<_NodeP> arChildren;
}NodeExpand;

class A: public QWidget
{
Q_OBJECT

public:
    void init();
    void startRemeberNode();
    void startExpandNode();
    void recursiveRememberNodeState(NodeExpand& ne, QModelIndex root);
    void recursiveExpandNode(NodeExpand& ne, QModelIndex root);
private:
    Ui::A*ui;
    NodeExpand _nodeExpand;
    QStandardItemModel *_treeViewModel = Q_NULLPTR;
};

#endif

 ”刷新“按钮调用 init(),因为存在多次刷新,所以每一次startRemeberNode的时候要清空arChildren。

/////.cpp

void A::init()
{
    startRemeberNode();

    /**
    * 这里写你的刷新函数
    **/

    startExpandNode();
}

void A::recursiveRememberNodeState(NodeExpand &ne, QModelIndex root)
{
    int nChild = _treeViewModel->rowCount(root);

    for (int i = 0; i < nChild; i++)
    {
        QModelIndex index = _treeViewModel->index(i, 0, root);
        if (ui->treeView->isExpanded(index))
        {
            NodeExpand n;
            n.nRowNo = i;
            recursiveRememberNodeState(n, index);
            ne.arChildren.push_back(n);
        }
    }
}


void A::recursiveExpandNode(NodeExpand &ne, QModelIndex root)
{
    QModelIndex index = _treeViewModel->index(ne.nRowNo, 0, root);
    if (!index.isValid())
    {
        return;
    }


    ui->treeView->expand(index);
    for (int i = 0; i < ne.arChildren.size(); i++)
    {
        recursiveExpandNode(ne.arChildren[i], index);
    }
}

void A::startExpandNode()
{
    QModelIndex root = ui->treeView->rootIndex();
    for (int i = 0; i < _nodeExpand.arChildren.size(); i++)
    {
        recursiveExpandNode(_nodeExpand.arChildren[i], root);
    }
}


void A::startRemeberNode()
{
    _nodeExpand.arChildren.clear();
    QModelIndex root = ui->treeView->rootIndex();
    recursiveRememberNodeState(_nodeExpand, root);
}

参考借鉴

QTreeVIew 记忆展开状态, 在数据更新时能保持原样_qtreeview model展开状态记录-优快云博客

import QtQuick import QtQuick.Controls // ≥6.3 才带 TreeView import Qt.labs.qmlmodels 6.2 // TreeModel 仍放在 labs import QtQuick.Layouts 1.15 import QtQuick.Window 2.15 import QtQuick.Controls.Basic 6.2 // TreeViewDelegate 所在 Rectangle { id: mainWindow width: 1920 height: 1080 // visible: true // title: "工业设备监控系统" // color: "#1e1e2e" color: "transparent" // 摄像机总模型(示例) ListModel { id: cameraModel ListElement { type: "皮带"; name: "Camera1"; cameraId: "CAM-BELT-1" } ListElement { type: "皮带"; name: "Camera2"; cameraId: "CAM-BELT-2" } ListElement { type: "钢丝绳"; name: "Camera3"; cameraId: "CAM-WIRE-1" } ListElement { type: "刮板链"; name: "Camera4"; cameraId: "CAM-CHAIN-1" } ListElement { type: "托辊"; name: "Camera5"; cameraId: "CAM-ROLLER-1" } } // 报表总模型(示例) ListModel { id: reportAllModel ListElement { deviceType:"皮带"; recordId:1; timestamp:"2023-07-15 09:45"; type:"皮带跑偏"; level:"警告" } ListElement { deviceType:"钢丝绳"; recordId:2; timestamp:"2023-07-15 11:30"; type:"断丝"; level:"警告" } ListElement { deviceType:"刮板链"; recordId:3; timestamp:"2023-07-15 13:45"; type:"振动异常"; level:"注意" } ListElement { deviceType:"托辊"; recordId:4; timestamp:"2023-07-15 15:20"; type:"磨损"; level:"严重" } } // 顶部状态栏 Rectangle { id: statusBar y: 0 anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: 0 anchors.rightMargin: 0 height: 0.000001 color: "#00007f" opacity :0 //RowLayout { //anchors.fill: parent //anchors.margins: 10 //spacing: 12 //Text { text: "工业设备监控系统"; color: "white"; font.bold: true; font.pixelSize: 18 } //Rectangle { width: 12; height: 12; radius: 6; color: "green" } //Text { text: "系统运行中"; color: "white"; font.pixelSize: 14 } //Item { Layout.fillWidth: true } //Text { // id: timeDisplay // text: Qt.formatDateTime(new Date(), "yyyy-MM-dd hh:mm:ss") // color: "white"; font.pixelSize: 14 // Timer { interval: 1000; running: true; repeat: true; onTriggered: timeDisplay.text = Qt.formatDateTime(new Date(), "yyyy-MM-dd hh:mm:ss") } //} //} } // 主体区域:三列布局(左 列 中 右 列) RowLayout { anchors.top: statusBar.bottom anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right anchors.margins: 12 anchors.leftMargin: 16 anchors.rightMargin: 8 anchors.topMargin: 0 anchors.bottomMargin: 24 spacing: 12 // 左侧:摄像机列表 Rectangle { id: cameraListPanel Layout.preferredWidth: parent.width * 0.2 Layout.fillHeight: true color: "#2c3e50" radius: 8 property string selectedType: "皮带" ColumnLayout { anchors.fill: parent anchors.margins: 12 spacing: 12 Text { text: "摄像机列表"; color: "white"; font.pixelSize: 20; font.bold: true; horizontalAlignment: Text.AlignHCenter } ColumnLayout{ Layout.fillWidth: true // 关键:让内层布局填满外层宽度 spacing: 12 // 建议添加间距,避免组件挤在一起 Rectangle { id: searchBoxContainer Layout.fillWidth: true height: 50 color: "white" border.color: "#ddd" border.width: 1 RowLayout { anchors.fill: parent anchors.leftMargin: 15 anchors.rightMargin: 15 spacing: 10 // 搜索图标 Text { text: "🔍" font.pixelSize: 16 Layout.alignment: Qt.AlignVCenter } // 搜索输入框 TextField { id: searchField Layout.fillWidth: true placeholderText: "输入搜索内容..." font.pixelSize: 16 background: Rectangle { color: "transparent" } onTextChanged: { // 使用计时器延迟搜索,避免频繁搜索 searchTimer.restart() } // 按回车键执行搜索 onAccepted: { performSearch(text) } } } } ComboBox { id: comboBox // 删掉 Layout.preferredWidth: 350,换成自适应 Layout.fillWidth: true // 和搜索框一样,填充父容器 Layout.preferredHeight: 50 Layout.preferredWidth: 350 background: Rectangle{ color:"white" } model: ["1 画面", "2 画面", "3 画面", "4 画面"] currentIndex: 0 onCurrentIndexChanged: { cam1.visible = false cam2.visible = false cam3.visible = false cam4.visible = false if (currentIndex >= 0) cam1.visible = true if (currentIndex >= 1) cam2.visible = true if (currentIndex >= 2) cam3.visible = true if (currentIndex >= 3) cam4.visible = true } } } ListModel { id: flatModel ListElement { name: "根节点"; depth: 0; hasChildren: true; visible: true } ListElement { name: "子节点 1"; depth: 1; hasChildren: false; visible: false } ListElement { name: "子节点 2"; depth: 1; hasChildren: false; visible: false } } /* 3. 第一次把顶层灌进去 */ Component.onCompleted: { for (let i = 0; i < rawData.length; i++) flatModel.append({ name: rawData[i].name, depth: 0, expanded: false }); } /* 4. 展开/折叠逻辑 */ function toggle(index) { const node = flatModel.get(index); if (node.depth !== 0) return; // 目前只支持一层,想多层自己递归 if (node.expanded) { // 已展开 → 收拢 /* 从 index+1 开始连续删掉 depth=1 的行 */ for (var i = flatModel.count - 1; i > index; --i) { if (flatModel.get(i).depth === 1) flatModel.remove(i); } flatModel.setProperty(index, "expanded", false); } else { // 未展开展开 const kids = rawData[0].children; // 这里按自己数据结构取 for (let i = 0; i < kids.length; i++) flatModel.insert(index + 1 + i, { name: kids[i], depth: 1 }); flatModel.setProperty(index, "expanded", true); } } TreeView { Layout.preferredWidth: 350 // 关键修复:设置最小高度,避免被ListView挤压 Layout.minimumHeight: 250 // 强制保留至少250像素高度 Layout.maximumHeight: 300 // 可选:限制最大高度,防止过高 Layout.alignment: Qt.AlignTop model: flatModel rootIndex: undefined delegate: ItemDelegate { text: model.name width: parent.width leftPadding: model.depth * 20 onClicked: toggle(index) // 这里调用展开/折叠 } } ListView { id: cameraListView Layout.fillWidth: true Layout.fillHeight: true model: ListModel { id: filteredCameraModel } spacing: 6 } } onSelectedTypeChanged: updateCameraModel() function updateCameraModel() { filteredCameraModel.clear() for (let i = 0; i < cameraModel.count; ++i) { const it = cameraModel.get(i) if (it.type === selectedType) filteredCameraModel.append(it) } } } // 中间:4个摄像头(均分 2x2) Rectangle { id: centerPanel Layout.fillWidth: true Layout.fillHeight: true color: "#2c3e50" radius: 8 GridLayout { id: centerGrid anchors.fill: parent anchors.margins: 12 columns: 2 rows: 2 columnSpacing: 12 rowSpacing: 12 // 每个 CameraView 使用 Layout.fillWidth/fillHeight 以均分 CameraView { id: cam1 Layout.fillWidth: true Layout.fillHeight: true title: "摄像头1" cameraId: "CAM-BELT-1" cameraName: "Camera1" borderColor: "#3498db" visible: false // ← 关键 } CameraView { id: cam2 Layout.fillWidth: true Layout.fillHeight: true title: "摄像头2" cameraId: "CAM-WIRE-1" cameraName: "Camera3" borderColor: "#2ecc71" visible: false } CameraView { id: cam3 Layout.fillWidth: true Layout.fillHeight: true title: "摄像头3" cameraId: "CAM-CHAIN-1" cameraName: "Camera4" borderColor: "#e74c3c" visible: false // 👇 关键:当只有3个画面时,cam3 居中显示 Layout.columnSpan: cam3.visible && !cam4.visible ? 2 : 1 Layout.alignment: cam3.visible && !cam4.visible ? Qt.AlignHCenter : Qt.AlignLeft } CameraView { id: cam4 Layout.fillWidth: true Layout.fillHeight: true title: "摄像头4" cameraId: "CAM-ROLLER-1" cameraName: "Camera5" borderColor: "#f39c12" visible: false } } } } Item { id: __materialLibrary__ } /*********************** * CameraView 组件(已修正为用 anchors 控制,确保“模拟画面”在画面区域内部) ***********************/ component CameraView: Rectangle { id: cameraRoot property string title: "摄像机" property string cameraId: "" property string cameraName: "" property color borderColor: "#3498db" color: "#34495e" radius: 8 border.color: borderColor border.width: 3 // 使用 anchors 布局:titleBar 在上方固定高度,videoArea 占剩余空间 // 这样内部的模拟画面始终在 videoArea 区域内居中 Rectangle { id: titleBar anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top height: 36 color: "#2c3e50" radius: 4 border.color: Qt.darker(borderColor, 1.2) border.width: 1 Text { anchors.centerIn: parent text: cameraRoot.title color: "white" font.pixelSize: 16 font.bold: true } } Rectangle { id: videoArea anchors.left: parent.left anchors.right: parent.right anchors.top: titleBar.bottom anchors.bottom: parent.bottom anchors.margins: 6 color: "#2c3e50" radius: 4 border.color: Qt.darker(borderColor, 1.5) border.width: 1 // 背景画面(内边距) Rectangle { id: bg anchors.fill: parent anchors.margins: 6 color: "#1e1e2e" radius: 3 border.color: "#7f8c8d" border.width: 1 } // 中间明显的模拟画面框(**明确锚定到 videoArea 的中心**) Rectangle { id: simVideo width: Math.max(120, videoArea.width * 0.78) height: Math.max(80, videoArea.height * 0.62) anchors.horizontalCenter: videoArea.horizontalCenter anchors.verticalCenter: videoArea.verticalCenter color: "#555555" radius: 6 border.color: "#aaaaaa" border.width: 2 Text { anchors.centerIn: parent text: "模拟画面" color: "white" font.pixelSize: 20 font.bold: true } } // 左下角显示 摄像机名称 + ID(锚定到 videoArea,确保不与 titleBar 重合) Text { id: cameraLabel text: cameraRoot.cameraName + " (" + cameraRoot.cameraId + ")" color: "white" font.pixelSize: 13 anchors.left: videoArea.left anchors.bottom: videoArea.bottom anchors.margins: 8 } // 右上角状态指示器(锚定到 videoArea) Rectangle { width: 12; height: 12; radius: 6 color: "green" anchors.top: videoArea.top anchors.right: videoArea.right anchors.margins: 8 } } } // end CameraView } 为什么树状图没有显示
10-24
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值