上篇我们体验了一个从事件处理程序到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. vsnprintf

    http://www.cplusplus.com/reference/cstdio/vsnprintf/ int vsnprintf (char * s, size_t n, const char * ...

  2. Howto: 如何将ArcGIS Server缓存移动到新服务器

     Howto: 如何将ArcGIS Server缓存移动到新服务器 文章编号: 33686 软件: ArcGIS Server 9.2, 9.3, 9.3.1 操作系统: Windows 2000, ...

  3. udp通信的原理---makefile文件

    由于UDP通信不需要事先建立连接,因此不需要TCP中的connect函数. 服务器端的步骤如下: 1. socket:      建立一个socket 2. bind:          将这个soc ...

  4. PTA week10

    // // main.c // Bonus2 // // Created by 余南龙 on 2016/11/27. // Copyright © 2016年 余南龙. All rights rese ...

  5. [转载]Altium规则详解及设置

    在Altium中进行PCB的设计时,经常会使用规则(Rule)来进行限定以确定线宽孔径等参数,此文将简要的介绍规则中的一些标量代表了什么. Electrical——电气规则.安全间距,线网连接等 Ro ...

  6. background-size的两个属性:cover和contain

    两种都不会造成图片失真,其中: (1)cover:相当于宽度等于元素的宽度,高度设为auto: (2)contain:相当于高度等于元素的高度,宽度设为auto: 例如:设置一个高度和宽度都为300p ...

  7. 图解 & 深入浅出 JavaWeb:Servlet必会必知

    从[JavaEE 要懂的小事] Http相关,一直想写点Web开发相关的.最近项目接口开发紧,还有准备新的九月份战斗.JDK IO源码就隔一段落,温故知新看看Servlet & JSP 相关. ...

  8. shell脚本学习(一)

    Shell脚本最常用于系统管理工作,或者用于结合现有的程序以完成小型.特定的工作. Shell的特点有: 1. 简单性 2. 可移植性 3. 开发容易 [什么是shell] 简单点理解,就是系统跟计算 ...

  9. Asp.Net MVC4入门指南(1): 入门介绍

    前言 本教程将为您讲解使用微软的Visual Studio Express 2012或Visual Web Developer 2010 Express Service Pack 1 来建立一个ASP ...

  10. 和为S的两个数字

    /*  * 和为S的两个数字  * 题目描述  * 输入一个递增排序的数组和一个数字S,在数组中查找两个数  * 使得他们的和正好是S,如果有多对数字的和等于S,输出两个  * 数的乘积最小的.  * ...