说不尽的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中一个非 ...
随机推荐
- 动端逐渐出了许多的移动端的框架,比如Sencha Touch、JQTouch、Jquery-moblie、jqMobi等等。这些框架都有优缺点,不同的框架应用在不同的项目中。现简单阐述一下各框架的优缺点:
移动前端工作的那些事---前端制作之微信小技巧篇 (2013-11-15 15:20) 转载▼ 标签: it css3/javascript html5 webapp 手机网站搭建 分类: 前端制 ...
- Docker常用操作
启动容器并安装package docker run xxx apt-get -y xxx 其中-y要加上避免无法交互 批量删除容器 docker ps -a | awk '{print $1}' |x ...
- Multiple annotations found at this line: - The content of element type "mapper" must match "EMPTY". - Attribute "namespace" must be declared for element type "mapper".
今天在mybatis的mapper映射配置文件中遇到了这样的问题,困扰了我3个小时: Multiple annotations found at this line: - The content of ...
- JSON基本用法
JSON基本用法 2016-08-10 16:42:19 JSON的全称是“JavaScript Object Notation”,意思是JavaScript对象表示法,它是一种基于文本,独立于语 ...
- instancetype、id、NSObject的联系和区别
1.id和instancetype都能省去具体类型,提高代码的通用性.而NSObject *则没有这种功能. 2.instancetype只能用于方法的返回类型,而id用处和NSObject *类似. ...
- [z] error C2471 错误
error C2471: 无法更新程序数据库“d:/Work/ Project/FBReader/debug/vc90.pdb” fatal error C1083: 无法打开程序数据库文件:“d:/ ...
- 设置阿里云maven中央仓库的settings.xml
本来想找一个可用的设置文件,结果乱七八糟的,干脆自己做了一个,同时还放上了Spring的SNAPSHOT和MILESTONE/RELEASE仓库,希望能帮到一些人. <?xml version= ...
- live555 直播arm-linux视频
live555例程testOnDemandRTSPServer.cpp启动一个流服务器 首先启动使用环境, TaskScheduler* scheduler = BasicTaskScheduler: ...
- 1016. Phone Bills (25)
分析: 模拟题,提交无数次WA,注意几点: 1.如果某人没有有效通话记录,则不输出该人的信息,在此WA15次,题目看了N遍也没出现啊. 2.通话时间钱的计算:假设我们计算time1到time2的账单: ...
- 如何评估ETL的数据加载时间
简述如何评估大型ETL数据加载时间. 答:评估一个大型的ETL的数据加载时间是一件很复杂的事情.数据加载分为两类,一类是初次加载,另一类是增量加载. 在数据仓库正式投入使用时,需要进行一次初次加载,而 ...