不管是在流氓腾的问问社区,仍是在黑度贴吧,或是“厕所等你”论坛上,曾经看到过很多朋友讨论INotifyPropertyChanged接口。很多朋友认为该接口是为双向绑定而使用的,那么,真实的状况是这样的吗?dom
INotifyPropertyChanged接口位于System.ComponentModel命名空间,在该命名空间下还有另外一个接口:INotifyPropertyChanging。INotifyPropertyChanging接口定义了PropertyChanging事件,应该在在属性值正在改变时引起;INotifyPropertyChanged接口定义了PropertyChanged事件,应当在属性的值已经改变后引起。工具
因为INotifyPropertyChanging接口仅在完整的.net库才有,在可移植的库里面并无定义,所以,INotifyPropertyChanged接口的使用频率更高。并且,多数状况下,咱们只关心属性值是否已经改变,而对属性值的修改过程并不关注。开发工具
上面废话了一大堆,本文的主旨问题就来了——INotifyPropertyChanged接口是否只是跟双向绑定有关?测试
下面咱们考虑第一种状况。this
在单向绑定中,使用INotifyPropertyChanged接口和不使用INotifyPropertyChanged接口会有什么不一样。spa
我们定义一个类,这个类有一个公共的Value属性,当实例化类时,会经过Timer类,每隔3秒钟更新一下Value属性,属性值使用随机整数。代码以下:.net
public class TestDemo { Timer _timer = null; Random _rand = null; public TestDemo () { _rand = new Random(); TimerCallback cb = ( s ) => { Value = _rand.Next(); }; _timer = new Timer(cb, null, TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(3)); } int _val; public int Value { get { return _val; } set { _val = value; } } }
代码我不解释了,相信你们能看懂,由于不复杂,注意的是,Timer对象一但实例化就会立刻计时的。
如今把这个示范类用在单向绑定上,让Value属性的值显示在TextBlock上。3d
<Window x:Class="SampleApp1.MainWindow" …… xmlns:local="clr-namespace:SampleApp1" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.Resources> <local:TestDemo x:Key="td"/> </Grid.Resources> <TextBlock FontSize="24" Text="{Binding Source={StaticResource td},Path=Value,Mode=OneWay}"/> </Grid> </Window>
OneWay就是单向绑定,如今运行应用程序,这时会发现,TextBlock上的文本一值没有改变。那是否是计时器没有成功计时呢?双向绑定
经过断点调试发现,计时器是成功计时了,而Value属性也顺利地被修改,以下图:调试
按理说,单向绑定会让数据从数据源流向绑定目标的,那为何TextBlock控件没有即时更新呢? 缘由是Binding没有接收到属性更改通知,故没有去取最新的值。
下面咱们让示范类实现INotifyPropertyChanged接口。
public class TestDemo:INotifyPropertyChanged { Timer _timer = null; Random _rand = null; public TestDemo () { _rand = new Random(); TimerCallback cb = ( s ) => { Value = _rand.Next(); }; _timer = new Timer(cb, null, TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(3)); } int _val; public int Value { get { return _val; } set { if (_val != value) { _val = value; // 引起属性更改通知事件 if (this.PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("Value")); } } } } // 实现INotifyPropertyChanged接口的事件 public event PropertyChangedEventHandler PropertyChanged; }
这时候,再次运行应用程序,就发现TextBlock中的值可以自动更新了。
上面的例子说明了什么? 它代表,属性更改通知并非只有在双向绑定中才使用,在单向绑定中一样须要。
下面再看看双向绑定的状况。
咱们先来验证一个问题:做为数据源的类型是否是必定要实现INotifyPropertyChanged接口才能被UI更新呢?
先定义一个用来测试的类。
public class Employee { private string _name; private string _city; public string Name { get { return _name; } set { _name = value; } } public string City { get { return _city; } set { _city = value; } } }
<Window x:Class="SampleApp2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SampleApp2" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.Resources> <local:Employee x:Key="emp" Name="小明" City="重庆"/> </Grid.Resources> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> </Grid.RowDefinitions> <GroupBox Grid.Row="0"> <GroupBox.Header> <TextBlock Text="修改信息" FontSize="24" Foreground="Blue"/> </GroupBox.Header> <StackPanel DataContext="{Binding Source={StaticResource emp}}"> <TextBlock Text="贡工姓名:"/> <TextBox Width="200" HorizontalAlignment="Left" Text="{Binding Path=Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> <TextBlock Margin="0,15,0,0" Text="所在城市:"/> <TextBox Width="200" HorizontalAlignment="Left" Text="{Binding Path=City,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> </StackPanel> </GroupBox> <GroupBox Grid.Row="1" Margin="0,20,0,0"> <GroupBox.Header> <TextBlock Text="显示信息" Foreground="Blue" FontSize="24"/> </GroupBox.Header> <TextBlock DataContext="{Binding Source={StaticResource emp}}"> 员工姓名; <Run Text="{Binding Name}"/> <LineBreak/> 所在城市: <Run Text="{Binding City}"/> </TextBlock> </GroupBox> </Grid> </Window>
Employee类并无实现INotifyPropertyChanged接口,可是运行上面程序后会发现,在TextBox中修改数据后,下面的TextBlock是能够自动更新的。下面咱们把上面例子改一下,不经过Binding来更新数据,而是用代码来手动改。
<StackPanel DataContext="{Binding Source={StaticResource emp}}"> <TextBlock Text="贡工姓名:"/> <!--<TextBox Width="200" HorizontalAlignment="Left" Text="{Binding Path=Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>--> <TextBox Width="200" HorizontalAlignment="Left" x:Name="txtName"/> <TextBlock Margin="0,15,0,0" Text="所在城市:"/> <!--<TextBox Width="200" HorizontalAlignment="Left" Text="{Binding Path=City,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>--> <TextBox Width="200" HorizontalAlignment="Left" x:Name="txtCity"/> <Button Content="更 新" Click="OnClick" Width="200" HorizontalAlignment="Left" Margin="0,10,0,0"/> </StackPanel>
private void OnClick ( object sender, RoutedEventArgs e ) { Employee emp = layoutRoot.Resources["emp"] as Employee; if (emp != null) { emp.Name = txtName.Text; emp.City = txtCity.Text; } }
这种状况下,是经过代码来修改示例对象的属性。运行示例程序后,会发现,修改内容后,下面的TextBlock控件不会自动更新。而经过断点调试,发现Employee实例的属性值确实已经被更新,但是TextBlock没有显示新的值。
而后,咱们让Employee类实现
public class Employee : INotifyPropertyChanged { private string _name; private string _city; public string Name { get { return _name; } set { if (_name != value) { _name = value; OnPropertyChanged(); } } } public string City { get { return _city; } set { if (_city != value) { _city = value; OnPropertyChanged(); } } } private void OnPropertyChanged([CallerMemberName] string propName=""){ if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propName)); } } public event PropertyChangedEventHandler PropertyChanged; }
在每一个属性值发生更改后都要引起PropertyChanged事件,这里用一个OnPropertyChanged方法封装起来,参数是发生更改的属性的名字。该处用到一个技巧,就是在参数上附加CallerMemberNameAttribute特性,并给参数一个默认值:空字符串。
在属性的set访问器中调用OnPropertyChanged方法时就不须要写上属性的名字了,CallerMemberNameAttribute会自动把调用方的成员名字赋给方法参数,因为OnPropertyChanged方法是在被更改的属性内调用的,因此CallerMemberNameAttribute获得的正是这个属性的名字,如此一来咱们就省事不少了。
如今运行应用程序。修改对象属性,TextBlock就可以自动更新了。
经过以上各例,能够发现,INotifyPropertyChanged接口并非绝对地与双向绑定有关,在彻底使用Binding进行双向处理的时候,即便不实现INotifyPropertyChanged接口也能够实现获取更新,固然,Binding的源必定是同一个实例。但若是修改数据不是经过Binding来完成的,使用数据源的各个客户方就不会得到属性更改通知,所以这时候须要实现INotifyPropertyChanged接口。
通过上面几个演示,咱们能够发现,INotifyPropertyChanged接口并不必定要在双向绑定的时候使用,可是为了让使用数据的代码可以及时得到属性更改通知,数据源对象都应该实现INotifyPropertyChanged接口,你们能够看看Linq to SQL或者实体模型中,开发工具生成的实体类型都是实现INotifyPropertyChanged接口的,这正是考虑到要让全部数据使用都能及时得到更新通知的作法。
但愿,经过我这篇烂文的讲述,你们可以对INotifyPropertyChanged有新的认识。