FluentTreeView Part.2

  1. 1. 使用TreeView
    1. 1.1. 数据的准备
    2. 1.2. 界面的准备

使用TreeView

上次说到了TreeView具有递归的结构,而在WPF中是数据驱动界面,这就要求我们的数据源也具有一定的递归结构

数据的准备

按照数据源是否可以继续展开下去,我们将数据源定义为两个ViewModel:TreeNodeVM,TreeLeafVM,其中TreeNodeVM作为树的中间节点出现,其下可继续出现中间节点和叶子节点,TreeLeafVM作为树的叶子节点出现,不能再出现其他节点。TreeNodeVM中的子节点的个数是不定的,所以应该由集合表示。而TreeNodeVM的子节点可能是TreeNodeVMTreeLeafVM的任意一种,这就需要他们有一个公共的父类。这给我们如下的数据定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
abstract class TreeVMBase : ViewModelBase
{
public abstract ObservableCollection<TreeVMBase> Items { get; set; }
private string _name;
public string Name
{
get { return _name; }
private set { Set (ref _name, value); }
}
protected static IEnumerable<TreeVMBase> Empty => Enumerable.Empty<TreeVMBase> ();
public TreeVMBase (string name)
{
Name = name;
}
}
class TreeNodeVM : TreeVMBase
{
private ObservableCollection<TreeVMBase> _items;
public override ObservableCollection<TreeVMBase> Items
{
get
{
return _items;
}
set
{
Set (ref _items, value);
}
}
public TreeNodeVM (string name, IEnumerable<TreeVMBase> items) : base (name)
{
_items = new ObservableCollection<TreeVMBase> (items??Empty);
}
}
class TreeLeafVM : TreeVMBase
{
private static readonly ObservableCollection<TreeVMBase> _items = new ObservableCollection<TreeVMBase> ();
public override ObservableCollection<TreeVMBase> Items
{
get
{
return _items;
}
set { }
}
public TreeLeafVM (string name) : base (name) { }
}

我们的MainViewModel需要给TreeView提供数据源。上一节里提到,每一个TreeViewItem都是一棵树的根节点。而我们的TreeView,作为最顶级TreeViewItem的直接父节点,应该是森林。TreeView的ItemsSource的类型应该是IEnumerable<TreeVMBase>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class MainViewModel : ViewModelBase
{
private ObservableCollection<TreeVMBase> _treeViewBase;
public ObservableCollection<TreeVMBase> TreeViewBase
{
get { return _treeViewBase; }
set { Set (ref _treeViewBase, value); }
}
public MainViewModel ()
{
var leafs = new []
{
new TreeLeafVM ("Leaf1"),
new TreeLeafVM ("Leaf2"),
new TreeLeafVM ("Leaf3")
};
var nodes = new []
{
new TreeNodeVM ("Node1", null),
new TreeNodeVM ("Node2", leafs),
new TreeNodeVM ("Node3", leafs.Skip (1).Take(1))
};
TreeViewBase = new ObservableCollection<TreeVMBase> (new []
{
new TreeNodeVM ("Root1", nodes),
new TreeNodeVM ("Root2", nodes)
});
}
}

界面的准备

相比有继承关系的数据,界面这边就简单一些了。界面的主要问题是,需要一个办法来告诉TreeViewItem应该如何呈现数据,应该数据上下文的哪一个属性上寻找应该生成孩子的数据源,以及递归地告诉自己的孩子如何呈现数据,如何生成孩子的孩子。

DataTemplate已经能告诉TreeViewItem如何呈现数据,关键在于如何寻找生成孩子的数据源。这个问题的答案就藏在HierarchicalDataTemplate里。Hierarchical有层级的意思,它比DataTemplate多的一个属性叫做ItemsSource。把ItemsSource设置为TreeVMBase的抽象属性Items,TreeViewItem就会自动地在数据上下文上寻找Items属性,按照属性值生成新的TreeViewItem,并且把这个过程递归地进行下去。

1
2
3
4
5
6
7
8
9
<TreeView ItemsSource="{Binding TreeViewBase}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<Grid>
<TextBlock Text="{Binding Name}"></TextBlock>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>

大功告成

Normal tree view


源代码参见
https://github.com/Verrickt/Melchior-Sample/tree/master/FluentTreeView