打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
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值是多少!

完!

 

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Silverlight 4 数据绑定
Silverlight实例教程 - Validation客户端同步数据验证
白话 WPF/SL 绑定(Binding)
深入浅出WPF之Binding的使用(一)
闲话WPF之十五(WPF的数据处理 [2])
C# 扩展集合ObservableCollection使集合在添加、删除、值变更后触发事件
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服