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 | +->Le开发者_如何学编程vel1 | | | +=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.)
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.
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
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
精彩评论