[Silverlight入门系列]使用MVVM模式(7):ViewModel的INotifyPropertyChanged接口实现
本文说说ViewModel的这个INotifyPropertyChanged接口可以用来做啥?
举例1:我有个TabControl,里面放了很多View,每个由ViewModel控制,我想是想TabSelectionChanged就打开相应的ViewModel,怎么做?
解答:用ViewModel的INotifyPropertyChanged接口实现,因为TabItem作为一个选择器就有 IsSelected属性,把这个属性绑定到ViewModel的IsSelected字段,然后这个字段改变的时候用INotifyPropertyChanged接口实现通知即可。整个流程用MVVM实现非常整洁。
Xaml
1 <TabControl ...>
2 <TabControl.ItemContainerStyle>
3 <Style TargetType="{x:Type TabItem}">
4 <Setter Property="IsSelected"
5 Value="{Binding Path=IsSelected,Mode=TwoWay}"/>
6 </Style>
7 </TabControl.ItemContainerStyle>
8 </TabControl>
2 <TabControl.ItemContainerStyle>
3 <Style TargetType="{x:Type TabItem}">
4 <Setter Property="IsSelected"
5 Value="{Binding Path=IsSelected,Mode=TwoWay}"/>
6 </Style>
7 </TabControl.ItemContainerStyle>
8 </TabControl>
ViewModel
1 public class MyViewModel : INotifyPropertyChanged
2 {
3 private bool _isLoaded;
4
5 private void Load()
6 {
7 // code
8 }
9
10 private bool _isSelected;
11
12 public bool IsSelected
13 {
14 get
15 {
16 return this._isSelected;
17 }
18 set
19 {
20 if (this._isSelected != value)
21 {
22 this._isSelected = value;
23
24 if (this._isSelected && !this._isLoaded)
25 {
26 this.Load();
27 this._isLoaded = true;
28 }
29
30 var propertyChanged = this.PropertyChanged;
31 if (propertyChanged != null)
32 {
33 propertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
34 }
35 }
36 }
37 }
38
39 public event PropertyChangedEventHandler PropertyChanged;
40 }
2 {
3 private bool _isLoaded;
4
5 private void Load()
6 {
7 // code
8 }
9
10 private bool _isSelected;
11
12 public bool IsSelected
13 {
14 get
15 {
16 return this._isSelected;
17 }
18 set
19 {
20 if (this._isSelected != value)
21 {
22 this._isSelected = value;
23
24 if (this._isSelected && !this._isLoaded)
25 {
26 this.Load();
27 this._isLoaded = true;
28 }
29
30 var propertyChanged = this.PropertyChanged;
31 if (propertyChanged != null)
32 {
33 propertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
34 }
35 }
36 }
37 }
38
39 public event PropertyChangedEventHandler PropertyChanged;
40 }
举例2:我有个TreeView,里面的项非常复杂,还需要惰性加载(点击了才取数据并展开),怎么用MVVM实现?彻底晕了
解答:还是用ViewModel的INotifyPropertyChanged接口实现,要使得你的视图没有代码,就要你不再把TreeView当成一个存储数据的地方,而是看做一个展现数据的地方,那么一切都将水到渠成。这就是ViewModel这个想法的由来。而用INotifyPropertyChanged接口可以神奇的实现它们之间的解耦。
Xaml
1 <TreeView ItemsSource="{Binding FirstGeneration}">
2 <TreeView.ItemContainerStyle>
3 <!--
4 This Style binds a TreeViewItem to a PersonViewModel.
5 -->
6 <Style TargetType="{x:Type TreeViewItem}">
7 <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
8 <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
9 <Setter Property="FontWeight" Value="Normal" />
10 <Style.Triggers>
11 <Trigger Property="IsSelected" Value="True">
12 <Setter Property="FontWeight" Value="Bold" />
13 </Trigger>
14 </Style.Triggers>
15 </Style>
16 </TreeView.ItemContainerStyle>
17
18 <TreeView.ItemTemplate>
19 <HierarchicalDataTemplate ItemsSource="{Binding Children}">
20 <TextBlock Text="{Binding Name}" />
21 </HierarchicalDataTemplate>
22 </TreeView.ItemTemplate>
23 </TreeView>
2 <TreeView.ItemContainerStyle>
3 <!--
4 This Style binds a TreeViewItem to a PersonViewModel.
5 -->
6 <Style TargetType="{x:Type TreeViewItem}">
7 <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
8 <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
9 <Setter Property="FontWeight" Value="Normal" />
10 <Style.Triggers>
11 <Trigger Property="IsSelected" Value="True">
12 <Setter Property="FontWeight" Value="Bold" />
13 </Trigger>
14 </Style.Triggers>
15 </Style>
16 </TreeView.ItemContainerStyle>
17
18 <TreeView.ItemTemplate>
19 <HierarchicalDataTemplate ItemsSource="{Binding Children}">
20 <TextBlock Text="{Binding Name}" />
21 </HierarchicalDataTemplate>
22 </TreeView.ItemTemplate>
23 </TreeView>
ViewModel
1 public class PersonViewModel
2 {
3 public PersonViewModel(Person person)
4 : this(person, null)
5 {
6 }
7
8 private PersonViewModel(Person person, PersonViewModel parent)
9 {
10 _person = person;
11 _parent = parent;
12
13 _children = new ReadOnlyCollection<PersonViewModel>(
14 (from child in _person.Children
15 select new PersonViewModel(child, this))
16 .ToList<PersonViewModel>());
17 }
18
19 private bool _isSelected;
20 public bool IsSelected
21 {
22 get { return _isSelected; }
23 set
24 {
25 if (value != _isSelected)
26 {
27 _isSelected = value;
28 this.OnPropertyChanged("IsSelected");
29 }
30 }
31 }
32
33 private bool _isExpanded;
34 public bool IsExpanded
35 {
36 get { return _isExpanded; }
37 set
38 {
39 if (value != _isExpanded)
40 {
41 _isExpanded = value;
42 this.OnPropertyChanged("IsExpanded");
43 }
44
45 // Expand all the way up to the root.
46 if (_isExpanded && _parent != null)
47 _parent.IsExpanded = true;
48 }
49 }
50 public string Name
51 {
52 get { return _person.Name; }
53 }
54 }
55 public class Person
56 {
57 readonly List<Person> _children = new List<Person>();
58 public IList<Person> Children
59 {
60 get { return _children; }
61 }
62
63 public string Name { get; set; }
64 }
2 {
3 public PersonViewModel(Person person)
4 : this(person, null)
5 {
6 }
7
8 private PersonViewModel(Person person, PersonViewModel parent)
9 {
10 _person = person;
11 _parent = parent;
12
13 _children = new ReadOnlyCollection<PersonViewModel>(
14 (from child in _person.Children
15 select new PersonViewModel(child, this))
16 .ToList<PersonViewModel>());
17 }
18
19 private bool _isSelected;
20 public bool IsSelected
21 {
22 get { return _isSelected; }
23 set
24 {
25 if (value != _isSelected)
26 {
27 _isSelected = value;
28 this.OnPropertyChanged("IsSelected");
29 }
30 }
31 }
32
33 private bool _isExpanded;
34 public bool IsExpanded
35 {
36 get { return _isExpanded; }
37 set
38 {
39 if (value != _isExpanded)
40 {
41 _isExpanded = value;
42 this.OnPropertyChanged("IsExpanded");
43 }
44
45 // Expand all the way up to the root.
46 if (_isExpanded && _parent != null)
47 _parent.IsExpanded = true;
48 }
49 }
50 public string Name
51 {
52 get { return _person.Name; }
53 }
54 }
55 public class Person
56 {
57 readonly List<Person> _children = new List<Person>();
58 public IList<Person> Children
59 {
60 get { return _children; }
61 }
62
63 public string Name { get; set; }
64 }
按需加载:
View Code
1 interface ITreeViewItemViewModel : INotifyPropertyChanged
2 {
3 ObservableCollection<TreeViewItemViewModel> Children { get; }
4 bool HasDummyChild { get; }
5 bool IsExpanded { get; set; }
6 bool IsSelected { get; set; }
7 TreeViewItemViewModel Parent { get; }
8 }
9 public TreeViewItemViewModel : ITreeViewItemViewModel
10 {
11 protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
12 {
13 _parent = parent;
14
15 _children = new ObservableCollection<TreeViewItemViewModel>();
16
17 if (lazyLoadChildren)
18 _children.Add(DummyChild);
19 }
20 public bool IsExpanded
21 {
22 get { return _isExpanded; }
23 set
24 {
25 if (value != _isExpanded)
26 {
27 _isExpanded = value;
28 this.OnPropertyChanged("IsExpanded");
29 }
30
31 // Expand all the way up to the root.
32 if (_isExpanded && _parent != null)
33 _parent.IsExpanded = true;
34
35 // Lazy load the child items, if necessary.
36 if (this.HasDummyChild)
37 {
38 this.Children.Remove(DummyChild);
39 this.LoadChildren();
40 }
41 }
42 }
43
44 /// <summary>
45 /// Returns true if this object's Children have not yet been populated.
46 /// </summary>
47 public bool HasDummyChild
48 {
49 get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
50 }
51
52 /// <summary>
53 /// Invoked when the child items need to be loaded on demand.
54 /// Subclasses can override this to populate the Children collection.
55 /// </summary>
56 protected virtual void LoadChildren()
57 {
58 }
59 }
2 {
3 ObservableCollection<TreeViewItemViewModel> Children { get; }
4 bool HasDummyChild { get; }
5 bool IsExpanded { get; set; }
6 bool IsSelected { get; set; }
7 TreeViewItemViewModel Parent { get; }
8 }
9 public TreeViewItemViewModel : ITreeViewItemViewModel
10 {
11 protected TreeViewItemViewModel(TreeViewItemViewModel parent, bool lazyLoadChildren)
12 {
13 _parent = parent;
14
15 _children = new ObservableCollection<TreeViewItemViewModel>();
16
17 if (lazyLoadChildren)
18 _children.Add(DummyChild);
19 }
20 public bool IsExpanded
21 {
22 get { return _isExpanded; }
23 set
24 {
25 if (value != _isExpanded)
26 {
27 _isExpanded = value;
28 this.OnPropertyChanged("IsExpanded");
29 }
30
31 // Expand all the way up to the root.
32 if (_isExpanded && _parent != null)
33 _parent.IsExpanded = true;
34
35 // Lazy load the child items, if necessary.
36 if (this.HasDummyChild)
37 {
38 this.Children.Remove(DummyChild);
39 this.LoadChildren();
40 }
41 }
42 }
43
44 /// <summary>
45 /// Returns true if this object's Children have not yet been populated.
46 /// </summary>
47 public bool HasDummyChild
48 {
49 get { return this.Children.Count == 1 && this.Children[0] == DummyChild; }
50 }
51
52 /// <summary>
53 /// Invoked when the child items need to be loaded on demand.
54 /// Subclasses can override this to populate the Children collection.
55 /// </summary>
56 protected virtual void LoadChildren()
57 {
58 }
59 }
真正加载子项的工作留给子类去实现。它们重载LoadChildren方法来提供一个跟类型相关的加载子项的实现。比如下面的RegionViewModel类,它重载了该方法来加载State对象并且创建StateViewModel对象。
View Code
1 public class RegionViewModel : TreeViewItemViewModel
2 {
3 readonly Region _region;
4
5 public RegionViewModel(Region region)
6 : base(null, true)
7 {
8 _region = region;
9 }
10
11 public string RegionName
12 {
13 get { return _region.RegionName; }
14 }
15
16 protected override void LoadChildren()
17 {
18 foreach (State state in Database.GetStates(_region))
19 base.Children.Add(new StateViewModel(state, this));
20 }
21 }
2 {
3 readonly Region _region;
4
5 public RegionViewModel(Region region)
6 : base(null, true)
7 {
8 _region = region;
9 }
10
11 public string RegionName
12 {
13 get { return _region.RegionName; }
14 }
15
16 protected override void LoadChildren()
17 {
18 foreach (State state in Database.GetStates(_region))
19 base.Children.Add(new StateViewModel(state, this));
20 }
21 }
Xaml
1 <TreeView ItemsSource="{Binding Regions}">
2 <TreeView.ItemContainerStyle>
3 <!--
4 This Style binds a TreeViewItem to a TreeViewItemViewModel.
5 -->
6 <Style TargetType="{x:Type TreeViewItem}">
7 <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
8 <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
9 <Setter Property="FontWeight" Value="Normal" />
10 <Style.Triggers>
11 <Trigger Property="IsSelected" Value="True">
12 <Setter Property="FontWeight" Value="Bold" />
13 </Trigger>
14 </Style.Triggers>
15 </Style>
16 </TreeView.ItemContainerStyle>
17
18 <TreeView.Resources>
19 <HierarchicalDataTemplate
20 DataType="{x:Type local:RegionViewModel}"
21 ItemsSource="{Binding Children}"
22 >
23 <StackPanel Orientation="Horizontal">
24 <Image Width="16" Height="16"
25 Margin="3,0" Source="Images"Region.png" />
26 <TextBlock Text="{Binding RegionName}" />
27 </StackPanel>
28 </HierarchicalDataTemplate>
29
30 <HierarchicalDataTemplate
31 DataType="{x:Type local:StateViewModel}"
32 ItemsSource="{Binding Children}"
33 >
34 <StackPanel Orientation="Horizontal">
35 <Image Width="16" Height="16"
36 Margin="3,0" Source="Images"State.png" />
37 <TextBlock Text="{Binding StateName}" />
38 </StackPanel>
39 </HierarchicalDataTemplate>
40
41 <DataTemplate DataType="{x:Type local:CityViewModel}">
42 <StackPanel Orientation="Horizontal">
43 <Image Width="16" Height="16"
44 Margin="3,0" Source="Images"City.png" />
45 <TextBlock Text="{Binding CityName}" />
46 </StackPanel>
47 </DataTemplate>
48 </TreeView.Resources>
49 </TreeView>
2 <TreeView.ItemContainerStyle>
3 <!--
4 This Style binds a TreeViewItem to a TreeViewItemViewModel.
5 -->
6 <Style TargetType="{x:Type TreeViewItem}">
7 <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
8 <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
9 <Setter Property="FontWeight" Value="Normal" />
10 <Style.Triggers>
11 <Trigger Property="IsSelected" Value="True">
12 <Setter Property="FontWeight" Value="Bold" />
13 </Trigger>
14 </Style.Triggers>
15 </Style>
16 </TreeView.ItemContainerStyle>
17
18 <TreeView.Resources>
19 <HierarchicalDataTemplate
20 DataType="{x:Type local:RegionViewModel}"
21 ItemsSource="{Binding Children}"
22 >
23 <StackPanel Orientation="Horizontal">
24 <Image Width="16" Height="16"
25 Margin="3,0" Source="Images"Region.png" />
26 <TextBlock Text="{Binding RegionName}" />
27 </StackPanel>
28 </HierarchicalDataTemplate>
29
30 <HierarchicalDataTemplate
31 DataType="{x:Type local:StateViewModel}"
32 ItemsSource="{Binding Children}"
33 >
34 <StackPanel Orientation="Horizontal">
35 <Image Width="16" Height="16"
36 Margin="3,0" Source="Images"State.png" />
37 <TextBlock Text="{Binding StateName}" />
38 </StackPanel>
39 </HierarchicalDataTemplate>
40
41 <DataTemplate DataType="{x:Type local:CityViewModel}">
42 <StackPanel Orientation="Horizontal">
43 <Image Width="16" Height="16"
44 Margin="3,0" Source="Images"City.png" />
45 <TextBlock Text="{Binding CityName}" />
46 </StackPanel>
47 </DataTemplate>
48 </TreeView.Resources>
49 </TreeView>
未完待续。
Powered By D&J (URL:http://www.cnblogs.com/Areas/)