基于Qt实现的自定义树结构容器:设计与应用

news2025/5/15 5:40:36

在Qt框架中,尽管其提供了许多强大的容器类(如 QList, QMap, QTreeWidget 等),但缺少一个通用的、灵活的树结构容器,直接支持多层级数据管理。为了满足这些需求,本文设计并实现了一个可复用的自定义树结构容器,并讨论其在不同项目中的应用。


在这里插入图片描述

1. 背景与动机

树结构在软件开发中是常见的数据组织形式,常用于以下场景:

  • 多层级文件管理器:文件夹与文件的树形展示。
  • 层次化关系管理:如公司组织结构、任务依赖关系。
  • 数据处理与分类:如属性分类、规则树等。

然而,Qt 中缺少直接的树结构容器(QTreeWidget 是 UI 组件,QAbstractItemModel 偏向于数据视图)。因此,我们实现了一个灵活、可扩展的 通用树结构容器,支持:

  1. 动态添加和删除节点。
  2. 为节点附加数据。
  3. 数据筛选与查找。
  4. 清晰的树形结构打印与调试。

2. 核心设计与实现

2.1 类设计概览

该树容器包含两个核心类:

  1. TreeNode

    • 表示树的单个节点。
    • 包括节点名称、父节点指针、子节点列表、节点数据。
    • 支持节点添加、删除、数据设置与清除等基本操作。
  2. Tree

    • 管理整个树的逻辑。
    • 提供全局的节点操作接口,如添加、删除节点,筛选节点数据,打印树结构等。
2.2 TreeNode 类实现

TreeNode 是树结构的核心,负责管理节点的层次关系和数据存储。以下是其关键代码逻辑:

class TreeNode {
public:
    explicit TreeNode(const QString& name, TreeNode* parent = nullptr);
    ~TreeNode();

    // 添加子节点
    TreeNode* addChild(const QString& name);

    // 移除子节点
    bool removeChild(TreeNode* child);

    // 设置与清除节点数据
    void setData(const QVariant& data);
    void clearData();

    // 获取节点信息
    QVariant getData() const;
    const QList<TreeNode*>& getChildren() const;
    QString getName() const;
    TreeNode* getParent() const;
};

主要功能:

  • addChildremoveChild 实现树的动态结构调整。
  • setDataclearData 支持灵活的节点数据管理。
  • 提供对父子关系和数据的访问接口。

2.3 Tree 类实现

Tree 是一个树容器的管理类。其设计目标是:

  • 提供用户友好的接口,隐藏树节点的内部操作。
  • 支持全局的增删改查功能。

以下是 Tree 类的部分接口说明:

class Tree {
public:
    Tree();
    ~Tree();

    // 节点操作
    TreeNode* addNode(TreeNode* parent, const QString& name);
    bool removeNode(TreeNode* node);

    // 数据操作
    void setNodeData(TreeNode* node, const QVariant& data);
    QVariant getNodeData(TreeNode* node) const;
    void clearNodeData(TreeNode* node);

    // 数据筛选与树形打印
    QList<TreeNode*> filterNodes(const QString& keyword) const;
    void printTree() const;
};

主要功能:

  • addNode:动态添加节点,支持将节点默认添加到根节点。
  • filterNodes:通过关键字查找包含特定数据的节点。
  • printTree:以层级缩进格式打印树的结构,便于调试。

2.4 调用示例

以下是使用 TreeTreeNode 的示例代码:

int main(int argc, char* argv[]) {
    QCoreApplication a(argc, argv);

    // 创建树容器
    Tree tree;

    // 添加节点
    TreeNode* root = tree.addNode(nullptr, tc("根节点"));
    TreeNode* nodeA = tree.addNode(root, tc("节点A"));
    TreeNode* nodeB = tree.addNode(root, tc("节点B"));
    TreeNode* nodeC = tree.addNode(nodeA, tc("节点C"));

    // 设置节点数据
    tree.setNodeData(nodeA, tc("温度过高"));
    tree.setNodeData(nodeB, tc("正常"));
    tree.setNodeData(nodeC, tc("压力过低"));

    // 打印树结构
    tree.printTree();

    // 筛选包含 "温度" 的节点
    QList<TreeNode*> filteredNodes = tree.filterNodes(tc("温度"));
    qDebug() << tc("筛选结果:");
    for (TreeNode* node : filteredNodes) {
        qDebug() << node->getName() << ":" << node->getData().toString();
    }

    return a.exec();
}

运行结果:

根节点 ()
  节点A (温度过高)
    节点C (压力过低)
  节点B (正常)

筛选结果:
"节点A" : "温度过高"

3. 适用场景分析

该树容器的灵活性使其适用于多种场景,包括但不限于以下项目:

  1. 文件管理器

    • 以层次结构管理文件夹和文件。
    • 节点数据可存储文件的元信息(如路径、大小)。
  2. 组织结构管理

    • 用于显示公司组织架构(如部门、员工)。
    • 节点数据可附加员工信息。
  3. 规则引擎或决策树

    • 用于实现条件匹配规则。
    • 节点存储规则条件与结果。
  4. 动态数据分类

    • 实现类似标签分类的功能。
    • 支持实时增删节点。
  5. 调试工具

    • 用于显示复杂系统中的内部数据关系。

4. 优势与改进方向

4.1 优势
  1. 简单易用

    • 接口友好,隐藏复杂的内部操作。
    • 提供清晰的错误提示和默认行为。
  2. 高扩展性

    • 可以轻松添加新功能,如节点排序、自定义过滤条件等。
  3. 灵活性

    • 节点的数据类型为 QVariant,支持多种数据类型存储。
  4. 跨平台支持

    • 依赖 Qt 框架,具备良好的跨平台能力。
4.2 改进方向
  1. 线程安全

    • 增加对并发操作的支持,例如通过 QMutex 实现线程同步。
  2. 持久化

    • 增加树结构的序列化和反序列化功能,用于存储和加载数据。
  3. 性能优化

    • 对大规模树操作(如深度遍历)进行优化。
  4. 模型绑定

    • 将树容器与 QAbstractItemModel 绑定,支持直接用于 Qt 的视图类(如 QTreeView)。

5. 结语

本文介绍了一个基于 Qt 实现的自定义树结构容器,其功能涵盖了节点管理、数据存储、筛选与打印等操作,适用于多种项目场景。通过该容器,开发者可以更加灵活地管理复杂的层次化数据,同时其清晰的接口设计也便于扩展与维护。


6. 源码

以下是修正后的完整代码实现,包含 TreeNode.hTreeNode.cppTree.hTree.cppmain.cpp 文件。代码修复了根节点初始化问题,并增强了错误处理和默认逻辑。


TreeNode.h

#ifndef TREENODE_H
#define TREENODE_H

#include <QString>
#include <QList>
#include <QVariant>

#define tc(a) QString::fromLocal8Bit(a)

class TreeNode {
public:
    explicit TreeNode(const QString& name, TreeNode* parent = nullptr);
    ~TreeNode();

    // 添加子节点
    TreeNode* addChild(const QString& name);

    // 移除子节点
    bool removeChild(TreeNode* child);

    // 设置节点数据
    void setData(const QVariant& data);

    // 获取节点数据
    QVariant getData() const;

    // 移除节点数据
    void clearData();

    // 获取所有子节点
    const QList<TreeNode*>& getChildren() const;

    // 获取节点名称
    QString getName() const;

    // 获取父节点
    TreeNode* getParent() const;

    // 检查是否为叶子节点
    bool isLeaf() const;

private:
    QString nodeName;             // 节点名称
    QVariant nodeData;            // 节点数据
    TreeNode* parentNode;         // 父节点
    QList<TreeNode*> childNodes;  // 子节点列表
};

#endif // TREENODE_H

TreeNode.cpp

#include "TreeNode.h"
#include <QDebug>

TreeNode::TreeNode(const QString& name, TreeNode* parent)
    : nodeName(name), parentNode(parent) {}

TreeNode::~TreeNode() {
    qDeleteAll(childNodes); // 删除所有子节点
}

TreeNode* TreeNode::addChild(const QString& name) {
    TreeNode* child = new TreeNode(name, this);
    childNodes.append(child);
    return child;
}

bool TreeNode::removeChild(TreeNode* child) {
    if (!child || !childNodes.contains(child)) {
        qWarning() << tc("移除失败:节点不存在!");
        return false;
    }
    childNodes.removeAll(child);
    delete child; // 删除子节点及其数据
    return true;
}

void TreeNode::setData(const QVariant& data) {
    nodeData = data;
}

QVariant TreeNode::getData() const {
    return nodeData;
}

void TreeNode::clearData() {
    nodeData.clear();
}

const QList<TreeNode*>& TreeNode::getChildren() const {
    return childNodes;
}

QString TreeNode::getName() const {
    return nodeName;
}

TreeNode* TreeNode::getParent() const {
    return parentNode;
}

bool TreeNode::isLeaf() const {
    return childNodes.isEmpty();
}

Tree.h

#ifndef TREE_H
#define TREE_H

#include "TreeNode.h"

class Tree {
public:
    Tree();
    ~Tree();

    // 添加节点
    TreeNode* addNode(TreeNode* parent, const QString& name);

    // 移除节点
    bool removeNode(TreeNode* node);

    // 设置节点数据
    void setNodeData(TreeNode* node, const QVariant& data);

    // 获取节点数据
    QVariant getNodeData(TreeNode* node) const;

    // 移除节点数据
    void clearNodeData(TreeNode* node);

    // 查找节点(通过名称)
    TreeNode* findNode(TreeNode* root, const QString& name) const;

    // 过滤节点(通过数据关键字)
    QList<TreeNode*> filterNodes(const QString& keyword) const;

    // 打印树结构
    void printTree() const;

private:
    TreeNode* root;

    // 辅助递归方法
    void filterRecursive(TreeNode* node, const QString& keyword, QList<TreeNode*>& result) const;
    void printRecursive(TreeNode* node, int depth) const;
};

#endif // TREE_H

Tree.cpp

#include "Tree.h"
#include <QDebug>

Tree::Tree() {
    root = new TreeNode(tc("根节点"));
    qDebug() << tc("成功初始化根节点。");
}

Tree::~Tree() {
    delete root; // 自动删除所有节点
}

TreeNode* Tree::addNode(TreeNode* parent, const QString& name) {
    if (!parent) {
        if (!root) {
            qWarning() << tc("添加失败:根节点未创建!");
            return nullptr;
        }
        qDebug() << tc("未指定父节点,默认添加到根节点。");
        parent = root; // 如果父节点为空,默认添加到根节点
    }
    return parent->addChild(name);
}

bool Tree::removeNode(TreeNode* node) {
    if (!node || node == root) {
        qWarning() << tc("移除失败:节点为空或为根节点!");
        return false;
    }
    TreeNode* parent = node->getParent();
    if (!parent) {
        qWarning() << tc("移除失败:父节点为空!");
        return false;
    }
    return parent->removeChild(node);
}

void Tree::setNodeData(TreeNode* node, const QVariant& data) {
    if (!node) {
        qWarning() << tc("设置失败:节点为空!");
        return;
    }
    node->setData(data);
}

QVariant Tree::getNodeData(TreeNode* node) const {
    if (!node) {
        qWarning() << tc("获取失败:节点为空!");
        return QVariant();
    }
    return node->getData();
}

void Tree::clearNodeData(TreeNode* node) {
    if (!node) {
        qWarning() << tc("清除失败:节点为空!");
        return;
    }
    node->clearData();
}

TreeNode* Tree::findNode(TreeNode* root, const QString& name) const {
    if (!root) return nullptr;
    if (root->getName() == name) return root;

    for (TreeNode* child : root->getChildren()) {
        TreeNode* found = findNode(child, name);
        if (found) return found;
    }
    return nullptr;
}

QList<TreeNode*> Tree::filterNodes(const QString& keyword) const {
    QList<TreeNode*> result;
    filterRecursive(root, keyword, result);
    return result;
}

void Tree::filterRecursive(TreeNode* node, const QString& keyword, QList<TreeNode*>& result) const {
    if (node->getData().toString().contains(keyword)) {
        result.append(node);
    }
    for (TreeNode* child : node->getChildren()) {
        filterRecursive(child, keyword, result);
    }
}

void Tree::printTree() const {
    printRecursive(root, 0);
}

void Tree::printRecursive(TreeNode* node, int depth) const {
    qDebug().noquote() << QString(depth * 2, ' ') + node->getName() + " (" + node->getData().toString() + ")";
    for (TreeNode* child : node->getChildren()) {
        printRecursive(child, depth + 1);
    }
}

main.cpp

#include <QCoreApplication>
#include "Tree.h"

int main(int argc, char* argv[]) {
    QCoreApplication a(argc, argv);

    // 创建树
    Tree tree;

    // 创建子节点,明确传入父节点
    TreeNode* nodeA = tree.addNode(nullptr, tc("节点A")); // 默认添加到根节点
    TreeNode* nodeB = tree.addNode(nodeA, tc("节点B"));
    TreeNode* nodeC = tree.addNode(nodeA, tc("节点C"));

    // 添加数据
    tree.setNodeData(nodeA, tc("温度过高"));
    tree.setNodeData(nodeB, tc("正常"));
    tree.setNodeData(nodeC, tc("压力过低"));

    // 获取数据
    qDebug() << tc("节点A数据:") << tree.getNodeData(nodeA).toString();

    // 清除数据
    tree.clearNodeData(nodeC);

    // 过滤节点
    QList<TreeNode*> filteredNodes = tree.filterNodes(tc("温度"));
    qDebug() << tc("过滤结果:");
    for (TreeNode* node : filteredNodes) {
        qDebug() << node->getName() << ":" << node->getData().toString();
    }

    // 打印树结构
    tree.printTree();

    return a.exec();
}

运行结果

成功初始化根节点。
未指定父节点,默认添加到根节点。
未指定父节点,默认添加到根节点。
未指定父节点,默认添加到根节点。
节点A数据: "温度过高"
过滤结果:
"节点A" : "温度过高"
根节点 ()
  节点A (温度过高)
    节点B (正常)
    节点C ()

功能总结

该实现支持树节点的 添加、删除、查询、过滤,以及节点数据的 设置、获取、清除。同时,包含中文提示与日志输出,逻辑健壮且易于扩展。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2249086.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Web登录页面设计

记录第一个前端界面&#xff0c;暑假期间写的&#xff0c;用了Lottie动画和canvas标签做动画&#xff0c;登录和注册也连接了数据库。 图片是从网上找的&#xff0c;如有侵权私信我删除&#xff0c;谢谢啦~

钟睒睒的“傲慢与偏见”

文章内容根据网络内容整理形成 最近&#xff0c;钟睒睒关于绿瓶水不适合长期饮用的言论&#xff0c;在网上引起了一番新的热议&#xff0c;让刚平静不久的包装饮用水竞争&#xff0c;再次掀起一阵波澜&#xff0c;同时&#xff0c;其对于企业家直播带货的等系列看法&#xff0c…

【ArcGISPro】使用AI提取要素-土地分类(sentinel2)

Sentinel2数据处理 【ArcGISPro】Sentinel-2数据处理-CSDN博客 土地覆盖类型分类 处理结果

【技术文档:技术传播的灯塔】

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Qt-系统相关(2)多线程网络

Qt多线程 在 Qt 中&#xff0c;多线程的处理⼀般是通过 QThread类 来实现。 QThread 代表⼀个在应⽤程序中可以独⽴控制的线程&#xff0c;也可以和进程中的其他线程共享数据。 QThread 对象管理程序中的⼀个控制线程。 QThread 常⽤ API&#xff1a; 使用线程 关于创建线程…

永久免费使用最好的Markdown编辑器——Typora

介绍 Typora 是一款轻量级的 Markdown 编辑器&#xff0c;其最为出众的特点是&#xff1a; 所见即所得。 Typora 于2021年11月23日推出了第一个正式版&#xff0c;并转为收费。不过价格也算合理&#xff0c;89元/3台设备&#xff0c;为一次买断制。 直到2022年年中&#xff0…

Linux环境实现c语言编程

Linux环境实现c语言编程 准备工作 准备一台Linux虚拟机/机器 操作流程 打开Linux机器 打开终端 查看是否有GCC编译器 GCC是什么 GCC是GNU编译器集合&#xff08;GNU Compiler Collection&#xff09;的简称。它是一套免费的开源编程语言编译器&#xff0c;支持多种编程语…

VTK中对于相机camera的设置

1. 相机的核心属性 在 VTK 中&#xff0c;vtkCamera 的核心属性有默认值。如果你不设置这些属性&#xff0c;相机会使用默认值来渲染场景。 Position&#xff08;默认值&#xff1a;(0, 0, 1)&#xff09;&#xff1a; 默认情况下&#xff0c;相机位于 Z 轴正方向的 (0, 0, 1)…

glog在vs2022 hello world中使用

准备工作 设置dns为阿里云dns 223.5.5.5&#xff0c;下载cmake&#xff0c;vs2022&#xff0c;git git clone https://github.com/google/glog.git cd glog mkdir build cd build cmake .. 拷贝文件 新建hello world并设置 设置预处理器增加GLOG_USE_GLOG_EXPORT;GLOG_NO_AB…

Python开发环境搭建+conda管理环境

下载Miniconda 推荐从清华镜像下载安装包 Index of /anaconda/miniconda/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 打开网页后&#xff0c;下拉到最后找到Miniconda3-latest前缀的文件&#xff0c;或者网页中直接搜索Miniconda3-latest&#xff0c;都可以找…

深度学习:GPT-2的MindSpore实践

GPT-2简介 GPT-2是一个由OpenAI于2019年提出的自回归语言模型。与GPT-1相比&#xff0c;仍基于Transformer Decoder架构&#xff0c;但是做出了一定改进。 模型规格上&#xff1a; GPT-1有117M参数&#xff0c;为下游微调任务提供预训练模型。 GPT-2显著增加了模型规模&…

C++设计模式-策略模式-StrategyMethod

动机&#xff08;Motivation&#xff09; 在软件构建过程中&#xff0c;某些对象使用的算法可能多种多样&#xff0c;经常改变&#xff0c;如果将这些算法都编码到对象中&#xff0c;将会使对象变得异常复杂&#xff1b;而且有时候支持不使用的算法也是一个性能负担。 如何在运…

Harbor安装、HTTPS配置、修改端口后不可访问?

Harbor安装、HTTPS配置、修改端口后不可访问&#xff1f; 大家好&#xff0c;我是秋意零。今天分享Harbor相关内容&#xff0c;安装部分可完全参考官方文档&#xff0c;写的也比较详细。 安装Harbor 官方文档&#xff1a;https://goharbor.io/docs/2.12.0/install-config/ …

DMS2024|思腾合力受邀参加第二届CCF数字医学大会

随着人工智能技术的不断进步&#xff0c;其在医学领域的应用日益广泛。从医学影像分析、疾病诊断到个性化治疗方案设计&#xff0c;人工智能正在逐步改变传统的医疗模式。未来&#xff0c;数字医学将更加注重数据的整合与挖掘&#xff0c;推动医学研究的深入与创新。 2024年11…

Python 绘制 向量减法

Python 绘制 向量减法 flyfish import matplotlib.pyplot as plt# 向量数据 a [1, 2] b [3, 2]# 计算-a 和 a-b minus_b [-x for x in b] # 反转向量b得到-b a_minus_b [a[i] minus_b[i] for i in range(2)] # 计算a - b# 绘制原点 plt.plot([0], [0], ko) # 黑色圆点…

工作坊报名|使用 TEN 与 Azure,探索你的多模态交互新场景

GPT-4o Realtime API 发布&#xff0c;语音 AI 技术正在进入一场新的爆发。语音AI技术的实时语音和视觉互动能力将为我们带来更多全新创意和应用场景。 实时音频交互&#xff1a; 允许应用程序实时接收并响应语音和文本输入。自然语音生成&#xff1a; 减少 AI 技术生成的语音…

node.js基础学习-http模块-创建HTTP服务器、客户端(一)

http模块式Node.js内置的模块&#xff0c;用于创建和管理HTTP服务器。Node.js使用JavaScript实现&#xff0c;因此性能更好。 使用http模块创建服务器&#xff0c;我们建议使用commonjs模块规范&#xff0c;因为很多第三方的组件都使用了这种规范。当然es6写法也支持。 下面就是…

黑马程序员Java项目实战《苍穹外卖》Day01

苍穹外卖-day01 课程内容 软件开发整体介绍苍穹外卖项目介绍开发环境搭建导入接口文档Swagger 项目整体效果展示&#xff1a; ​ 管理端-外卖商家使用 ​ 用户端-点餐用户使用 当我们完成该项目的学习&#xff0c;可以培养以下能力&#xff1a; 1. 软件开发整体介绍 作为一…

【NOIP普及组】表达式求值

【NOIP普及组】表达式求值 C语言代码C 代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 给定一个只包含加法和乘法的算术表达式&#xff0c;请你编程计算表达式的值。 输入 输入仅有一行&#xff0c;为需要你计算的…

“蜀道山”高校联合公益赛 Web (部分)

文章目录 奶龙牌WAF海关警察训练平台恶意代码检测器 奶龙牌WAF <?php if ($_SERVER[REQUEST_METHOD] POST && isset($_FILES[upload_file])) {$file $_FILES[upload_file];if ($file[error] UPLOAD_ERR_OK) {$name isset($_GET[name]) ? $_GET[name] : basen…