У многих программистов существует неудержимое желание организовывать любые объекты в иерархии, даже когда иерархия фактически не существует. При разработке интерфейса это проявляется в использовании контролов типа TreeView.
Основной недостаток TreeView в WPF заключается в том что не существует тривиальных путей подружить этот контрол с паттерном MVVM.
Привязать данные к элементам дерева достаточно просто. Для этого есть HierarchicalDataTemplate. В первом приближении ViewModel для элементов дерева будет выглядеть так:
1: public class TreeViewItemModel: ViewModelBase
2: {
3: string _name;
4:
5: public TreeViewItemModel()
6: {
7: Children = new ObservableCollection<TreeViewItemModel>();
8: }
9:
10: public string Name
11: {
12: get { return _name; }
13: set { _name = value; OnPropertyChanged("Name"); }
14: }
15:
16: public ObservableCollection<TreeViewItemModel> Children { get; private set; }
17: }
ViewModelBase – класс из MVVM Toolkit.
В XAML пишем простой темплейт
1: <TreeView.ItemTemplate>
2: <HierarchicalDataTemplate ItemsSource="{Binding Children}">
3: <TextBlock Text="{Binding Name}" />
4: </HierarchicalDataTemplate>
5: </TreeView.ItemTemplate>
Теперь можно в качестве ItemsSource для TreeView указать любую коллекцию TreeViewItemModel.
Обеспечить обратную связь – от TreeView к модели представления – гораздо сложнее. Свойство TreeView.SelectedValue не имеет сеттера, поэтому привязать его в XAML к модели представления не получится, надо искать обходные пути.
Можно привязать свойство TreeViewItem.IsSelected к модели вида, но делать это надо в стиле дерева, а не в DataTemplate.
1: <TreeView.ItemContainerStyle>
2: <Style TargetType="{x:Type TreeViewItem}">
3: <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
4: </Style>
5: </TreeView.ItemContainerStyle>
В класс TreeViewItemModel надо добавить следующее свойство:
1: bool _isSelected;
2:
3: public bool IsSelected
4: {
5: get { return _isSelected; }
6: set { _isSelected = value; OnPropertyChanged("IsSelected"); }
7: }
Теперь можно создать модель представления всего дерева:
1: public class TreeViewModel : ViewModelBase
2: {
3: public TreeViewModel()
4: {
5: TopLevelItems = new ObservableCollection<TreeViewItemModel>();
6: }
7:
8: public ObservableCollection<TreeViewItemModel> TopLevelItems { get; private set; }
9:
10: public TreeViewItemModel SelectedItem
11: {
12: get
13: {
14: return TopLevelItems
15: .Traverse(item => item.Children)
16: .FirstOrDefault(m => m.IsSelected);
17: }
18: }
19: }
1: public class TreeViewModel : ViewModelBase
2: {
3: PropertyChangedEventHandler _propertyChangedHandler;
4: NotifyCollectionChangedEventHandler _collectionChangedhandler;
5:
6: public TreeViewModel()
7: {
8: TopLevelItems = new ObservableCollection<TreeViewItemModel>();
9: _propertyChangedHandler = new PropertyChangedEventHandler(item_PropertyChanged);
10: _collectionChangedhandler = new NotifyCollectionChangedEventHandler(items_CollectionChanged);
11: TopLevelItems.CollectionChanged += _collectionChangedhandler;
12: }
13:
14: public ObservableCollection<TreeViewItemModel> TopLevelItems { get; private set; }
15:
16: public TreeViewItemModel SelectedItem
17: {
18: get
19: {
20: return TopLevelItems
21: .Traverse(item => item.Children)
22: .FirstOrDefault(m => m.IsSelected);
23: }
24: }
25:
26: void subscribePropertyChanged(TreeViewItemModel item)
27: {
28: item.PropertyChanged += _propertyChangedHandler;
29: item.Children.CollectionChanged += _collectionChangedhandler;
30: foreach (var subitem in item.Children)
31: {
32: subscribePropertyChanged(subitem);
33: }
34: }
35:
36: void unsubscribePropertyChanged(TreeViewItemModel item)
37: {
38: foreach (var subitem in item.Children)
39: {
40: unsubscribePropertyChanged(subitem);
41: }
42: item.Children.CollectionChanged -= _collectionChangedhandler;
43: item.PropertyChanged -= _propertyChangedHandler;
44: }
45:
46:
47: void items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
48: {
49: if (e.OldItems != null)
50: {
51: foreach (TreeViewItemModel item in e.OldItems)
52: {
53: unsubscribePropertyChanged(item);
54: }
55: }
56:
57: if (e.NewItems != null)
58: {
59: foreach (TreeViewItemModel item in e.NewItems)
60: {
61: subscribePropertyChanged(item);
62: }
63: }
64: }
65:
66: void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
67: {
68: if (e.PropertyName == "IsSelected")
69: {
70: OnPropertyChanged("SelectedItem");
71: }
72: }
73: }
XAML код элемента TreeView:
1: <TreeView ItemsSource="{Binding TopLevelItems}">
2: <TreeView.ItemContainerStyle>
3: <Style TargetType="{x:Type TreeViewItem}">
4: <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
5: </Style>
6: </TreeView.ItemContainerStyle>
7:
8: <TreeView.ItemTemplate>
9: <HierarchicalDataTemplate ItemsSource="{Binding Children}">
10: <!--Здесь можно задать любой шаблон для элемента-->
11: <TextBlock Text="{Binding Name}" />
12: </HierarchicalDataTemplate>
13: </TreeView.ItemTemplate>
14: </TreeView>
PS. TreeViewItem также имеет свойство IsExpanded, которое очень помогает если надо сделать отложенную загрузку элементов дерева.