不管是在流氓騰的問問社區,仍是在黑度貼吧,或是「廁所等你」論壇上,曾經看到過很多朋友討論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有新的認識。