In the Qt framework, although it provides many powerful container classes (such asQList
, QMap
, QTreeWidget
, etc.), but lacks a general and flexible tree structure container, which directly supports multi-level data management. To meet these needs, this paper designs and implements a reusable custom tree structure container and discusses its application in different projects.
1. Background and motivation
Tree structure is a common form of data organization in software development and is often used in the following scenarios:
- Multi-level file manager: tree display of folders and files.
- Hierarchical relationship management: such as company organizational structure and task dependency.
- Data processing and classification: such as attribute classification, rule tree, etc.
However, there is a lack of a direct tree structure container in Qt (QTreeWidget is a UI component, and QAbstractItemModel tends toward data views). Therefore, we implement a flexible, scalable universal tree structure container that supports:
- Dynamically add and delete nodes.
- Attach data to the node.
- Data filtering and search.
- Clear tree structure printing and debugging.
2. Core design and implementation
2.1 Overview of Class Design
This tree container contains two core classes:
TreeNode:
- Represents a single node of the tree.
- Includes node name, parent node pointer, child node list, and node data.
- Supports basic operations such as node addition, deletion, data setting and clearing.
Tree:
- The logic that manages the entire tree.
- Provides global node operation interfaces, such as adding and deleting nodes, filtering node data, printing tree structure, etc.
2.2 TreeNode class implementation
TreeNode is the core of the tree structure, responsible for managing the hierarchical relationships and data storage of nodes. Here is its key code logic:
class TreeNode { public: explicit TreeNode(const QString& name, TreeNode* parent = nullptr); ~TreeNode(); // Add child nodes TreeNode* addChild(const QString& name); // Remove child nodes bool removeChild(TreeNode* child); // Set and clear node data void setData(const QVariant& data); void clearData(); // Get node information QVariant getData() const; const QList<TreeNode*>& getChildren() const; QString getName() const; TreeNode* getParent() const; };
Main functions:
- addChild and removeChild implement dynamic structural adjustment of the tree.
- setData and clearData support flexible node data management.
- Provides access interfaces to parent-child relationships and data.
2.3 Tree class implementation
Tree is a management class for tree containers. Its design goals are:
- Provides user-friendly interfaces to hide the internal operations of tree nodes.
- Supports global addition, deletion, modification and search functions.
The following are some interface descriptions of the Tree class:
class Tree { public: Tree(); ~Tree(); // Node operation TreeNode* addNode(TreeNode* parent, const QString& name); bool removeNode(TreeNode* node); // Data operation void setNodeData(TreeNode* node, const QVariant& data); QVariant getNodeData(TreeNode* node) const; void clearNodeData(TreeNode* node); // Data filtering and tree printing QList<TreeNode*> filterNodes(const QString& keyword) const; void printTree() const; };
Main functions:
- addNode: dynamically add nodes, supports adding nodes to the root node by default.
- ilterNodes: Find nodes containing specific data by keywords.
- printTree: Print the tree structure in a hierarchical indentation format for easy debugging.
2.4 Call Example
Here is a sample code using Tree and TreeNode:
int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); // Create a tree container Tree tree; // Add node TreeNode* root = (nullptr, tc("Root Node")); TreeNode* nodeA = (root, tc("Node A")); TreeNode* nodeB = (root, tc("Node B")); TreeNode* nodeC = (nodeA, tc("Node C")); // Set node data (nodeA, tc("Temperature is too high")); (nodeB, tc("normal")); (nodeC, tc("Super low pressure")); // Print tree structure (); // Filter nodes containing "temperature" QList<TreeNode*> filteredNodes = (tc("temperature")); qDebug() << tc("Filter results:"); for (TreeNode* node : filteredNodes) { qDebug() << node->getName() << ":" << node->getData().toString(); } return (); }
Running results:
Root node ()
Node A (Temperature is too high)
Node C (pressure is too low)
Node B (normal)
Filter results:
"Node A": "Temperature is too high"
3. Applicable scenario analysis
The flexibility of this tree container makes it suitable for a variety of scenarios, including but not limited to the following:
File Manager:
- Manage folders and files in a hierarchy.
- Node data can store meta information (such as paths, sizes) of files.
Organizational Structure Management:
- Used to display the company's organizational structure (such as departments, employees).
- Node data can attach employee information.
Rule engine or decision tree:
- Used to implement conditional matching rules.
- Node stores rule conditions and results.
Dynamic data classification:
- Implement functions similar to tag classification.
- Supports real-time addition and deletion of nodes.
Debugging tools:
Used to display internal data relationships in complex systems.
4. Advantages and improvement directions
4.1 Advantages
Simple and easy to use:
- Interface friendly, hiding complex internal operations.
- Provides clear error prompts and default behavior.
High scalability:
New features can be added easily, such as node sorting, custom filtering conditions, etc.
flexibility:
The data type of the node is QVariant, and it supports multiple data types to store.
Cross-platform support:
Rely on the Qt framework and have good cross-platform capabilities.
4.2 Directions for improvement
Thread Safety:
Add support for concurrent operations, such as thread synchronization through QMutex.
Persistence:
Adds the serialization and deserialization functions of tree structures to store and load data.
Performance optimization:
Optimize large-scale tree operations (such as deep traversal).
Model binding:
Bind the tree container with QAbstractItemModel, supporting view classes (such as QTreeView) that are directly used for Qt.
5. Conclusion
This article introduces a custom tree structure container based on Qt implementation. Its functions cover node management, data storage, filtering and printing, and is suitable for a variety of project scenarios. Through this container, developers can manage complex hierarchical data more flexibly, and their clear interface design is also convenient for expansion and maintenance.
6. Source code
The following is the revised complete code implementation, including , , , and files. The code fixes root node initialization issues and enhances error handling and default logic.
#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(); // Add child nodes TreeNode* addChild(const QString& name); // Remove child nodes bool removeChild(TreeNode* child); // Set node data void setData(const QVariant& data); // Get node data QVariant getData() const; // Remove node data void clearData(); // Get all child nodes const QList<TreeNode*>& getChildren() const; // Get the node name QString getName() const; // Get the parent node TreeNode* getParent() const; // Check whether it is a leaf node bool isLeaf() const; private: QString nodeName; // Node name QVariant nodeData; // Node data TreeNode* parentNode; // Parent node QList<TreeNode*> childNodes; // List of child nodes}; #endif // TREENODE_H
#include "" #include <QDebug> TreeNode::TreeNode(const QString& name, TreeNode* parent) : nodeName(name), parentNode(parent) {} TreeNode::~TreeNode() { qDeleteAll(childNodes); // Delete all child nodes} TreeNode* TreeNode::addChild(const QString& name) { TreeNode* child = new TreeNode(name, this); (child); return child; } bool TreeNode::removeChild(TreeNode* child) { if (!child || !(child)) { qWarning() << tc("Removal failed: Node does not exist!"); return false; } (child); delete child; // Delete child nodes and their data return true; } void TreeNode::setData(const QVariant& data) { nodeData = data; } QVariant TreeNode::getData() const { return nodeData; } void TreeNode::clearData() { (); } 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 (); }
#ifndef TREE_H #define TREE_H #include "" class Tree { public: Tree(); ~Tree(); // Add node TreeNode* addNode(TreeNode* parent, const QString& name); // Remove node bool removeNode(TreeNode* node); // Set node data void setNodeData(TreeNode* node, const QVariant& data); // Get node data QVariant getNodeData(TreeNode* node) const; // Remove node data void clearNodeData(TreeNode* node); // Find node (by name) TreeNode* findNode(TreeNode* root, const QString& name) const; // Filter nodes (via data keywords) QList<TreeNode*> filterNodes(const QString& keyword) const; // Print tree structure void printTree() const; private: TreeNode* root; // Auxiliary recursive method void filterRecursive(TreeNode* node, const QString& keyword, QList<TreeNode*>& result) const; void printRecursive(TreeNode* node, int depth) const; }; #endif // TREE_H
#include "" #include <QDebug> Tree::Tree() { root = new TreeNode(tc("Root Node")); qDebug() << tc("Successfully initialized root node."); } Tree::~Tree() { delete root; // Automatically delete all nodes} TreeNode* Tree::addNode(TreeNode* parent, const QString& name) { if (!parent) { if (!root) { qWarning() << tc("Addition failed: The root node was not created!"); return nullptr; } qDebug() << tc("The parent node is not specified, it is added to the root node by default."); parent = root; // If the parent node is empty, it is added to the root node by default } return parent->addChild(name); } bool Tree::removeNode(TreeNode* node) { if (!node || node == root) { qWarning() << tc("Removal failed: Node is empty or root node!"); return false; } TreeNode* parent = node->getParent(); if (!parent) { qWarning() << tc("Removal failed: Parent node is empty!"); return false; } return parent->removeChild(node); } void Tree::setNodeData(TreeNode* node, const QVariant& data) { if (!node) { qWarning() << tc("Setting failed: Node is empty!"); return; } node->setData(data); } QVariant Tree::getNodeData(TreeNode* node) const { if (!node) { qWarning() << tc("Fetch failed: Node is empty!"); return QVariant(); } return node->getData(); } void Tree::clearNodeData(TreeNode* node) { if (!node) { qWarning() << tc("Clear failed: Node is empty!"); 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)) { (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); } }
#include <QCoreApplication> #include "" int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); // Create a tree Tree tree; // Create a child node and explicitly pass it into the parent node TreeNode* nodeA = (nullptr, tc("Node A")); // Add to the root node by default TreeNode* nodeB = (nodeA, tc("Node B")); TreeNode* nodeC = (nodeA, tc("Node C")); // Add data (nodeA, tc("Temperature is too high")); (nodeB, tc("normal")); (nodeC, tc("Super low pressure")); // Get data qDebug() << tc("Node A data:") << (nodeA).toString(); // Clear data (nodeC); // Filter nodes QList<TreeNode*> filteredNodes = (tc("temperature")); qDebug() << tc("Filter results:"); for (TreeNode* node : filteredNodes) { qDebug() << node->getName() << ":" << node->getData().toString(); } // Print tree structure (); return (); }
Running results
The root node was initialized successfully.
The parent node is not specified and is added to the root node by default.
The parent node is not specified and is added to the root node by default.
The parent node is not specified and is added to the root node by default.
Node A data: "Temperature is too high"
Filter results:
"Node A": "Temperature is too high"
Root node ()
Node A (Temperature is too high)
Node B (normal)
Node C ()
Function summary
This implementation supports the addition, deletion, query, filtering of tree nodes, as well as the setting, acquisition, and clearing of node data. At the same time, it includes Chinese prompts and log output, which is logically robust and easy to expand.
This is the end of this article about custom tree structure containers based on Qt implementation. For more related contents of Qt custom tree structure containers, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!