使用TreeView
上次说到了TreeView具有递归的结构,而在WPF中是数据驱动界面,这就要求我们的数据源也具有一定的递归结构
数据的准备
按照数据源是否可以继续展开下去,我们将数据源定义为两个ViewModel:TreeNodeVM
,TreeLeafVM
,其中TreeNodeVM
作为树的中间节点出现,其下可继续出现中间节点和叶子节点,TreeLeafVM
作为树的叶子节点出现,不能再出现其他节点。TreeNodeVM
中的子节点的个数是不定的,所以应该由集合表示。而TreeNodeVM
的子节点可能是TreeNodeVM
和TreeLeafVM
的任意一种,这就需要他们有一个公共的父类。这给我们如下的数据定义:
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>
|
大功告成
源代码参见
https://github.com/Verrickt/Melchior-Sample/tree/master/FluentTreeView