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

1. 背景与动机
树结构在软件开发中是常见的数据组织形式,常用于以下场景:
- 多层级文件管理器:文件夹与文件的树形展示。
- 层次化关系管理:如公司组织结构、任务依赖关系。
- 数据处理与分类:如属性分类、规则树等。
然而,Qt 中缺少直接的树结构容器(QTreeWidget 是 UI 组件,QAbstractItemModel 偏向于数据视图)。因此,我们实现了一个灵活、可扩展的 通用树结构容器,支持:
- 动态添加和删除节点。
- 为节点附加数据。
- 数据筛选与查找。
- 清晰的树形结构打印与调试。
2. 核心设计与实现
2.1 类设计概览
该树容器包含两个核心类:
-
TreeNode:
- 表示树的单个节点。
- 包括节点名称、父节点指针、子节点列表、节点数据。
- 支持节点添加、删除、数据设置与清除等基本操作。
-
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;
};
主要功能:
addChild和removeChild实现树的动态结构调整。setData和clearData支持灵活的节点数据管理。- 提供对父子关系和数据的访问接口。
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 调用示例
以下是使用 Tree 和 TreeNode 的示例代码:
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. 适用场景分析
该树容器的灵活性使其适用于多种场景,包括但不限于以下项目:
-
文件管理器:
- 以层次结构管理文件夹和文件。
- 节点数据可存储文件的元信息(如路径、大小)。
-
组织结构管理:
- 用于显示公司组织架构(如部门、员工)。
- 节点数据可附加员工信息。
-
规则引擎或决策树:
- 用于实现条件匹配规则。
- 节点存储规则条件与结果。
-
动态数据分类:
- 实现类似标签分类的功能。
- 支持实时增删节点。
-
调试工具:
- 用于显示复杂系统中的内部数据关系。
4. 优势与改进方向
4.1 优势
-
简单易用:
- 接口友好,隐藏复杂的内部操作。
- 提供清晰的错误提示和默认行为。
-
高扩展性:
- 可以轻松添加新功能,如节点排序、自定义过滤条件等。
-
灵活性:
- 节点的数据类型为
QVariant,支持多种数据类型存储。
- 节点的数据类型为
-
跨平台支持:
- 依赖 Qt 框架,具备良好的跨平台能力。
4.2 改进方向
-
线程安全:
- 增加对并发操作的支持,例如通过
QMutex实现线程同步。
- 增加对并发操作的支持,例如通过
-
持久化:
- 增加树结构的序列化和反序列化功能,用于存储和加载数据。
-
性能优化:
- 对大规模树操作(如深度遍历)进行优化。
-
模型绑定:
- 将树容器与
QAbstractItemModel绑定,支持直接用于 Qt 的视图类(如QTreeView)。
- 将树容器与
5. 结语
本文介绍了一个基于 Qt 实现的自定义树结构容器,其功能涵盖了节点管理、数据存储、筛选与打印等操作,适用于多种项目场景。通过该容器,开发者可以更加灵活地管理复杂的层次化数据,同时其清晰的接口设计也便于扩展与维护。
6. 源码
以下是修正后的完整代码实现,包含 TreeNode.h、TreeNode.cpp、Tree.h、Tree.cpp 和 main.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 ()
功能总结
该实现支持树节点的 添加、删除、查询、过滤,以及节点数据的 设置、获取、清除。同时,包含中文提示与日志输出,逻辑健壮且易于扩展。
相关文章:
基于Qt实现的自定义树结构容器:设计与应用
在Qt框架中,尽管其提供了许多强大的容器类(如 QList, QMap, QTreeWidget 等),但缺少一个通用的、灵活的树结构容器,直接支持多层级数据管理。为了满足这些需求,本文设计并实现了一个可复用的自定义树结构容…...
网络命令Linux
目录 一,Linux 二,CMD 一,Linux ping www.baidu.com 测试联网 -c 2 次数,ping几次 , -i 间隔 -W timeout 超时时间,等待响应的超时时间 ss -lntup |grep -w 22 netstat -lntup |grep -w 22 lsof -i:22 ls…...
简单的Activiti Modoler 流程在线编辑器
简单的Activiti Modoler 流程在线编辑器 1.需求 我们公司使用的流程是activiti5.22.0,版本有些老了,然后使用的编辑器都是eclipse的流程编辑器插件,每次编辑流程需要打开eclipse进行编辑,然后再导入到项目里面,不是特…...
【NodeJS】Express写接口的整体流程
前提条件 开发 Node.js,首先就必须要安装 Node.js。推荐使用 nvm,它可以随意切换 node 版本。下载 nvm,具体可以看本人另一篇文章:nvm的作用、下载、使用、以及Mac使用时遇到commond not found:nvm如何解决。 nvm官方࿱…...
Oracle 锁表的解决方法及避免锁表问题的最佳实践
背景介绍 在 Oracle 数据库中,锁表或锁超时相信大家都不陌生,是一个常见的问题,尤其是在执行 DML(数据操作语言)语句时。当一个会话对表或行进行锁定但未提交事务时,其他会话可能会因为等待锁资源而出现超…...
关于 vue+element 日期时间选择器 限制只能选当天以及30天之前的日期
业务需求,需要实现选择当天以及30天之前的日期,于是我想到的是利用picker-options去限制可选范围 代码如下 <el-date-pickerv-model"searchData.acceptTime"type"datetimerange"value-format"yyyy-MM-dd hh:mm:ss"styl…...
租辆酷车小程序开发(二)—— 接入微服务GRPC
vscode中golang的配置 设置依赖管理 go env -w GO111MODULEon go env -w GOPROXYhttps://goproxy.cn,direct GO111MODULEauto 在$GOPATH/src 外面且根目录有go.mod 文件时,开启模块支持 GO111MODULEoff 无模块支持,go会从GOPATH 和 vendor 文件夹寻找包…...
如何在 Ubuntu 22.04 上安装 Metabase 数据可视化分析工具
简介 Metabase 提供了一个简单易用的界面,让你能够轻松地对数据进行探索和分析。通过本文的指导,你将能够在 Ubuntu 22.04 系统上安装并配置 Metabase,并通过 Nginx 进行反向代理以提高安全性。本教程假设你已经拥有了一个非 root 用户&…...
MySQL 用户与权限管理
MySQL 是一种广泛使用的关系型数据库管理系统,支持多用户访问和权限控制。在多用户环境下,数据库安全至关重要,而用户和权限管理是数据库管理中最基础也是最重要的一部分。通过合理地创建和管理用户、分配和管理权限、使用角色权限,可以有效地保护数据库,确保数据的安全性…...
【Web前端】如何构建简单HTML表单?
HTML 表单是 Web 开发中非常重要的组成部分。它们是与用户交互的主要方式,能够收集用户输入的数据。表单的灵活性使它们成为 HTML 中最复杂的结构之一,但若使用正确的结构和元素,可以确保其可用性和无障碍性。 表单的基本结构 HTML 表单使用…...
Spring Boot 3 集成 Spring Security(3)数据管理
文章目录 准备工作新建项目引入MyBatis-Plus依赖创建表结构生成基础代码 逻辑实现application.yml配置SecurityConfig 配置自定义 UserDetailsService创建测试 启动测试 在前面的文章中我们介绍了 《Spring Boot 3 集成 Spring Security(1)认证》和 《…...
书生大模型实战营第四期-入门岛-4. maas课程任务
书生大模型实战营第四期-入门岛-4. maas课程任务 任务一、模型下载 任务内容 使用Hugging Face平台、魔搭社区平台(可选)和魔乐社区平台(可选)下载文档中提到的模型(至少需要下载config.json文件、model.safetensor…...
Spring ApplicationListener监听
【JavaWeb】Spring ApplicationListener-CSDN博客 ApplicationEvent以及Listener是Spring为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。事件发布…...
K8s调度器扩展(scheduler)
1.K8S调度器 筛选插件扩展 为了熟悉 K8S调度器扩展步骤,目前只修改 筛选 插件 准备环境(到GitHub直接下载压缩包,然后解压,解压要在Linux系统下完成) 2. 编写调度器插件代码 在 Kubernetes 源代码目录下编写调度插件…...
IntelliJ IDEA 中,自动导包功能
在 IntelliJ IDEA 中,自动导包功能可以极大地提高开发效率,减少手动导入包所带来的繁琐和错误。以下是如何在 IntelliJ IDEA 中设置和使用自动导包功能的详细步骤: 一、设置自动导包 打开 IntelliJ IDEA: 启动 IntelliJ IDEA 并打…...
Spring事务笔记
目录 1.Spring 编程式事务 2.Transactional 3.事务隔离级别 4.Spring 事务传播机制 什么是事务? 事务是⼀组操作的集合, 是⼀个不可分割的操作. 事务会把所有的操作作为⼀个整体, ⼀起向数据库提交或者是撤销操作请求. 所以这组操作要么同时成 功, 要么同时失败 1.Spri…...
SQLite 管理工具 SQLiteStudio 3.4.5 发布
SQLiteStudio 3.4.5 版本现已发布,它带来了大量的 bug 修复,并增加了一些小功能。SQLiteStudio 是一个跨平台的 SQLite 数据库的管理工具。 具体更新内容包括: 现在可以使用 Collations Editor 窗口在数据库中注册 Extension-based collatio…...
QT 实现组织树状图
1.实现效果 在Qt中使用QGraphicsItem和QGraphicsScene实现树状图,你需要创建自定义的QGraphicsItem类来表示树的节点,并管理它们的位置和连接,以下是实现效果图。 2.实现思路 可以看见,上图所示,我们需要自定义连线类和节点类。 每个节点类Node,需要绘制矩形框体文字…...
go-学习
文章目录 简介标识符字符串的拼接,关键字数据类型声明变量常量算术运算符关系运算符逻辑运算符位运算赋值运算符其他运算符 简介 Go 语言的基础组成有以下几个部分: 1.包声明 2.引入包 3.函数 4.变量 5.语句 & 表达式 6.注释 package main import &q…...
【面试分享】主流编程语言的内存回收机制及其优缺点
以下是几种主流编程语言的内存回收机制及其优缺点: 一、Java 内存回收机制: Java 使用自动内存管理,主要通过垃圾回收器(Garbage Collector,GC)来回收不再被使用的对象所占用的内存。Java 的垃圾回收器会定…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
算法岗面试经验分享-大模型篇
文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer (1)资源 论文&a…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
