上篇我们体验了一个从事件处理程序到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) – 从通知属性说起的更多相关文章

  1. 说不尽的MVVM(1) – Why MVVM

    最近学的一篇课文<说不尽的狗>竟让我有了写<说不尽的MVVM>这一想法,事非亵渎,实出无奈.我在刚学WPF不久时听说有MVVM这种东西,做了下尝试,发现他能给程序的设计带来很大 ...

  2. WPF使用MVVM(一)-属性绑定

    WPF使用MVVM(一)-属性绑定 简单介绍MVVM MVVM是Model(数据类型),View(界面),ViewModel(数据与界面之间的桥梁)的缩写,是一种编程模式,优点一劳永逸,初步增加一些逻 ...

  3. 说不尽的MVVM(4) – 发号施令的Command

    知识预备 阅读本文,我假定你具备以下知识: C# WPF基础知识 知道WPF的命令 WPF相对WinForm加了一种Command的机制,对用户的操作进行更加灵活的处理,相信很多朋友知道并用过Rout ...

  4. 说不尽的MVVM(2) – MVVM初体验

    知识预备 阅读本文,我假定你已经具备以下知识: C#.WPF基础知识 了解Lambda表达式和TPL 对事件驱动模型的了解 知道ICommand接口 发生了什么 某程序员接到一个需求,编写一个媒体渲染 ...

  5. 说不尽的MVVM(5) - 消息满天飞

    知识预备 阅读本文,我假定你具备以下知识: C#和WPF基础知识 Lambda表达式 清楚ViewModel的职责 如果我们的程序需要弹出一个MessageBox,我们应该怎么做? 我见过不少人在Vi ...

  6. 属性通知之INotifyPropertyChanged

    为什么后台绑定的值改变了前台不发生变化了? 针对这个初学者很容易弄错的问题,这里介绍一下INotifyPropertyChanged的用法 INotifyPropertyChanged:用于绑定属性更 ...

  7. Android之Bean属性通知类

    调用: import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import android. ...

  8. MVVM框架下,WPF实现Datagrid里的全选和选择

    最近的一个项目是用MVVM实现,在实现功能的时候,就会有一些东西,和以前有很大的区别,项目中就用到了常用的序号,就是在Datagrid里的一个字段,用checkbox来实现. 既然是MVVM,就要用到 ...

  9. WPF自定义控件与样式(14)-轻量MVVM模式实践

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. MVVM是WPF中一个非 ...

随机推荐

  1. MYSQL服务器字符集设置

    修改默认字符集 vi /etc/my.cnf 在[mysqld]下面加入default-character-set=utf8 在[client]下面加入default-character-set=ut ...

  2. MongoDB中的连接池

    参见 http://www.cnblogs.com/huangfox/archive/2012/04/01/2428947.html

  3. Android-Service生命周期

    Service的基本概念,以及Service的生命周期: 一.Service的基本概念: 一个Service就是应用程序的组件,可以在后台长期跑,或者是为其他的应用提供功能上的支持.Service一般 ...

  4. NGUI 便捷的显示与隐藏界面

    所有的UI都继承自class UIBase,可以把UI做成预设体,同个场景下,读取预设体时,将子UI对象都保存在类似Dictionary<UIType, UIBase>这样的结构中,这样的 ...

  5. AngularJS学习--- AngularJS中模板链接和图像 ng-src step6

    接上一篇文章,本文将主要介绍angularjs中的模板链接,和图像显示? 首先,切换分支,启动项目: git checkout step- npm start 1.效果 相较于前一篇文章,明显感觉多了 ...

  6. iOS.StaticLibrary.1-avoid-duplicate-symbol-in-static-library[draft]

    Avoid duplicate symbol in static library and its customer 发布static library给使用者使用.在实际的工程实践中,iOS静态库一般会 ...

  7. [Office][C#] NPOI、OpenXML SDK、OpenOffice.org SDK 写入资料到 EXCEL 档案[转]

    原文地址:http://www.dotblogs.com.tw/chou/archive/2010/04/29/14912.aspx 一.簡介 要將資料寫入 EXCEL 檔案有許多的方法,但假如電腦不 ...

  8. iOS UILabel根据字符串长度自动适应宽度和高度

    //这个frame是初设的,没关系,后面还会重新设置其size.     UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0,0, ...

  9. linux笔记-硬链接和符号链接

    硬链接:指多个路径名(不同目录下的不同文件名)指向同一个硬盘数据,用其中的随便哪个文件打开修改数据,都会在其他文件打开中更新, 原因就是硬链接和“原”文件inode相同,每增加或者删除一个链接,链接计 ...

  10. ubuntu14 下配置cgi

    1.安装apache2 sudo apt-get install apache2 2.配置apache2 vim /etc/apache2/sites-enabled/ 000-default.con ...