Reputation: 82301
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.)
Upvotes: 5
Views: 2926
Reputation: 361
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
Upvotes: 1
Reputation: 62909
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.
Upvotes: 5
Reputation: 292405
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
Upvotes: 2