Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF - Good Way to take a list to a Tree

I have a list that looks like this:

Base/Level1/Item1
Base/Level1/Item2
Base/Level1/Sub1/Item1
Base/Level2/Item1
Base/Level3/Sub1/Item1

I would like an easy way put that into a ListView. (Ie similar to this)

Base
  |
  +->Level1
  |    |
  |    +=Item1
  |    +=Item2
  |    |
  |    +->Sub1
  |        |
  |        +=Item1
  |
  +->Level2
  |    |
  |    +=Item1

  |
  +->Level3
       |
       +->Sub1
           |
           +=Item1

Is there an established way to make this kind of conversion or do I just need to roll my own parser?

(In case it may be relevant the real items in my code are TFS Iteration Paths.)

like image 861
Vaccano Avatar asked Jan 20 '10 23:01

Vaccano


3 Answers

This will take your list of strings and turn it into a tree suitable for viewing with TreeView as you described:

public IList BuildTree(IEnumerable<string> strings)
{
  return
    from s in strings
    let split = s.Split("/")
    group s by s.Split("/")[0] into g  // Group by first component (before /)
    select new
    {
      Name = g.Key,
      Children = BuildTree(            // Recursively build children
        from s in grp
        where s.Length > g.Key.Length+1
        select s.Substring(g.Key.Length+1)) // Select remaining components
    };
}

This will return a tree of anonymous types, each containing a Name property and a Children property. This can be bound directly to the TreeView by specifying a HierarchicalDataTemplate with ItemsSource="{Binding Children}" and content consisting of a <TextBlock Text="{Binding Name}"> or similar.

Alternatively you could define a tree node class in code if you want additional members or semantics. For example, given this node class:

public class Node 
{
  public string Name { get; set; } 
  public List<Node> Children { get; set; } 
}

your BuildTree function would be slightly different:

public List<Node> BuildTree(IEnumerable<string> strings)
{
  return (
    from s in strings
    let split = s.Split("/")
    group s by s.Split("/")[0] into g  // Group by first component (before /)
    select new Node
    {
      Value = g.Key,
      Children = BuildTree(            // Recursively build children
        from s in grp
        where s.Length > g.Key.Length+1
        select s.Substring(g.Key.Length+1)) // Select remaining components
    }
    ).ToList();
}

Again this can be bound directly using a HierarchicalDataTemplate. I generally use the first solution (anonymous types) unless I want to do something special with the tree nodes.

like image 121
Ray Burns Avatar answered Nov 16 '22 10:11

Ray Burns


The WPF TreeView can display hierarchical data using HierarchicalDataTemplates. However, currently your data is flat, so you will have to transform it to a hierarchical data structure. There is no built-in way to do that for you...

For instance, you can create a class like that :

class Node
{
    public string Name { get; set; }
    public List<Node> Children { get; set; }
}

Parse your flat data into a list of Node objects with subnodes , and create a TreeView with the following HierarchicalDataTemplate in the resources :

<TreeView ItemsSource="{Binding ListOfNodes}">
  <TreeView.Resources>
    <HierarchicalDataTemplate DataType="{x:Type local:Node}" ItemsSource="{Binding Children}">
      <TextBlock Text="{Binding Name}" />
    </HierarchicalDataTemplate>
  </TreeView.Resources>
</TreeView>

The tree nodes will be automatically generated based on your data. If you need different classes for different levels of the hierarchy, create a different HierarchicalDataTemplate for each class

like image 23
Thomas Levesque Avatar answered Nov 16 '22 08:11

Thomas Levesque


A more generic implementation could be this one. Imagine a Node class defined as:

public class Node<TItem, TKey>
{
    public TKey Key { get; set; }
    public int Level { get; set; }
    public IEnumerable<TItem> Data { get; set; }
    public List<Node<TItem, TKey>> Children { get; set; }
}

and two generic IEnumerable<T> extension methods:

public static List<Node<TItem, TKey>> ToTree<TItem, TKey>(this IEnumerable<TItem> list, params Func<TItem, TKey>[] keySelectors)
{
    return list.ToTree(0, keySelectors);
}

public static List<Node<TItem, TKey>> ToTree<TItem, TKey>(this IEnumerable<TItem> list, int nestingLevel, params Func<TItem, TKey>[] keySelectors)
{
    Stack<Func<TItem, TKey>> stackSelectors = new Stack<Func<TItem, TKey>>(keySelectors.Reverse());
    if (stackSelectors.Any())
    {
        return list
            .GroupBy(stackSelectors.Pop())
            .Select(x => new Node<TItem, TKey>()
            {
                Key = x.Key,
                Level = nestingLevel,
                Data = x.ToList(),
                Children = x.ToList().ToTree(nestingLevel + 1, stackSelectors.ToArray())
            })
            .ToList();
        }
        else
        {
            return null;
        }

You can use these methods to aggregate a flat list of user objects to a tree, with an arbitrary aggregation level and a more elegant syntax. The only limitation is that the aggregation keys must be of the same type.

Example:

class A
{
    public int a { get;set; }
    public int b { get;set; }
    public int c { get;set; }
    public int d { get;set; }
    public string s { get;set; }

    public A(int _a, int _b, int _c, int _d, string _s)
    {
        a = _a;
        b = _b;
        c = _c;
        d = _d;
        s = _s;     
    }
}

void Main()
{
    A[] ls = {
        new A(0,2,1,10,"one"),
        new A(0,1,1,11,"two"),
        new A(0,0,2,12,"three"),
        new A(0,2,2,13,"four"),
        new A(0,0,3,14,"five"),
        new A(1,0,3,15,"six"),
        new A(1,1,4,16,"se7en"),
        new A(1,0,4,17,"eight"),
        new A(1,1,5,18,"nine"),
        new A(1,2,5,19,"dunno")
    };

    var tree = ls.ToTree(x => x.a, x => x.b, x => x.c, x => x.d);

}

Notes: This implementation is not a "real" tree, since there is no single root node, but you can implement a Tree<TItem, TKey> wrapper class quite easily.

HTH

like image 1
silentman.it Avatar answered Nov 16 '22 09:11

silentman.it