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

devtools/2024/11/29 8:46:14/

在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.ppmy.cn/devtools/137878.html

相关文章

Opencv+ROS实现摄像头读取处理画面信息

一、工具 ubuntu18.04 ROSopencv2 编译器&#xff1a;Visual Studio Code 二、原理 图像信息 ROS数据形式&#xff1a;sensor_msgs::Image OpenCV数据形式&#xff1a;cv:Mat 通过cv_bridge()函数进行ROS向opencv转换 cv_bridge是在ROS图像消息和OpenCV图像之间进行转…

Spring Events 最新详解(spring4.2前后变化)

事件驱动设计模式&#xff0c;也可能通过Spring来实现。 围绕事件的三个角色&#xff1a; 事件&#xff08;Event&#xff09;事件发布者&#xff08;Publisher&#xff09;事件监听者&#xff08;Listener&#xff09; 文章内容&#xff1a; Spring Event.jpg 1. Demo-01:…

15 go语言(golang) - 并发编程goroutine原理及数据安全

底层原理 Go 的 goroutine 是一种轻量级的线程实现&#xff0c;允许我们在程序中并发地执行函数。与传统的操作系统线程相比&#xff0c;goroutine 更加高效和易于使用。 轻量级调度 用户态调度&#xff1a;Go 运行时提供了自己的调度器&#xff0c;这意味着 goroutine 的创建…

C++:探索哈希表秘密之哈希桶实现哈希

文章目录 前言一、链地址法概念二、哈希表扩容三、哈希桶插入逻辑四、析构函数五、删除逻辑六、查找七、链地址法代码实现总结 前言 前面我们用开放定址法代码实现了哈希表&#xff1a; C&#xff1a;揭秘哈希&#xff1a;提升查找效率的终极技巧_1 对于开放定址法来说&#…

JavaScript 对象

JavaScript 对象 一、对象是什么 JavaScript 对象是一种复合数据类型&#xff0c;它将相关的数据&#xff08;属性&#xff09;和操作这些数据的方法&#xff08;函数&#xff09;组合在一起。 二、特点 属性多样性&#xff1a;可以包含各种类型的数据作为属性。方法灵活性…

自动控制原理——BliBli站_DR_CAN

自动控制 2 稳定性分析 极点在左半平面 输入为单位冲击&#xff0c;而拉普拉斯变换为1&#xff1b;因此&#xff0c;开环和闭环系统&#xff0c;研究其传递函数的稳定性就可以了 2.5_非零初始条件下的传递函数_含有初始条件的传递函数 如果一个系统的初始条件不为0&#xff0…

蓝桥杯嵌入式入门指南-按键KEY(TIM6)【3】

在bsp文件夹中新建key.c和key.h PB0 PB1 PB2 PA0设置为GPIO_input&#xff0c;模式为上拉 打开TIM 输入频率/(PSC*Counter)中断频率 设置为1ms的中断 tips:一定要记得开NVIC中断 点击生成代码 将key.c添加到User文件夹 记得在all.h中添加key.h,tim.h头文件 key.c #include…

【CSS】clip-path 属性(剪裁显示区域)

文章目录 属性用法&#xff1a; 使用背景&#xff1a;遇到这样一个需求&#xff0c;嵌入一个网页到系统&#xff0c;但是不需要他顶部的导航栏&#xff0c;这时候就可以使用clip-path 属性剪裁到顶部导航栏&#xff0c;把网页相当于照片&#xff0c;把不想要的部分剪掉就好了 使…