無論是在流氓騰的問問社區,還是在黑度貼吧,或是“廁所等你”論壇上,曾經看到過不少朋友討論INotifyPRopertyChanged接口。不少朋友認為該接口是為雙向綁定而使用的,那么,真實的情況是這樣的嗎?
INotifyPropertyChanged接口位于System.ComponentModel命名空間,在該命名空間下還有另一個接口:INotifyPropertyChanging。INotifyPropertyChanging接口定義了PropertyChanging事件,應該在在屬性值正在改變時引發;INotifyPropertyChanged接口定義了PropertyChanged事件,應當在屬性的值已經改變后引發。
由于INotifyPropertyChanging接口僅在完整的.net庫才有,在可移植的庫里面并沒有定義,因此,INotifyPropertyChanged接口的使用頻率更高。而且,多數情況下,我們只關心屬性值是否已經改變,而對屬性值的修改過程并不關注。
上面廢話了一大堆,本文的主旨問題就來了——INotifyPropertyChanged接口是否只是跟雙向綁定有關?
下面我們考慮第一種情況。
在單向綁定中,使用INotifyPropertyChanged接口和不使用INotifyPropertyChanged接口會有什么不同。
咱們定義一個類,這個類有一個公共的Value屬性,當實例化類時,會通過Timer類,每隔3秒鐘更新一下Value屬性,屬性值使用隨機整數。代碼如下:
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上。
<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有新的認識。
新聞熱點
疑難解答