本文的代码都是基于WPF的,对于Silverlight,这些技术也同样适用。 本文详细讨论了INotifyPropertyChanged和ObservableCollection一些使用技巧。


本文的代码都是基于WPF的,对于Silverlight,这些技术也同样适用。

(一)INotifyPropertyChanged的使用场合

先写一个最简单的数据绑定,每次点击Button后,TextBlock的值都会自增1。

效果图如下所示:

玩转INotifyPropertyChanged和ObservableCollection_sed

这里使用了MVVM模式,并把Click事件抽象为了Command。

代码下载:WpfApplication4_1.zip

观察上面的代码,注意到几个细节:

1. UserName和Age属性作为ViewModel的两个属性,因为Age递增是基于绑定实现的,所以ViewModel要实现INotifyPropertyChanged接口。

2. 我们只在Age上添加了OnPropertyChanged方法,它会根据Age属性的变化而自动更新XAML中绑定的值。

而对于UserName属性,由于它是始终不变的,所以没有添加OnPropertyChanged方法。

由此可见,OnPropertyChanged方法决定了后台数据的变化是否能影响到前台绑定的XAML。

此外,对于一次性绑定(以后不会再改变)的属性,不要添加OnPropertyChanged方法,因为该方法会增加额外的性能开销。

但是,在MVVM模式中,我们常常将UserName和Age属性抽象出来,作为一个实体类:

玩转INotifyPropertyChanged和ObservableCollection_数据_02 public class UserInfo
玩转INotifyPropertyChanged和ObservableCollection_sed_03 玩转INotifyPropertyChanged和ObservableCollection_sed_04
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public string UserName 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public int Age 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_12 }

与此同时,在ViewModel中添加一个UserInfo类型的属性:

public class RegisterUserViewModel : INotifyPropertyChanged
{
public UserInfo User { get ; set ; }

注意:此时XAML中的绑定相应要修改为:

< TextBlock Name ="tbUserName" Text =" {Binding User.UserName} " …. />
< TextBlock Name ="tbAge" Text =" {Binding User. Age} " …. />

这时候问题就来了。到底是UserInfo实现INotifyPropertyChanged接口呢,还是ViewModel实现INotifyPropertyChanged接口呢?

1.如果UserInfo实现INotifyPropertyChanged接口,,那么UserInfo实体类相应地修改为:

玩转INotifyPropertyChanged和ObservableCollection_数据_02 public class UserInfo : INotifyPropertyChanged
玩转INotifyPropertyChanged和ObservableCollection_sed_03 玩转INotifyPropertyChanged和ObservableCollection_sed_04
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public string UserName 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
private int age;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public int Age
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
get
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
return this .age;
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
set
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
if ( this .age != value)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .age = value;
玩转INotifyPropertyChanged和ObservableCollection_sed_20 OnPropertyChanged(
" Age " );
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_数据_07 玩转INotifyPropertyChanged和ObservableCollection_控件_06
INotifyPropertyChanged Members #region INotifyPropertyChanged Members
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public event PropertyChangedEventHandler PropertyChanged;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
private void OnPropertyChanged( string propertyName)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20 PropertyChangedEventHandler handler
= this .PropertyChanged;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
if (handler != null )
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20 handler(
this , new PropertyChangedEventArgs(propertyName));
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_数据_31
#endregion

玩转INotifyPropertyChanged和ObservableCollection_sed_12 }

而ViewModel相应地就简化了:

玩转INotifyPropertyChanged和ObservableCollection_数据_02 public class RegisterUserViewModel
玩转INotifyPropertyChanged和ObservableCollection_sed_03 玩转INotifyPropertyChanged和ObservableCollection_sed_04
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public RegisterUserViewModel()
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
this .User = new UserInfo 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { UserName = " Baobao " , Age = 27 } ;
玩转INotifyPropertyChanged和ObservableCollection_sed_20 ClickCommand
= new DelegateCommand < object > (OnClick, arg => true );
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
void OnClick( object obj)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .User.Age += 1 ;
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public UserInfo User 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public RegisterUserView View 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public ICommand ClickCommand 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_12 }

注意到,此时ViewModel不需要实现INotifyPropertyChanged接口。因为INotifyPropertyChanged监视的是UserInfo实体里面的Age属性,所以在OnClick方法中,只要修改User实体的Age属性就能通知XAML做出改变。

示例下载:WpfApplication4_2.zip

2.如果UserInfo不实现INotifyPropertyChanged接口,那么这个实体类就非常简单了:

玩转INotifyPropertyChanged和ObservableCollection_数据_02 public class UserInfo
玩转INotifyPropertyChanged和ObservableCollection_sed_03 玩转INotifyPropertyChanged和ObservableCollection_sed_04
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public string UserName 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public int Age 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_12 }

这时,ViewModel就必须要实现INotifyPropertyChanged接口,而且要为User属性添加OnPropertyChanged方法,以下是部分代码(省略了INotifyPropertyChanged接口的实现):

玩转INotifyPropertyChanged和ObservableCollection_数据_02 public class RegisterUserViewModel : INotifyPropertyChanged
玩转INotifyPropertyChanged和ObservableCollection_sed_03 玩转INotifyPropertyChanged和ObservableCollection_sed_04
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public RegisterUserViewModel()
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
this .User = new UserInfo 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { UserName = " Baobao " , Age = 27 } ;
玩转INotifyPropertyChanged和ObservableCollection_sed_20 ClickCommand
= new DelegateCommand < object > (OnClick, arg => true );
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
void OnClick( object obj)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .User.Age += 1 ;
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
private UserInfo user;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public UserInfo User
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
get
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
return this .user;
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
set
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
if ( this .user != value)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .user = value;
玩转INotifyPropertyChanged和ObservableCollection_sed_20 OnPropertyChanged(
" User " );
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public RegisterUserView View 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public ICommand ClickCommand 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_12 }

代码下载:WpfApplication4_3.zip

如果运行上述代码,你会发现XAML中的Age并不会自增1。问题应该出在OnClick方法上,我在下面一行添加了断点:

this .User.Age += 1 ;

我发现User的Age属性确实由27变成了28,但是并没有把改变结果通知XAML。

这是因为,INotifyPropertyChanged接口只监视UserInfo这个实体的地址是否发生了改变,而目前这个地址并没有变化,只是存储在UserInfo实体中的成员Age发生了改变,而Age的地址并不在INotifyPropertyChanged接口的监视之下,所以XAML中没有任何改变。

看来只有修改UserInfo这个实体的地址了,最好的方法就是重新实例化一个UserInfo实体:

玩转INotifyPropertyChanged和ObservableCollection_sed_04 玩转INotifyPropertyChanged和ObservableCollection_sed_03 Code
void OnClick( object obj)
{
// this syntax below cannot run, so it is marked
// this.User.Age += 1;

this .User = new UserInfo() { UserName = this .User.UserName, Age = this .User.Age + 1 };
}

示例下载:WpfApplication4_4.zip

比较两种实现方式,我个人倾向于第一种,因为它更自然,而第二种,由于要new一个新的UserInfo对象,所以要多牺牲一些性能。但是不管哪种实现方式,都不要ViewModel和UserInfo同时实现INotifyPropertyChanged接口,这是非常消耗性能的。

(二)ObservableCollection和List的使用场合

始终对ObservableCollection怀有恐惧,因为它太笼统了。

首先它是一个集合,但它是一个既实现了INotifyPropertyChanged接口又实现了INotifyCollectionChanged接口的集合,这是它与List<T>的不同之处。

使用ObservableCollection<T>注定要比List<T>消耗性能,所以我一直在探寻它们各自不同的使用场合。而且,当泛型参数T也实现了INotifyPropertyChanged接口时,就更加复杂了。

于是分为以下四种情况:

1.最简单是List<T>,其中T没有实现INotifyPropertyChanged接口,就是一个简单的实体。

做一个简单的示例,统计一个班级的计算机成绩。

玩转INotifyPropertyChanged和ObservableCollection_sed_166

由于List<T>中的T没有实现INotifyPropertyChanged接口,所以增加、删除、修改这3个Button都不会起作用。

着眼于“及格”这个CheckBox,勾选后就只显示分数不小于60的学生列表,取消勾选择显示全部学生。为此,在XAML中要把CheckBox的绑定方式设置为TwoWay(虽然CheckBox默认就是TwoWay,但还是建议显式指定):

< CheckBox Name ="chkPass" IsChecked =" {Binding Path=IsPassed, Mode=TwoWay} " > 及格 </ CheckBox >

在ViewModel中,关键是ModelPropertyChanged方法,在IsPassed属性变化的时候,会执行这个方法:

玩转INotifyPropertyChanged和ObservableCollection_数据_02 void ModelPropertyChanged( object sender, PropertyChangedEventArgs e)
玩转INotifyPropertyChanged和ObservableCollection_sed_03 玩转INotifyPropertyChanged和ObservableCollection_sed_04
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
switch (e.PropertyName)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
case " IsPassed " :
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .StudentList = this .IsPassed ? StudentService.RetrievePassedStudentList() : StudentService.RetrieveStudentList();
玩转INotifyPropertyChanged和ObservableCollection_sed_20
break ;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
default :
玩转INotifyPropertyChanged和ObservableCollection_sed_20
break ;
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_12 }

看得出,根据IsPassed属性的不同,分别为StudentList分配不同的地址,所以在XAML中的DataGrid会跟着发生改变。

随着这里的ViewModel也实现了INotifyPropertyChanged接口,但这是为了IsPassed属性和StudentList属性而实现的,与Student实体类无关。

玩转INotifyPropertyChanged和ObservableCollection_sed_182

代码下载:WpfApplication6_1.zip

如果大家读过我之前介绍MVP模式的一系列文章,这个小例子应该很简单。

结论:List<T>(T未实现INotifyPropertyChanged接口),适用于一次性绑定,适用于重新实例化整个List集合的绑定(例如上文的StudentList)。

2.稍微复杂点,List<T>,其中T实现了INotifyPropertyChanged接口。

也就是说,让Student实体类实现INotifyPropertyChanged接口。

玩转INotifyPropertyChanged和ObservableCollection_数据_02 public class Student : INotifyPropertyChanged
玩转INotifyPropertyChanged和ObservableCollection_sed_03 玩转INotifyPropertyChanged和ObservableCollection_sed_04
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public int UserId 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public string UserName 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
private int score;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public int Score
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
get
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
return this .score;
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
set
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
if ( this .score != value)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .score = value;
玩转INotifyPropertyChanged和ObservableCollection_sed_20 OnPropertyChanged(
" Score " );
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_数据_07 玩转INotifyPropertyChanged和ObservableCollection_控件_06
INotifyPropertyChanged Members #region INotifyPropertyChanged Members
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public event PropertyChangedEventHandler PropertyChanged;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
private void OnPropertyChanged( string propertyName)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20 PropertyChangedEventHandler handler
= this .PropertyChanged;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
if (handler != null )
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20 handler(
this , new PropertyChangedEventArgs(propertyName));
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_数据_31
#endregion

玩转INotifyPropertyChanged和ObservableCollection_sed_12 }

其它代码保持不变。

我们看到,Modify按钮可以使用了。修改后的数据立刻展现在XAML中。

玩转INotifyPropertyChanged和ObservableCollection_数据_239

但是,Add和Remove按钮仍然不能使用。因为INotifyPropertyChanged接口只是监视Student,而没有监视List<Student>集合。所以修改单笔Student数据,可以发送通知给XAML;但是修改List<Student>集合,除非像上一种方式那样重新实例化List<Student>集合(勾选CheckBox仅显示及格的学生),否则必须另辟蹊径。

代码下载:WpfApplication6_2.zip

结论:List<T>(T实现INotifyPropertyChanged接口),适用于修改集合中单笔数据的成员(比如说Student的Score属性)。

3.换个思路,用ObservableCollection<T>怎么样?其中T没有实现INotifyPropertyChanged接口。

也就是说,Student类仍然是一个简单的实体类:

玩转INotifyPropertyChanged和ObservableCollection_数据_02 public class Student
玩转INotifyPropertyChanged和ObservableCollection_sed_03 玩转INotifyPropertyChanged和ObservableCollection_sed_04
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public int UserId 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public string UserName 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public int Score 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_12 }

相应地,把ViewModel中的List<Student>全都修改为ObservableCollection<Student>,并在构造函数中把原始的List集合转换为ObservableCollection集合:

玩转INotifyPropertyChanged和ObservableCollection_数据_02 public class ScoreListViewModel : INotifyPropertyChanged
玩转INotifyPropertyChanged和ObservableCollection_sed_03 玩转INotifyPropertyChanged和ObservableCollection_sed_04
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public ScoreListViewModel()
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .StudentList = new ObservableCollection < Student > (StudentService.RetrieveStudentList());
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20 AddCommand
= new DelegateCommand < object > (OnAdd, arg => true );
玩转INotifyPropertyChanged和ObservableCollection_sed_20 ModifyCommand
= new DelegateCommand < object > (OnModify, arg => true );
玩转INotifyPropertyChanged和ObservableCollection_sed_20 RemoveCommand
= new DelegateCommand < object > (OnRemove, arg => true );
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .PropertyChanged += ModelPropertyChanged;
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
void ModelPropertyChanged( object sender, PropertyChangedEventArgs e)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
switch (e.PropertyName)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
case " IsPassed " :
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .StudentList = new ObservableCollection < Student > (
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .IsPassed ? StudentService.RetrievePassedStudentList() : StudentService.RetrieveStudentList());
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
break ;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
default :
玩转INotifyPropertyChanged和ObservableCollection_sed_20
break ;
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public ScoreListView View 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
private bool isPassed;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public bool IsPassed
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
get
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
return this .isPassed;
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
set
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
if ( this .isPassed != value)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .isPassed = value;
玩转INotifyPropertyChanged和ObservableCollection_sed_20 OnPropertyChanged(
" IsPassed " );
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public ObservableCollection < Student > StudentList 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public ICommand AddCommand 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public ICommand ModifyCommand 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
public ICommand RemoveCommand 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { get ; set ; }
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public void OnAdd( object obj)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
this .StudentList.Add( new Student() 玩转INotifyPropertyChanged和ObservableCollection_数据_05 { UserId = 7 , UserName = " 李永京 " , Score = 75 } );
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public void OnModify( object obj)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20 var item
= this .StudentList.SingleOrDefault(x => x.UserId == 1 );
玩转INotifyPropertyChanged和ObservableCollection_sed_20
if (item != null )
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20 item.Score
= 0 ;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
// item = new Student() { UserId = item.UserId, UserName = item.UserName, Score = 0 };
玩转INotifyPropertyChanged和ObservableCollection_数据_31
}

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public void OnRemove( object obj)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20 var item
= this .StudentList.SingleOrDefault(x => x.UserId == 2 );
玩转INotifyPropertyChanged和ObservableCollection_sed_20
if (item != null )
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20
this .StudentList.Remove(item);
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_数据_07 玩转INotifyPropertyChanged和ObservableCollection_控件_06
INotifyPropertyChanged Members #region INotifyPropertyChanged Members
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
public event PropertyChangedEventHandler PropertyChanged;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_sed_20
private void OnPropertyChanged( string propertyName)
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20 PropertyChangedEventHandler handler
= this .PropertyChanged;
玩转INotifyPropertyChanged和ObservableCollection_sed_20
if (handler != null )
玩转INotifyPropertyChanged和ObservableCollection_控件_06 玩转INotifyPropertyChanged和ObservableCollection_数据_07
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20 handler(
this , new PropertyChangedEventArgs(propertyName));
玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_数据_31 }

玩转INotifyPropertyChanged和ObservableCollection_sed_20
玩转INotifyPropertyChanged和ObservableCollection_数据_31
#endregion

玩转INotifyPropertyChanged和ObservableCollection_sed_12 }

看一下点击Add和Remove按钮后的效果图:

玩转INotifyPropertyChanged和ObservableCollection_sed_388

而由于ObservableCollection<T>中的T没有实现INotifyPropertyChanged接口,所以修改功能又不能使用了。

这时,我尝试着将

item.Score = 0 ;

修改为:

item = new Student() { UserId = item.UserId, UserName = item.UserName, Score = 0 };

但仍然无济于事。别灰心,还有最后一种解决方案没试呢~

代码下载:WpfApplication6_3.zip

结论:ObservableCollection<T>(T未实现INotifyPropertyChanged接口),适用于增删集合中的数据(上面的Add和Remove按钮)。但是对于单笔数据的更改,是无能为力的。

4.最复杂的情况,就是使用ObservableCollection<T>,并且T实现了INotifyPropertyChanged接口。

代码结合了上述第2、第3种编程的方式:

代码下载:WpfApplication6_4.zip

结论:所有的问题在次全都迎刃而解。这也许是最完美的解决方案了。但是,也是最消耗性能的。

(三)数据绑定中的OneWay和TwoWay

OneWay和TwoWay是最常见的两种绑定方式,描述了源数据(控件)和目标数据(控件)之间的关系。现在,让我们基于MVP模式的思想,重新认识数据绑定机制。

在下面的概念中,源数据(控件),在MVP里变成了数据(Model);目标(数据)控件,则相应地变成了XAML中绑定的控件。

在WPF和Silverlight的数据绑定中,因控件的不同,其默认的绑定方向也不一样。

在Silverlight 3.0中,BindingMode枚举有3个值,默认是OneWay:

玩转INotifyPropertyChanged和ObservableCollection_数据_02 public enum BindingMode
玩转INotifyPropertyChanged和ObservableCollection_sed_03 玩转INotifyPropertyChanged和ObservableCollection_sed_04
玩转INotifyPropertyChanged和ObservableCollection_数据_05 {
玩转INotifyPropertyChanged和ObservableCollection_sed_20 OneWay
= 1 ,
玩转INotifyPropertyChanged和ObservableCollection_sed_20 OneTime
= 2 ,
玩转INotifyPropertyChanged和ObservableCollection_sed_20 TwoWay
= 3
玩转INotifyPropertyChanged和ObservableCollection_sed_12 }

OneWay和OneTime是数据(Model)发生变化时通知XAML中的控件。而TwoWay则是数据和XAML之间任何一方发生变化都会通知对方。

我们在前面的例子中已经展示过,勾选“及格”这个CheckBox后,GridView中的数据会只显示Score>=60的学生,这个就是TwoWay的最好例证。

TwoWay,它适用于那些可以和用户互动的控件,比如说CheckBox和RadioButton的选中与否,比如在TextBox中输入了那些值,比如说在ListBox、ComboBox和GridView中选中了哪一行。我会在《Prism研究 之 包氏波动理念》来详细介绍其实现技术。

对于OneWay和OneTime,需要仔细说清楚。

A) OneWay是我们使用最频繁的了,无需多说,数据上的任何变化都会通知XAML中绑定的控件。

B) OneTime仅仅是一次性绑定。之后即使数据(Model)再发生变化,XAML中的控件也不会跟着改变。

还记得我们在本文的第一节《INotifyPropertyChanged的使用场合》举出的那个例子么?其中的UserName字段,就设置为OneTime的绑定方式,因为我们在初始化将其设置为”Baobao”后,就再也没改动过它。

将不变化的字段尽可能地设置为OneTime,而不是OneWay,这不就多少又节约了一些性能嘛。

而在WPF中,BindingMode枚举有5个值,默认是Default:

public enum BindingMode
{
TwoWay
= 0 ,
OneWay
= 1 ,
OneTime
= 2 ,
OneWayToSource
= 3 ,
Default
= 4
}

OneWayToSource,从字面上很容易理解,它表示当XAML中的控件发生变化时,通知数据(Model),但是在反方向上,数据发生变化却不会通知XAML中的控件。它和OneWay是正好相反的。这样,OneWayToSource在MVP模式中就没有半点用武之处(它的最大用处其实在于把非依赖属性绑定到依赖属性上,参加http://book.51cto.com/art/200908/145469.htm)。

在WPF中,绑定的默认值是Default,这不同于Silverlight 3的默认绑定OneWay。Default表示它会根据控件的不同而未控件自动选择其绑定行为。比如对于TextBox、CheckBox、RadioBtton这些用户可编辑的控件,Default值就是TwoWay;而对于只是用来显示数据的控件(比如ListBox、Label),Default值就是OneWay。