在代码中经常见到这个接口,它里面有什么?它的作用是什么?它和依赖属性有什么关系?
下面就来总结回答这三个问题。
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#endregionnamespace 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.它的作用是什么?
首先创建一个不用这个接口的例子。
创建一个Employee.cs类。
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
在MainWindow.xaml.cs里将Employee的Name属性和TextBox的Text属性绑定起来。
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{ ////// Interaction logic for MainWindow.xaml /// 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"; } }}
运行起来,效果如下:
也就是说,给textbox绑定了数据源Employee对象之后,我修改了Employee对象的Name属性,但在界面上并没有显示出来,界面上显示的还是原来的初始值。
这时,可以让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
实际上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 { , , , }
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来实现更加高明的订阅,学习中。