Introduction
There are times when it would be convenient to have an easy way to display hierarchical data as tree. This can be done using tree view, but sometimes just a text version is acceptable, with the advantages of being easy to handle the data, not having to carry a lot of objects and values around, and for copy and pasting. It is not a hard problem to solve; it just needs to be thought about a little while.
Implementation
The approach used is a recursion, with a List<string>
being used to capture a tree. When assembling the tree, each subtree is created as a List<string>
and combined into a List<List<string>>
. The first element in the List<string>
is the node that needs be attached to the base node of the tree. When assembling the nodes of a node in the tree, the first List<string>
in the List<List<string>>
is the parent, and the children are represented by the following nodes. The following code will put a horizontal line made up of the “|” character from the parent for each node, ending at the start of the last node. Where the base node starts for each child node, a “+” character with a vertical line right (“-“) will be inserted:
private static List<string> AppendTree(List<list<string>> limbs)
{
var list = new List<string>();
if (limbs.Any())
{
list.Add(limbs.First().First());
for (int i = 1; i < limbs.First().Count; i++)
list.Add(" | " + limbs.First()[i]);
}
for (int i = 1; i < limbs.Count - 1; i++)
{
if (limbs[i].Count > 0)
{
list.Add(" +- " + limbs[i][0]);
for (int j = 1; j < limbs[i].Count; j++)
list.Add(" | " + limbs[i][j]);
}
}
if (limbs.Last().Count > 1)
{
list.Add(" +- " + limbs.Last()[0]);
for (int j = 1; j < limbs.Last().Count; j++)
list.Add(" " + limbs.Last()[j]);
}
return list;
}
What is finally returned is a List<string>
.
I also have several helper methods, one will take a list of arguments and put them into a comma delimited string enclosed in brackets, and add a spacer line:
private static List<string> Enclose(params object[] o)
{
var s = o.Select(i => i.ToString());
var str = string.Join(", ", s);
return new List<string> { string.Format("< {0} >", str), string.Empty };
}
A second method will take a string
and an IEnumerable<T>
and convert it so something similar, except this allows quick conversion of list data to a single line:
private static List<string> EncloseList<t>(string s, IEnumerable<t> o)
{
return Enclose(s, string.Join(", ", o.Select(i => i.ToString())));
}
A fourth method is just a simple factory for creating a List<List<string>
> from a single List<string>
which becomes the first element of the new list. It just cleans up creating the conversion methods:
private static List<list<string>> StartList(List<string> list)
{
return new List<list<string>> { list };
}
One final helper method is used after the objects to display in the tree are processed. It converts the List<string>
to a single string
with carriage returns between the string
s in the List<string>
:
public static string Convert(IEnumerable<string> values)
{
var sb = new StringBuilder();
foreach (var value in values)
{
sb.AppendLine(" " + value);
}
return sb.ToString();
}
To make life simple, the method Convert
is overloaded. Each Convert
method takes a single argument and returns a List<string>
. The simplest convert
would just return the result of a call to the Enclose
method:
public static List<string> Convert(Class4 values)
{
return Enclose("Class 4", values.Id, values.Name, values.Property1);
}
To create a class to display a class and its children as a limb:
public static List<string> Convert(Class1 values)
{
var result = StartList(Enclose("Class 1", values.Id, values.Name,
values.Property1));
if (values.Children != null)
result.AddRange(values.Children.Select(Convert));
return AppendTree(result);
}
Once all the methods are set up to display a tree for an object and its children, two method calls are required, and it is the same for all classes for which Convert
methods have been created:
var str = ConvertToTreeViewString.Convert(
ConvertToTreeViewString.Convert(data));
The result would look something like this:
< Class 1, 1, name1, property1a >
|
+- < Class 2, 1, name2a, property2a >
| |
| +- < Property4, a, b, c, d >
| |
| +- < Class 2, 2, name3a, property3a >
| | |
| | +- < Property4, 1, 2, 3, 4 >
| |
| +- < Class 2, 3, name3b, property3d >
| |
| +- < Property4, 5, 6, 7, 8 >
| |
| +- < Class 4, 4, name4a, property4a >
| |
| +- < Class 4, 5, name4b, property4d >
|
+- < Class 2, 6, name2b, property2d >
|
+- < Property4, a, b, c, d >
|
+- < Class 2, 6, name3c, property3g >
| |
| +- < Property4, 7, 2, 3, 4 >
|
+- < Class 2, 1, name3d, property3j >
|
+- < Property4, 8, 2, 3, 4 >
If you have control of your objects, you can simplify things by having the object information to be displayed in the tree returned in the ToString()
method. I did not have that luxury since the objects in the tree were generated objects. Also I did not have a standard name for the children of an object for the same reason. I made a little bit of use of generics in my code when children were displayed as list, but that was it. A different application may allow use of generics to reduce the number of convert methods required.