说不尽的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中一个非 ...
随机推荐
- ios开发 通讯录
一.通信录开发 通信录开发主要是获取用户手机中的联系人 通过获取用户的通信录,可以在应用中添加好友等 二.如何访问用户的通讯录 在iOS9之前,有2个框架可以访问用户的通讯录 目前需要适配iOS8,所 ...
- 最近写了一个红包雨的小功能,但感觉自己的js还有很多地方可以提高,望大神们可以帮忙指点一二
js部分 'use strict'; function RedEnvelope(options){ if(this === window){ return new RedEnvelope(option ...
- 常用UML模型简要小结
关系: 关联(组合,生命周期相同:聚合,物以类聚),依赖,泛化(继承),实现 还有 包含,细化复用已有用例:扩展,非必要主要的用例 图: 1.用例图:就是描述一个功能场景(集合),其实用例编写(前后置 ...
- centos6搭建VPN
1,检查是否开启PPP #cat /dev/ppp cat: /dev/ppp: No such device or address //表示已经开启 2,安装ppp和iptables #yum in ...
- B. Factory Repairs--cf627B(线段树)
http://codeforces.com/problemset/problem/627/B 题目大意: n代表天数 ,k代表每一次维修持续的天数,a代表维修过后每天能生产a件产品,b代表维修之前每 ...
- 拓扑排序 +Floyd(poj 1094)
题目:Sorting It All Out 题意:字母表前n个字母,有m组他们中的大小关系,判断n个字母是否构成唯一序列: 1.Sorted sequence determined after xxx ...
- MySql自动分区
自动分区需要开启MySql中的事件调度器,可以通过如下命令查看是否开启了调度器 show variables like '%scheduler%'; 如果没开启的话通过如下指令开启 ; 1.创建一个分 ...
- android中 EditTex t的 inputType 属性
//文本类型,多为大写.小写和数字符号 android:inputType="none" android:inputType="text" a ...
- JAVA中的重载和重写
重载(Overloading) (1) 方法重载是让类以统一的方式处理不同类型数据的一种手段.多个同名函数同时存在,具有不同的参数个数/类型. 重载(Overloading)是一个类中多态性的一种表现 ...
- (转)ASP.NET Mvc 2.0 - 1. Areas的创建与执行
转自:http://www.cnblogs.com/terrysun/archive/2010/04/13/1711218.html ASP.NET Mvc 2.0 - 1. Areas的创建与执行 ...