说不尽的MVVM(3) – 从通知属性说起
上篇我们体验了一个从事件处理程序到MVVM程序的转变,在最后也留下了一个问题:RaisePropertyChanged的原理是什么?今天我们来一探究竟。
通过上节做的小例子我们知道,仅仅修改ViewModel的数据,UI是不会发生变化的,在数据的值被更改后,我们要通知UI,让UI重新来获取数据,这种具备通知能力的属性,就是我们今天的主角——通知属性。
知识预备
阅读本文,我假定你具备以下知识
- C#、WPF 基础知识
- .NET 反编译器的使用
- 能看懂基本的IL代码(可选)
INotifyPropertyChanged接口
通过查阅MSDN我们知道,INotifyPropertyChanged接口有一个PropertyChanged 事件,数据绑定正是盯着这个事件,一旦触发,马上更新数据的显示,上一篇我们用的RaisePropertyChanged方法里面就是在触发这个事件

从MVVM light的源码我们可以看到,在触发PropertyChanged 事件时,会带一个PropertyChangedEventArgs 的参数,这个参数有一个string 类型的PropertyName 属性,在触发事件时,Data binding就会知道,是哪个属性发生了变化。现在我们去实现这个接口。

先准备一个这样的UI,

为UI建立ViewModel,引入MVVM light类库,使用他提供的RelayCommand。



然后把这个类派生自INotifyPropertyChanged,并把事件触发包装成RaisePropertyChanged方法,在Text属性的set分支调用。
把这个类作为MainWindow的DataContext,并写好数据绑定,运行程序,就可以看到我们想要的效果了,非常简单。
WPF在背后为我们做的事

会用了以后,我们来看看他的原理。为了探究这个问题,我们把PropertyChanged 事件展开,然后在add分支设一个断点(要注意的是,展开后,要通过这个私有的 _propertyChanged 来触发事件)。

启动程序,让程序命中断点,这时,我们按工具栏上的Code Map 按钮,把Call Stack显示在Code Map上(需要Visual Studio 2012 或以上)

好长啊。。。我们看看关键部分

找到add_PropertyChanged 的上一级调用者——位于WindowsBase.dll中的System.ComponentModel.PropertyChangedEventManager 的 StartListening 方法,用.NET反编译器打开,

我们看到,这个方法把PropertyChangedEventManager 类的 OnPropertyChanged 方法(代码挺长的,刚好100行,就不贴出来了)加到了ViewModel的PropertyChanged 事件中,这个方法会获取数据绑定的属性列表,并更新属性。
尽量少地触发PropertyChanged事件
我们刚刚也知道了,在这个事件触发后,执行的代码有100行(其实远远不止100行),因此应该只在必要时触发这个事件,什么时候算是必要呢?当然是属性的值发生改变的时候了,我们看看优化后的代码

在set的时候,我们做一个判断,只有在新的值和旧的值不同时,才触发PropertyChanged 事件,相比那100行代码来说,做一个判断还是挺值得的。
RaisePropertyChanged方法的三个版本


这个方法我们用得太多了,最常见的是这种接收一个字符串的,在这个方法里面直接用这个传进来的字符串new一个PropertyChangedEventArgs 送出去,很简单的操作,但简单的背后有一个问题。

我们知道,Visual Studio有一项重构的功能,当属性的名字更改后,会自动把所有地方Rename ,当我们Rename后,由于RaisePropertyChanged 的参数是字符串,Visual Studio 并不会为我们Rename ,而我们又忘记改的话,你就会知道,bug是怎样产生的,嘿嘿。。


为了解决这种忘改带来的问题,降低程序维护的成本,有人想出了用lambda表达式代替字符串的办法,这种方法非常安全,如果名字没改,是编译不过去的,但他以牺牲性能为代价,用反射获取属性名,在一些对性能要求高的地方就不适用了。
有没有两全其美的办法呢?在2012年9月12日之前我不敢说,但现在,答案是肯定的。既然我们要性能,那我们就从接收字符串的那个版本着手,那这个propertyName 从哪获取呢?我们去找编译器要,怎么要?请看代码。

MVVM light里面用了这种方法,把这个 propertyName 变成可选参数,然后给他贴上CallerMemberName 这个Attribute ,这样,在这个方法调用时,propertyName 就会被赋值为这个方法调用者的名字,如果我们在属性包装器里调用,我们可以得到这个属性的名字。

现在,我们可以不带任何参数去调用这个方法,并得到我们想要的结果。
INotifyPropertyChanging接口
说完了 INotifyPropertyChanged ,我们稍微提一下这个和他类似的接口,INotifyPropertyChanging 有一个 PropertyChanging 事件,用于通知属性即将发生改变,如果希望数据的更改可以回滚,实现 INotifyPropertyChanging 是一个不错的选择。

对这两个事件的使用,我们一般这么做
你可能会有的疑问
CallerMemberName 是什么东西? 背后是怎样的?性能如何?
这是 C# 5.0中的一个新功能,用法请参考:Caller Information (C# and Visual Basic)
这个Attribute 位于System.Runtime.CompilerServices 命名空间,顾名思义,这是编译器提供的一种功能。

我们写一段简单的代码,然后去看他的IL

从IL可以看到,CallerMemberName的值是在编译时确定的,编译器自动填充了这个参数,性能我们也可以放心。
说不尽的MVVM(3) – 从通知属性说起的更多相关文章
- 说不尽的MVVM(1) – Why MVVM
最近学的一篇课文<说不尽的狗>竟让我有了写<说不尽的MVVM>这一想法,事非亵渎,实出无奈.我在刚学WPF不久时听说有MVVM这种东西,做了下尝试,发现他能给程序的设计带来很大 ...
- WPF使用MVVM(一)-属性绑定
WPF使用MVVM(一)-属性绑定 简单介绍MVVM MVVM是Model(数据类型),View(界面),ViewModel(数据与界面之间的桥梁)的缩写,是一种编程模式,优点一劳永逸,初步增加一些逻 ...
- 说不尽的MVVM(4) – 发号施令的Command
知识预备 阅读本文,我假定你具备以下知识: C# WPF基础知识 知道WPF的命令 WPF相对WinForm加了一种Command的机制,对用户的操作进行更加灵活的处理,相信很多朋友知道并用过Rout ...
- 说不尽的MVVM(2) – MVVM初体验
知识预备 阅读本文,我假定你已经具备以下知识: C#.WPF基础知识 了解Lambda表达式和TPL 对事件驱动模型的了解 知道ICommand接口 发生了什么 某程序员接到一个需求,编写一个媒体渲染 ...
- 说不尽的MVVM(5) - 消息满天飞
知识预备 阅读本文,我假定你具备以下知识: C#和WPF基础知识 Lambda表达式 清楚ViewModel的职责 如果我们的程序需要弹出一个MessageBox,我们应该怎么做? 我见过不少人在Vi ...
- 属性通知之INotifyPropertyChanged
为什么后台绑定的值改变了前台不发生变化了? 针对这个初学者很容易弄错的问题,这里介绍一下INotifyPropertyChanged的用法 INotifyPropertyChanged:用于绑定属性更 ...
- Android之Bean属性通知类
调用: import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import android. ...
- MVVM框架下,WPF实现Datagrid里的全选和选择
最近的一个项目是用MVVM实现,在实现功能的时候,就会有一些东西,和以前有很大的区别,项目中就用到了常用的序号,就是在Datagrid里的一个字段,用checkbox来实现. 既然是MVVM,就要用到 ...
- WPF自定义控件与样式(14)-轻量MVVM模式实践
一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. MVVM是WPF中一个非 ...
随机推荐
- [2015hdu多校联赛补题]hdu5378 Leader in Tree Land
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5378 题意:给你一棵n个结点的有根树.因为是有根树,那么每个结点可以指定以它为根的子树(后面讨论的子树 ...
- RAID简述
RAID:Redundant Arrays of Independent Disks(独立冗余磁盘阵列) ①磁盘阵列是由很多价格较便宜的磁盘,组合成一个容量巨大的磁盘组,利用个别磁盘提供数据所产生加成 ...
- Android自定义ImageView实现图片圆形 ,椭圆和矩形圆角显示
Android中的ImageView只能显示矩形的图片,为了用户体验更多,Android实现圆角矩形,圆形或者椭圆等图形,一般通过自定义ImageView来实现,首先获取到图片的Bitmap,然后通过 ...
- eval语句(sql)类似asp
执行字符串的SQL语句,相当于asp中的eval ***** declare @sql nvarchar(500) set @sql='select * from sk_hzp_fk_prescri ...
- weblogic myeclipse小知识
新建域 http://jingyan.baidu.com/article/f7ff0bfc72904e2e27bb136f.html svn 上down下来一些新项目的时候没法添加到weblogic ...
- Linux上安装php
1.安装mysql http://blog.csdn.net/wy3552128/article/details/8143686 2.安装配置Apache http://blog.csdn.net ...
- Robolectric 配置
费了些工夫,已配好,按记录留记录 按官网操作http://robolectric.org/getting-started/ 1引包 testCompile "org.robolectric: ...
- java JVM垃圾回收机制
Java语言出来之前,大家都在拼命的写C或者C++的程序,而此时存在一个很大的矛盾,C++等语言创建对象要不断的去开辟空间,不用的时候有需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都 ...
- python 中 深拷贝和浅拷贝的理解
在总结 python 对象和引用的时候,想到其实 对于python的深拷贝和浅拷贝也可以很好对其的进行理解. 在python中,对象的赋值的其实就是对象的引用.也就是说,当创建一个对象,然后赋给另外一 ...
- (Python )运算符
这一节,将学习运算符,主要是算术运算符和逻辑运算符 1.算术运算符 除法运算,整数/整数=整数,浮点数/整数=浮点数,整数/浮点数=浮点数: >>> 17/35>>> ...