Data Binding和INotifyPropertyChanged是如何協調工作的?


前言

WPF的一大基礎就是Data Binding。在基於MVVM架構的基礎上,只有通過實現INotifyPropertyChanged接口的ViewModel才能夠用於Data Binding。

要實現INotifyPropertyChanged接口,只需要實現一個事件,event PropertyChangedEventHandler PropertyChange

作為一個剛接觸WPF沒多久的人來說,我最不可理解的就是data binding的時候到底誰自動幫我完成了訂閱PropertyChanged這個事件?

delegate & event基礎知識回顧

先來回顧下C#里delegate和event的基礎知識。

我們知道在C#里,event其實就是一種簡化的delegate。所謂的簡化其實是為了更好地解決訂閱和廣播的問題。為什么?我們知道在處理訂閱時,各個listener之間應該時相互不知道對方的存在的,並且相互間互不影響。delegate和event都通過+=和-=來注冊和反注冊listener。而delegate還可以通過=進行賦值。這個=賦值完美地破壞了剛才我們期望的listener之間的相互關系。

如何實現一個event?

我們這里簡單列出一個完整的event實例,然后就這個實例來討論核心問題INotifyPropertyChanged。

 1 public class ValueChangedEventArgs : System.EventArgs
 2 {
 3   public readonly int oldValue;
 4   public readonly int newValue;
 5 
 6   public ValueChangedEventArgs(int oldOne, int newOne)
 7   {
 8     oldValue = oldOne;
 9     newValue = newOne;
10   }
11 }
12 
13 public class Commodity
14 {
15   string symbol;
16   int volume;
17 
18   public event EventHandler<ValueChangedEventArgs> ValueChanged;
19 
20   protected void OnValueChanged(ValueChangedEventArgs e)
21   {
22     if (ValueChanged != null)
23     {
24       ValueChanged(this, e);
25     }
26   }
27 
28   public int Volume
29   {
30     get { return volume; }
31     set
32     {
33       if (volume == value) { return; }
34 
35       int oldValue = volume;
36       volume = value;
37 
38       OnValueChanged(new ValueChangedEventArgs(oldValue, volume);
39   }
40 }
41 
42 class Test
43 {
44   static void Main()
45   {
46     Commodity c = new Commodity(“600000”);
47     c.Volume = 100;
48     c.ValueChanged += commodity_ValueChanged;
49     c.Volume = 200;
50   }
51 
52   static void commodity_ValueChanged(object sender, ValueChangedEventArgs e)
53   {
54     Console.WriteLine(“Volume Changed!”);
55   }
56 }

運行上面的例子我們會發現,c.Volume = 100時執行了OnValueChanged。但是由於ValueChanged這個event為空,OnValueChanged直接返回了。接下來通過給ValueChanged賦上commodity_ValueChanged后繼續執行c.Volume = 200,這時控制台就打印出我們想要的結果了。

現在我們回到對INotifyPropertyChanged的討論上來。

在WPF編程過程中,當我們要實現一個ViewModel時,我們會發現我們通過下面這一行代碼就實現了對PropertyChanged這個event的增刪操作:

public event PropertyChangedEventHandler PropertyChanged;

為什么說上面這一行實現了event的增刪操作?事實上編譯器我們后做了很多事情。參考C# 5.0 in a Nutshell: The Definitive Reference,上述的event語句會被編譯器展開成類似下面的語句:

1 PropertyChangedEventHandler _propertyChanged;
2 public event PropertyChangedEventHandler PropertyChanged
3 {
4   add { _propertyChanged += value; }
5   remove { _propertyChanged -= value; }
6 }

所以,對於event我們就可以簡寫成public event SomeEventHandler SomeEvent。

接下來,通過XAML的data binding就可以將控件中的某個屬性綁定ViewModel的某個屬性上了。比方說Commodity.Volume(這里假定Commodity 實現了INotifyPropertyChanged接口)。

再之后,改變Volume數值,相應控件的綁定屬性就同步更改了。

我在接觸WPF data binding時,最大的疑惑就在這里。PropertyChanged我從來就沒有去顯示的初始化,為什么運行時,PropertyChanged非空?是誰初始化了PropertyChanged?

PropertyChanged是如何被初始化的?

要回答這個問題,不得不說,需要一定的篇幅才能說清楚。

首先我們可以根據現象確定PropertyChanged再運行是是會被初始化的。既然這樣,那么我們可以通過設置斷點來看到底什么時候會被初始化。

如何設置斷點?

我們需要手動將編譯器做的事情自己來做一遍。自己寫event的add和remove。也就是上文看到的那個樣子。

然后再打斷點到add行。運行后我們會看到如下堆棧:

 

OK!PropertyChanged被初始化了。於此同時我們也發現了一個和PropertyChanged相關聯的類:PropertyChangedEventManager。

好家伙,這個manager類在MSDN上還有介紹!

PropertyChangedEventManager class provides a WeakEventManager implementation so that you can use the "weak event listener" pattern to attach listeners for the PropertyChanged event.

到此,我們算是明白PropertyChanged是被誰自動初始化的了。不過,我覺得這還不夠。既然.Net Framework都開源了,為啥不去看看這個類的源碼是怎么處理也個業務邏輯的呢?

Reference Source我們的好幫手

打開Reference Source主頁,搜索PropertyChangedEventManager,找到StartListening方法:

1 protected override void StartListening(object source)
2 {
3   INotifyPropertyChanged typedSource = (INotifyPropertyChanged)source;
4   typedSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
5 }

不管怎么說,這個實現簡直太直觀了。如果你想看OnPropertyChanged是如何實現的,請自行前往。所以當ViewModel里的屬性改變后,會調用ViewModel.PropertyChanged,繼而調用PropertyChangedEventManager.OnPropertyChanged。PropertyChangedEventManager.OnPropertyChanged會繼而調用WeakEventManager.DeliverEventToList,然后調用ListenerList.DeliverEvent。DeliverEvent會把sender和EventArgs e傳遞給Listener.Handler(sender, e)完成PropertyChanged的整個過程。

總之,自己親自去翻一番源碼,你一定能腦補成功。

關於PropertyChangedEventManager和WeakEventManager是什么東西,請參閱MSDN:Weak Event Patterns。至於更細節的東西,我目前無法提供。我也是新手,暫時還沒有做更深入的研究,后續有時間,會結合代碼再研究一下。

Data Binding的Target是如何處理PropertyChanged的?

我們就拿TextBlock.Text來分析這個問題。

首先TextBlock.Text屬性是一個dependency property。只有dependency property才能執行data binding操作。

廢話不多說,直接上源碼:

 1 public static readonly DependencyProperty TextProperty =
 2                 DependencyProperty.Register(
 3                         "Text",
 4                         typeof(string),
 5                         typeof(TextBlock),
 6                         new FrameworkPropertyMetadata(
 7                                 string.Empty,
 8                                 FrameworkPropertyMetadataOptions.AffectsMeasure |
 9                                 FrameworkPropertyMetadataOptions.AffectsRender,
10                                 new PropertyChangedCallback(OnTextChanged), 11                                 new CoerceValueCallback(CoerceText)));

 應該就是加粗的那一行用來處理PropertyChanged事件。

簡單驗證下,在Visual Studio里設置一個斷點:PresentationFramework.dll!System.Windows.Controls.TextBlock.OnTextChanged

我這里看到的堆棧調用是:

 

通過Visual Studio自帶的Locals窗口,你還可以看到傳入的newText值是多少!

完!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM