Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Quick and Dirty TreeView

0.00/5 (No votes)
25 Mar 2013 1  
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 strings 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here