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。
public class ValueChangedEventArgs : System.EventArgs
{
public readonly int oldValue;
public readonly int newValue; public ValueChangedEventArgs(int oldOne, int newOne)
{
oldValue = oldOne;
newValue = newOne;
}
} public class Commodity
{
string symbol;
int volume; public event EventHandler<ValueChangedEventArgs> ValueChanged; protected void OnValueChanged(ValueChangedEventArgs e)
{
if (ValueChanged != null)
{
ValueChanged(this, e);
}
} public int Volume
{
get { return volume; }
set
{
if (volume == value) { return; } int oldValue = volume;
volume = value; OnValueChanged(new ValueChangedEventArgs(oldValue, volume);
}
} class Test
{
static void Main()
{
Commodity c = new Commodity(“”);
c.Volume = ;
c.ValueChanged += commodity_ValueChanged;
c.Volume = ;
} static void commodity_ValueChanged(object sender, ValueChangedEventArgs e)
{
Console.WriteLine(“Volume Changed!”);
}
}
运行上面的例子我们会发现,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语句会被编译器展开成类似下面的语句:
PropertyChangedEventHandler _propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { _propertyChanged += value; }
remove { _propertyChanged -= value; }
}
所以,对于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方法:
protected override void StartListening(object source)
{
INotifyPropertyChanged typedSource = (INotifyPropertyChanged)source;
typedSource.PropertyChanged += new PropertyChangedEventHandler(OnPropertyChanged);
}
不管怎么说,这个实现简直太直观了。如果你想看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操作。
废话不多说,直接上源码:
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(TextBlock),
new FrameworkPropertyMetadata(
string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnTextChanged),
new CoerceValueCallback(CoerceText)));
应该就是加粗的那一行用来处理PropertyChanged事件。
简单验证下,在Visual Studio里设置一个断点:PresentationFramework.dll!System.Windows.Controls.TextBlock.OnTextChanged
我这里看到的堆栈调用是:
通过Visual Studio自带的Locals窗口,你还可以看到传入的newText值是多少!
完!
Data Binding和INotifyPropertyChanged是如何协调工作的?的更多相关文章
- .NET: WPF Data Binding
WPF是分离UI和Logic的最佳工具,不同于Window Form的事件驱动原理,WPF采用的是数据驱动,让UI成为了Logic的附属,达到分离的效果. 本篇主要讲讲wpf的精华:data bind ...
- Caliburn.Micro 杰的入门教程2 ,了解Data Binding 和 Events(翻译)
Caliburn.Micro 杰的入门教程1(翻译)Caliburn.Micro 杰的入门教程2 ,了解Data Binding 和 Events(翻译)Caliburn.Micro 杰的入门教程3, ...
- Optimizing Performance: Data Binding(zz)
Optimizing Performance: Data Binding .NET Framework 4.5 Other Versions Windows Presentation Founda ...
- WP8.1 Study5:Data binding数据绑定
一.数据绑定 最简单的编程UI控件的方法是写自己的数据来获取和设置控件的属性,e.g. , textBox1.Text = "Hello, world"; 但在复杂的应用程序,这样 ...
- Data Binding in WPF
http://msdn.microsoft.com/en-us/magazine/cc163299.aspx#S1 Data Binding in WPF John Papa Code downl ...
- Data Binding(数据绑定)用户指南
1)介绍 这篇文章介绍了如何使用Data Binding库来写声明的layouts文件,并且用最少的代码来绑定你的app逻辑和layouts文件. Data Binding库不仅灵活而且广泛兼容- 它 ...
- Android Data Binding实战(一)
在今年Google I/O大会上,Google推出Design Library库的同时也推出了Android Data Binding,那么什么是Data Binding?其名曰数据绑定,使用它我们可 ...
- Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)
题外话:本篇是对之前那篇的重排版.并拆分成两篇,免得没了看的兴趣. 前言 在Spring Framework官方文档中,这三者是放到一起讲的,但没有解释为什么放到一起.大概是默认了读者都是有相关经验的 ...
- Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion
本篇太乱,请移步: Spring Framework 官方文档学习(四)之Validation.Data Binding.Type Conversion(一) 写了删删了写,反复几次,对自己的描述很不 ...
随机推荐
- easyx与VS2015
7.10 之前在文件头将__acrt_iob_func重定义&__iob_func,在格子涂色的程序中解决了问题:然而在俄罗斯方块的程序中出现了更多的问题,好像是FILE在其他外部依赖项cor ...
- jdbcTemplate的Dao层封装
package com.easyrail.base.dao; import java.io.Serializable; import java.lang.reflect.Field; import j ...
- IaaS/PaaS/SaaS
如果你是一个网站站长,想要建立一个网站.不采用云服务,你所需要的投入大概是:买服务器,安装服务器软件,编写网站程序.现在你追随潮流,采用流行的云计算,如果你采用IaaS服务,那么意味着你就不用自己买服 ...
- [WPF]DataGridHyperlinkColumn网址过长TextTrimming无效
<DataGridHyperlinkColumn Binding="{Binding source}" Header="来源"> <DataG ...
- 关于codeblock中一些常用的快捷键(搬运)
关于codeblock中一些常用的快捷键(搬运) codeblock作为一个常用的C/C++编译器,是我最常用的一款编译器,但也因为常用,所以有时为了更加快速的操作难免会用到一些快捷键,但是因为我本身 ...
- shell中创建mysql库和执行sql脚本
以前执行oracle脚本都是放到plsql中执行 mysql 脚本执行: (1).先创建一个worlddb库 (2).导入sql脚本: 这就ok啦,哈哈.
- php empty,isset,is_null比较(差异与异同)
php empty,isset,is_null比较(差异与异同) http://www.cnblogs.com/chengmo/archive/2010/10/18/1854258.html
- 《精通C#》第十六章-动态类型和动态语言运行时-第一节至第四节
在.Net4.0中引入了一个关键字dynamic,这是一个动态类型关键字.Net中还有一个关键字是var,这是一个隐式类型,可以定义本地变量,此时var所代表的实际的数据类型有编译器在初次分配时决定, ...
- ACCESS中计算日均值
如图所示,现有时间数据的时间字段是精确到时分秒的,现在需要计算PM2.5的日平均值,因此在查询时需要过滤时间字段的格式,去掉时分秒部分,只提取年月日部分. 查找资料,发现一般用CONVERT()函数实 ...
- 关于app的具体实施
鉴于我们小组做的app是关于在线做题和游戏相融合的,所以,我会先学习UI设计,毕竟好的UI设计不仅会给用户耳目一新的体验,同时还会让用户愿意去包容一些小BUG,但如果你的软件做的非常好,功能提供的很全 ...