SoFunction
Updated on 2025-04-14

Custom tree structure container based on Qt implementation

In the Qt framework, although it provides many powerful container classes (such asQListQMapQTreeWidget, 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!