在代碼中常常見到這個接口,它裏面有什麼?它的做用是什麼?它和依賴屬性有什麼關係?app
下面就來總結回答這三個問題。ide
1.這個INotifyPropertyChanged接口裏就一個PropertyChanged的event,這個接口實際上是從.net 2.0就引入進來的,用它實現觀察者模式非常方便。函數
#region Assembly System.dll, v4.0.0.0 // C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\System.dll #endregion namespace System.ComponentModel { // Summary: // Notifies clients that a property value has changed. public interface INotifyPropertyChanged { // Summary: // Occurs when a property value changes. event PropertyChangedEventHandler PropertyChanged; } }
2.它的做用是什麼?學習
首先建立一個不用這個接口的例子。this
建立一個Employee.cs類。spa
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WpfAppLearning { public class Employee { private string _name; public string Name { get { return _name; } set { _name = value; } } } }
再建立一個MainWindow.xaml.net
<Window x:Class="WpfAppLearning.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <TextBox Name="txt1" HorizontalAlignment="Left" Height="23" Margin="148,74,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/> <Button Content="Button" HorizontalAlignment="Left" Margin="148,143,0,0" VerticalAlignment="Top" Width="75"/> </Grid> </Window>
在MainWindow.xaml.cs裏將Employee的Name屬性和TextBox的Text屬性綁定起來。3d
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfAppLearning { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); //新建一個員工,並給員工姓名賦初值 Employee employee = new Employee(); employee.Name = "Tom"; //建立綁定 Binding bind = new Binding(); bind.Source = employee; bind.Path = new PropertyPath("Name"); //設置綁定 this.txt1.SetBinding(TextBox.TextProperty, bind); //修改員工姓名之後 employee.Name = "Bob"; } } }
運行起來,效果以下:
code
也就是說,給textbox綁定了數據源Employee對象以後,我修改了Employee對象的Name屬性,但在界面上並無顯示出來,界面上顯示的仍是原來的初始值。orm
這時,可讓PropertyChanged登場了,其餘都不動,只從新修改Employee.cs代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel; namespace WpfAppLearning { public class Employee : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _name; public string Name { get { return _name; } set { _name = value; if (this.PropertyChanged != null) { this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); } } } } }
運行以下:
可見,只要實現了這個接口,在Name屬性值改變時激發一下PropertyChanged這個event,就能使binding獲得變動通知了。
顯然,在建立Binding對象並將它做爲數據源綁定到TextBox控件時,TextBox控件自動訂閱了這個PropertyChanged event。
但它是在哪裏訂閱的呢?很想知道,因而...
在Reflector裏查看Binding.cs的代碼,從它的構造函數,到Source及Path屬性的代碼中都找不到訂閱該event的蹤跡。
public class Binding : BindingBase { public Binding() { } public object Source { get { WeakReference<object> weakReference = (WeakReference<object>)base.GetValue(BindingBase.Feature.ObjectSource, null); if (weakReference == null) { return null; } object result; if (!weakReference.TryGetTarget(out result)) { return null; } return result; } set { base.CheckSealed(); if (this._sourceInUse != Binding.SourceProperties.None && this._sourceInUse != Binding.SourceProperties.Source) { throw new InvalidOperationException(SR.Get("BindingConflict", new object[] { Binding.SourceProperties.Source, this._sourceInUse })); } if (value != DependencyProperty.UnsetValue) { base.SetValue(BindingBase.Feature.ObjectSource, new WeakReference<object>(value)); this.SourceReference = new ExplicitObjectRef(value); return; } base.ClearValue(BindingBase.Feature.ObjectSource); this.SourceReference = null; } } public PropertyPath Path { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get { return this._ppath; } set { base.CheckSealed(); this._ppath = value; this._attachedPropertiesInPath = -1; base.ClearFlag(BindingBase.BindingFlags.PathGeneratedInternally); if (this._ppath == null || !this._ppath.StartsWithStaticProperty) { return; } if (this._sourceInUse == Binding.SourceProperties.None || this._sourceInUse == Binding.SourceProperties.StaticSource) { this.SourceReference = Binding.StaticSourceRef; return; } throw new InvalidOperationException(SR.Get("BindingConflict", new object[] { Binding.SourceProperties.StaticSource, this._sourceInUse })); } } }
實際上Binding類中有一個UpdateSourceTrigger屬性:
public class Binding : BindingBase { [DefaultValue(0)] public UpdateSourceTrigger UpdateSourceTrigger { get { switch (base.GetFlagsWithinMask(BindingBase.BindingFlags.UpdateOnLostFocus | BindingBase.BindingFlags.UpdateExplicitly)) { case BindingBase.BindingFlags.UpdateOnPropertyChanged: return UpdateSourceTrigger.PropertyChanged; case BindingBase.BindingFlags.UpdateOnLostFocus: return UpdateSourceTrigger.LostFocus; case BindingBase.BindingFlags.UpdateExplicitly: return UpdateSourceTrigger.Explicit; case (BindingBase.BindingFlags.UpdateOnLostFocus | BindingBase.BindingFlags.UpdateExplicitly): return UpdateSourceTrigger.Default; } Invariant.Assert(false, "Unexpected UpdateSourceTrigger value"); return UpdateSourceTrigger.Default; } set { base.CheckSealed(); BindingBase.BindingFlags flags = BindingBase.FlagsFrom(value); if (flags == BindingBase.BindingFlags.IllegalInput) { throw new InvalidEnumArgumentException("value", (int) value, typeof(UpdateSourceTrigger)); } base.ChangeFlagsWithinMask(BindingBase.BindingFlags.UpdateOnLostFocus | BindingBase.BindingFlags.UpdateExplicitly, flags); } } }
它的類型就是UpdateSourceTrigger枚舉,這個枚舉類型的值以下:
public enum UpdateSourceTrigger { Default, PropertyChanged, LostFocus, Explicit }
However, the default value for most dependency properties is System.Windows.Data.UpdateSourceTrigger.PropertyChanged,因此說,建立binding對象時雖然沒有設置這個屬性,但由於它有默認值,是PropertyChanged,以下:
//建立綁定
Binding bind = new Binding();
bind.Source = employee;
bind.Path = new PropertyPath("Name");
//bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; 這句能夠省略。
這樣看來,彷佛已經找到根了,此時Binding對象應該知道了要監聽PropertyChanged事件了,但實際上尚未具體訂閱上,到底在哪裏訂閱上PropertyChanged事件的呢?
Debug一下,發如今建立完上面的綁定以後, employee.PropertyChanged爲空,可見,此時還未訂閱。
employee.PropertyChanged
null
當向下執行完this.txt1.SetBinding(TextBox.TextProperty, bind) 這句後, employee.PropertyChanged不爲空了,說明此時已經訂閱上了。
employee.PropertyChanged
{Method = {Void OnPropertyChanged(System.Object, System.ComponentModel.PropertyChangedEventArgs)}}
base {System.MulticastDelegate}: {Method = {Void OnPropertyChanged(System.Object, System.ComponentModel.PropertyChangedEventArgs)}}
看看這個this.txt1.SetBinding的Reflector代碼,實際上仍是調用的BindingOperations類的SetBinding方法。
[RuntimeNameProperty("Name"), UsableDuringInitialization(true), StyleTypedProperty(Property="FocusVisualStyle", StyleTargetType=typeof(Control)), XmlLangProperty("Language")] public class FrameworkElement : UIElement, IFrameworkInputElement, IInputElement, ISupportInitialize, IHaveResources, IQueryAmbient { public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding) { return BindingOperations.SetBinding(this, dp, binding); } }
BindingOperations類的SetBinding方法代碼以下:
using MS.Internal.Data; using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Runtime; namespace System.Windows.Data { public static BindingExpressionBase SetBinding(DependencyObject target, DependencyProperty dp, BindingBase binding) { if (target == null) { throw new ArgumentNullException("target"); } if (dp == null) { throw new ArgumentNullException("dp"); } if (binding == null) { throw new ArgumentNullException("binding"); } BindingExpressionBase bindingExpressionBase = binding.CreateBindingExpression(target, dp); target.SetValue(dp, bindingExpressionBase); return bindingExpressionBase; } }
再跳進去查,仍是沒有發現具體訂閱的代碼,看來還藏得夠隱蔽的!算了,不查了,之後再說。
2013/9/3 補充,之因此找不到顯式的事件訂閱,多是使用了weakreference來實現更加高明的訂閱,學習中。