FluentTreeView Part.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

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计