All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Pages
Building Trees

Overview

Qtilities provides a set of classes which make tree building a trivial task. This article will explore these classes and show different ways to build trees. The tree classes also provides many additional features which comes in handy and saves time. The main class doing the work behind the scenes is Qtilities::Core::Observer. However this article will not look at the details of how things work, it will just show how to use the classes and build trees without worrying how it works in the background. The Observers article is a good place to start if you are interested in the details.

Table of contents:

First Steps

Overviews

Tree building classes

Before starting to build actual trees, lets look at the set of tree building classes provided which are shown below, where classes doing work behind the scenes are colored in grey.

tree_building_classes.jpg
Tree Building Classes

The different classes has different responsibilities:

It is easy extend the set of tree items by creating objects which inherit from Qtilities::CoreGui::TreeItemBase.

Lets build some trees

Trees are constructed on the principle that the Qtilities::CoreGui::TreeNode class can contain child items (leaf nodes) or other tree nodes. This allows us to build any tree structure that we want and in this section we will discuss and show how to build trees with different requirements.

The BuildingTreeStructures example in the QtilitiesExamples project shows a few trees which you can play around with or change to code to change the trees. The screenshot below shows this example in action:

example_building_tree_structures.jpg
Building Tree Structures Example

Basic trees

Lets look at a very simple tree with a depth of 2 which looks like the one shown below.

observer_dot_graph_example_tree_dot.jpg
Basic Tree Of Depth 2

To build this tree using Qtilities is done like this:

// Create tree structure:
TreeNode* node = new TreeNode("Root Node");
node->addItem("Item 1");
node->addItem("Item 2");
node->addItem("Item 3");

We can increase the depth to 3 and also introduce an advanced concept of Qtilities trees where any item in the tree can have multiple parents. The "Shared Item" in the tree below has both "Node A" and "Node B" as its parents.

observer_dot_graph_example_complex_tree_dot.jpg
Tree With Shared Item Of Depth 3

To build this tree using Qtilities is done like this:

// Create tree structure:
TreeNode* node = new TreeNode("Root Node");
TreeNode* nodeA = node->addNode("Node A");
nodeA->addItem("Item 1");
nodeA->addItem("Item 2");
TreeItem* sharedItem = nodeA->addItem("Shared Item");
TreeNode* nodeB = node->addNode("Node B");
nodeB->attachSubject(sharedItem);
nodeB->addItem("Item 3");
nodeB->addItem("Item 4");

As you can see, building trees with Qtilities is easy. It is important to understand we are using two basic classes, Qtilities::CoreGui::TreeNode and Qtilities::CoreGui::TreeItem here. However it is possible to build trees with your own items where tree nodes are based on Qtilities::Core::Observer and your leaf items can be any QObject based class. The More advanced trees: Controlling activity and names of items under a node section of this page introduces more advanced concepts but for now we will focus on building trees on a higher level and displaying them.

For the curious reader, the tree diagrams shown above were created using Qtilities::Core::ObserverDotWriter.

Tree with categorized leafs

An important feature of a tree node is that is can group child items into categories where a category icon will automatically be assigned to category nodes. This functionality is controlled by the Qtilities::CoreGui::TreeNode::enableCategorizedDisplay() function. The first tree is a simple tree which uses this feature to group items into different categories. The advantage of the categorized approach is that it is very simple to use and we can easily construct very deep trees since we don't have to construct the category nodes manually. The disadvantage is that we don't have access to the category nodes, thus we cannot control their formatting or define the context used for items inside the category (activity or naming control for example).

tree_docs_categorized_tree.jpg
Tree With Categorized Leaf Nodes

The actual implementation is show below:

TreeNode* rootNode = new TreeNode("Root");
rootNode->enableCategorizedDisplay();
rootNode->addItem("Child 1",QtilitiesCategory("Category 1"));
rootNode->addItem("Child 2",QtilitiesCategory("Category 1"));
rootNode->addItem("Child 3",QtilitiesCategory("Category 2"));
rootNode->addItem("Child 4",QtilitiesCategory("Category 2"));
rootNode->addItem("Child 5",QtilitiesCategory("Category 2"));
TreeWidget* categorized_widget = new TreeWidget(rootNode);
categorized_widget->show();

Note that deep trees can be constructed by using different constructors of Qtilities::Core::QtilitiesCategory. For example:

rootNode->addItem("Child 1",QtilitiesCategory("Top Category::Middle Category::Bottom Category","::"));

Tree without categorized leafs

Building trees without categories is powerful but becomes tedious when a tree gets deep and the nodes have to be created manually. Lets build the same tree again, but this time without using categories. To keep the examples to follow short, the code to create and show the observer widget is not repeated again.

tree_docs_uncategorized_tree.jpg
Tree With Categorized Leaf Nodes

The actual implementation is show below:

TreeNode* rootNode = new TreeNode("Root");
TreeNode* parentNode1 = rootNode->addNode("Parent 1");
TreeNode* parentNode2 = rootNode->addNode("Parent 2");
parentNode1->addItem("Child 1");
parentNode1->addItem("Child 2");
parentNode2->addItem("Child 3");
parentNode2->addItem("Child 4");
parentNode2->addItem("Child 5");

It is clear from the above example that deep trees will take much more lines of code than the categorized approach. This approach does however allows for much more complex trees to be build. In the trees that follow this will become apparent.

Saving and loading trees using XML

A very powerful feature of Qtilities trees are that they can save any created tree to a XML file and reconstruct it at a later time. A tree can also be defined entirely in XML and then constructed in memory and shown to the user.

Lets demonstrate this using the example tree presented thus far. Lets assume we append this to the above example:

QString fileName = QApplication::applicationDirPath() + "/example_tree.xml";
// Save the tree to a file:
rootNode->saveToFile(fileName);
// Load the tree from a file:
rootNode->loadFromFile(fileName);
// Or add the node produced from a file as a child to a node:
rootNode->addNodeFromFile(fileName);

The format of the output tree is defined by Qtilities::ExportVersion and a detailed overview of the output format can be found on the Serializing Qtilities Data Types Overview page.

Formatted tree which combines categorized and uncategorized nodes

Tree nodes and items (excluding category nodes) all inherit Qtilities::CoreGui::AbstractTreeItem which provides functions to format these items. For more details on the allowed formatting, see the class documentation. It is possible to combine the two approaches described thus far. That is, we can create a tree with tree nodes using categories along with other tree nodes which does not use categories.

Lets create a formatted tree to demonstrate this.

TreeNode* nodeA = new TreeNode("Node A");
TreeNode* nodeB = nodeA->addNode("Node B");
TreeNode* nodeC = nodeA->addNode("Node C");
nodeC->enableCategorizedDisplay();
// Add some formatting to the nodes:
nodeA->setForegroundRole(QBrush(Qt::darkRed));
nodeA->setFont(QFont("Helvetica [Cronyx]",20));
nodeA->setAlignment(Qt::AlignCenter);
nodeA->setBackgroundRole(QBrush(Qt::gray));
nodeB->setForegroundRole(QBrush(Qt::darkGreen));
nodeB->setFont(QFont("Helvetica [Cronyx]",20));
nodeB->setAlignment(Qt::AlignCenter);
nodeB->setBackgroundRole(QBrush(Qt::gray));
nodeC->setForegroundRole(QBrush(Qt::darkYellow));
nodeC->setFont(QFont("Helvetica [Cronyx]",20));
nodeC->setAlignment(Qt::AlignCenter);
nodeC->setBackgroundRole(QBrush(Qt::gray));
// Create the tree items:
// Be careful to use it like this, this is just to keep the example short.
// If the addItem() call fails it returns 0.
nodeA->addItem("Item 1")->setStatusTip("Hello, I'm a status tip.");
nodeB->addItem("Item 2")->setIcon(QIcon(qti_icon_EDIT_COPY_16x16));
nodeB->addItem("Item 3")->setIcon(QIcon(qti_icon_EDIT_CLEAR_16x16));
nodeB->addItem("Item 4")->setIcon(QIcon(qti_icon_EDIT_CUT_16x16));
nodeC->addItem("Item 5")->setWhatsThis("Hello, I'm a What's This message.");
nodeC->addItem("Item 6")->setToolTip("Hello, I'm a ToolTip text");
nodeC->addItem("Item 7")->setCategory(QtilitiesCategory("Category 1"),nodeC);
// Create an observer widget with the items:
TreeWidget* tree_widget = new TreeWidget(nodeA);
tree_widget->show();

The resulting tree is shown below. Note that "Node C" contains categorized items while the other two nodes does not use categories. There is no limitation on combining different nodes. It is for example possible to attach an uncategorized node to a specific category in a categorized node.

trees_simple_example_formatted.jpg
Formatted Tree Combining Categorized and Uncategorized Nodes

More advanced trees: Controlling activity and names of items under a node

Controlling the activity and/or names of items under a node is a common requirement found in applications. The tree building classes makes this very easy. The image below shows the required tree and its implementation.

tree_docs_more_advanced_tree.jpg
More Advanced Tree: Activity and Naming Control

The actual implementation is show below:

TreeNode* rootNode = new TreeNode("Root");
TreeNode* parentNode1 = rootNode->addNode("Parent 1");
parentNode1->enableActivityControl(ObserverHints::CheckboxActivityDisplay,ObserverHints::CheckboxTriggered);
TreeNode* parentNode2 = rootNode->addNode("Parent 2");
parentNode2->enableNamingControl(ObserverHints::EditableNames,NamingPolicyFilter::ProhibitDuplicateNames);
parentNode1->addItem("Child 1");
parentNode1->addItem("Child 2");
parentNode2->addItem("Child 3");
parentNode2->addItem("Child 4");
parentNode2->addItem("Child 5");

For the above code we see that it is possible to easily define how activity and naming control must be handled for items under a tree node. This example is only the tip of the iceberg and only shows the solution to one required tree. The activity control provides many options to the user. We can for example tell parentNode1 that only one child can be active at one time, or that the activity must follow the user's selection when items underneath parentNode1 are selected. It is also possible to define custom policies for a tree node or policies can be combined. The goal of this article is not to explore these features in depth. If you are interested in all the features available to developers, the following are good places to start:

Articles

API Documentation

It would not be possible to meet the requirements of the above tree using the categorized approach. The important thing to remember when building trees is that each node defines a context for the items attached to it. This allows us to control for example activity, or names for items underneath a node. Using the categorized approach results in a node which contains all its children, does not matter in which category they are, in one context. This can be desired in specific cases and in those cases categorized nodes in a tree is the way to go.

Handy tree features

Now that we've shown how to build some example trees, we look at other features and advantages of the Qtilities tree structure.

Parent and child access functions

Any object can have multiple parents and can be attached anywhere in the tree multiple times. Thus we can get the reference of a tree item under a node and attach it to a different node. From a user's perspective the result looks the same but from a developers perspective this open the door to additional possibilities when building trees. We can get the parents or children of any item in the tree as follows:

// Get the number of parents:
int parent_count = Observer::parentCount(obj);
// Get the references of any objects' parents:
QList<Observer*> parents = Observer::parentReferences(obj);
// Get the number of children:
int child_count = node->subjectCount();
// Get the references to all children:
QList<QObject*> children = node->subjectReferences();

A limitation of this flexibility is that recursive tree structures are not allowed and this is automatically checked each time a new child is attached to a node. The Qtilities::CoreGui::TreeNode class provides many other useful functions to use. See the class documentation for more information.

Monitoring changes to the tree

A powerful feature is the ability to monitor modifications to a tree. This is done through the Qtilities::Core::Interfaces::IModificationNotifier interface. This interface allows any object in the tree to broadcast any changes to its modification state. An example of where this is useful is where a tree is part of a project. In that case we want to indicate to the user that the project must be saved as soon as the tree structure or any of the objects in the tree change.

We can check if a the example tree is modified, and change the modification state of the tree as follows:

// Check if the tree is modified
if (rootNode->isModified()){
// If it is modified, we change its modification state to false for this example:
rootNode->setModificationState(false,IModificationNotifier::NotifySubjects);
}

Tree nodes also provide a number of signals which can be monitored if you are interested in specific changes in a tree. See the Qtilities::Core::Observer class documentation for more information.

Compare trees with each other

Trees can be compared to each other, and information about a tree can be serialized to a QDataStream using the Qtilities::Core::ObserverRelationalTable class.

ObserverRelationalTable tableA(parentNode1);
// Now we can dump detailed information about the tree to the logger
tableA.dumpTableInfo();
// We can compare two trees with each other:
ObserverRelationalTable tableB(parentNode2);
if (!tableA.compare(tableB)) {
// Tables are different, which will be the case for the example tree
}

We can also export detailed information about a tree to files are read them back at a later time. See Qtilities::CoreGui::TreeNode::saveToFile() for more information.

Trees automatically monitor the lifetime of objects in it

Trees automatically monitor the lifetime of objects inside the tree. Thus if we delete any item or node in a tree the tree will automatically refresh any observer widgets displaying it.

// We can also delete all objects in the tree like this:
rootNode->deleteAll();
// If we want to remove all objects from the tree but we don't want to delete them we do this:
rootNode->detachAll();

Note that rootNode will still have its objects attached to them after the detachAll() call. It is important to avoid memory leaks when detaching objects, thus you must then manually manage the lifetime. See Object lifetimes: Managing object lifetimes using observers for more information on this topic.

Under the hood: How trees are parsed

The tree building classes presented thus far deal with trees from a user's perspective. We defined how the must look to the user and then created it. The Qtilities tree structure provides the ability to build trees with any objects. That is, we can create a tree node and attach any QObject to it. When the tree is displayed the objectName() of the attached object is used as the name of the item. We can also build trees that will never be shown to the user, for example we create a tree and define the ownerships of the items in the tree in such a way that the lifetimes of objects in the tree is managed in a specific way (think of garbage collection).

Because trees can be so generic, they are parsed in a very specific way depending on what you are doing with the tree. This section provides details of how the parsing happens inside the Qtilities classes.

Tree parsing areas

In Qtilities trees are parsed in several places and it is important to make sure parsing happens in the same way everywhere. The following list gives an overview of classes where tree parsing happens:

All of these classes support the following approaches to parsing trees.

Approach 1: Observer with child observers

This approach is the most common and easiest and was used in the examples presented in this article so far. The tree is made up of Observers with other observers underneath it, with other observers underneath these observers etc.



Qtilities : Reference Documentation Back to top Copyright © 2009-2013, Jaco Naudé