Quick and Dirty TreeView






4.25/5 (8 votes)
Design to create a simple tree view that can be copy and pasted
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.