说不尽的MVVM(2) – MVVM初体验
知识预备
阅读本文,我假定你已经具备以下知识:
- C#、WPF基础知识
- 了解Lambda表达式和TPL
- 对事件驱动模型的了解
- 知道ICommand接口
发生了什么



某程序员接到一个需求,编写一个媒体渲染器,效果如图,功能就是渲染媒体并在UI上面报告进度,看看他是怎么实现的。
事件驱动模型的实现
首先是界面

三个TextBlock分别负责显示状态、进度和百分号,还有一个按钮让用户启动渲染操作。

点击按钮时,异步调用Render方法。

在Render方法里面更新UI,模拟渲染操作,更新进度。
看起来不错,功能也正确,他正开心呢,突然电话响了。。。
为毛改需求啊!!!
客户最烦了,他居然嫌按钮太土,要用菜单,还说数字不够直观,要用进度条显示。没办法,谁让客户是上帝呢,改吧。

很快他就做出了这样的界面,但是这下惨了,改了界面后,后台代码那是"全国山河一片红"啊

那些什么renderButton progressDisplay 全挂了,无奈,只好重新写。
细数事件驱动模型的三宗罪
第一宗罪:逻辑代码是无辜的!!!
后台代码中频繁引用UI上的控件,导致逻辑与UI的耦合过于紧密,一旦UI发生改变,后台的逻辑代码就会躺枪,接着我们的程序员就会躺枪,逻辑根本没变啊,为毛要重写?
第二宗罪:太多与数据无关的代码
我们看到,上面的代码很大一部分在更新UI,虽然这是一个示例程序,处理业务逻辑的代码很少,但也说明一个问题,逻辑代码中确实存在与数据无关的代码,这些本来应该由UI来完成的工作,却跑到了逻辑代码里面,造成一种混乱。
第三宗罪:Dispatcher很忙
在工作线程中更新UI每次都要调用UI的Dispatcher,无论从代码还是性能方面都说不过去。
用MVVM升级你的程序
在了解MVVM之前,我们不妨先体验下MVVM
发现UI的本质
其实我们发现,无论UI怎么变,这个程序总是在完成这么一个功能,就是需求描述里面的:渲染媒体并在UI上面报告进度。因此我们可以对UI建模:

MVVM light toolkit
MVVM light 是MVVM框架中个人认为比较不错的一款,开源(http://mvvmlight.codeplex.com/ ),本文以他为例,对MVVM进行一个简单的介绍。首先理清三个词,MVVM:一种设计模式,MVVM light toolkit:MVVM的一种实现,MVVM light:这个只是MVVM light toolkit的名字啦。
OK,我们先去获取MVVM light.

在NuGet里面搜一下就可以搜到,第一个包含了类库和一些工具,比如code sinppet,第二个是单纯的类库,第三个是可移植类库(Portable Class Libraries),这里我们选择第三个。

我们把后台代码去掉,UI恢复最初的状态。,开始上MVVM模式。
建立View Model

根据上面的建模,我们可以设计出这么一个类

类里面的Render方法模拟渲染操作

由于正在进行渲染操作时,不能启动另一个操作,因此要有一个方法判断当前是否能进行渲染操作
将UI与数据关联

把我们建立好的ViewModel作为MainWindow的DataContext,然后再UI里面用数据绑定。

默认情况下,WPF Data binding的source就是自身的DataContext ,因此我们只需要为binding 指定Path。
命令
Button的Command在ViewModel 里面是一个RelayCommand(MVVM light对 ICommand 的一种实现),以
ICommand的形式暴露出来。

我们知道,ICommand接口有两个方法,Execute和CanExecute。因此在初始化RelayCommand的时候,指定这两个方法。
现在启动程序,点击按钮,后面的数据已经开始处理了,但我们发现两个问题:UI上面的进度不会更新,按钮在正在处理时没有变灰。下面我们来解决这两个问题。
将数据的更改通知界面
虽然ViewModel里面的数据更新了,但是UI上没有变化,原因是UI并不知道后面的数据发生了改变,因此,在数据改变后,我们应该立即通知UI,数据变了。


这里我们可以把MVVM light的VoewModelBase作为ViewModel的基类,在ViewModelBase里面有一个RaisePropertyChanged方法,这个方法可以告诉data binding,数据更新了,我们只需要在属性的set分支里面调用就可以了,参数是属性名(这方法在C#5.0下不需要参数)。
让按钮变灰
我们知道,命令能否执行与IsProcessing是有关联的,那么,当IsProcessing改变的时候,通知一下。

因为我们会在别的线程中更新这个属性,因此我们通过DispatcherHelper来执行Command的RaiseCanExecutrChanged方法。

DispatcherHelper使用之前要初始化,本例在ViewModel的构造函数中进行。
等等,我不要True和False
bool 类型直接绑定到界面会显示他的ToString()结果,显然这不是我们想要的,但我们可以做一个转换。

我们新建一个类,实现 IValueConverter 接口,由于只是一个单向的转换,因此只需要实现Convert。

然后在UI上面使用。

这样,我们就可以得到我们想要的结果了。
再也不怕改UI了

现在把UI改成新的UI,ViewModel根本不需要动,只需要把Data binding再写一次就可以了。

你可能会有的疑问
为什么把ViewModel放到DataContext?
为数据绑定提供数据源的方法有很多种,放到DataContext是一个用的比较多的方法。
这里给一个MSDN的参考资料:How to: Make Data Available for Binding in XAML
RaisePropertyChanged的实现原理是什么?
ViewModelBase实现了INotifyPropertyChanged接口,通过PropertyChanged事件通知Data binding(下一篇会讲述)。
貌似做了很多额外工作,就为了那点灵活?
为这样一个小项目上一个框架,确实是过度设计了,但在一个大一些的项目中,这种UI和逻辑的解耦带来的便利和灵活远远超过你的付出。
说不尽的MVVM(2) – MVVM初体验的更多相关文章
- 【全面解禁!真正的Expression Blend实战开发技巧】第七章 MVVM初体验-在DataGrid行末添加按钮
原文:[全面解禁!真正的Expression Blend实战开发技巧]第七章 MVVM初体验-在DataGrid行末添加按钮 博客更新较慢,先向各位读者说声抱歉.这一节讲解的依然是开发中经常遇到的一种 ...
- 【腾讯Bugly干货分享】基于 Webpack & Vue & Vue-Router 的 SPA 初体验
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57d13a57132ff21c38110186 导语 最近这几年的前端圈子,由于 ...
- 【Knockout.js 学习体验之旅】(1)ko初体验
前言 什么,你现在还在看knockout.js?这货都已经落后主流一千年了!赶紧去学Angular.React啊,再不赶紧的话,他们也要变out了哦.身旁的90后小伙伴,嘴里还塞着山东的狗不理大蒜包, ...
- Knockout.js初体验
前不久在网上看到一个轻量级MVVM js类库叫Knockout.js,觉得很好奇,搜了一下Knockout.js相关资料,也初体验了一下,顿时感觉这个类库的设计很有意思.接下来就搞清楚什么是Knock ...
- 基于 Webpack & Vue & Vue-Router 的 SPA 初体验
基于 Webpack & Vue & Vue-Router 的 SPA 初体验 本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com ...
- "xaml+cs"桌面客户端跨平台初体验
"Xaml+C#"桌面客户端跨平台初体验 前言 随着 .Net 5的到来,微软在 .Net 跨平台路上又开始了一个更高的起点.回顾.Net Core近几年的成果,可谓是让.Ne ...
- cucumber java从入门到精通(1)初体验
cucumber java从入门到精通(1)初体验 cucumber在ruby环境下表现让人惊叹,作为BDD框架的先驱,cucumber后来被移植到了多平台,有cucumber-js以及我们今天要介绍 ...
- SSH初体验系列--Hibernate--2--crud操作
Ok,今天比较详细的学习一下hibernate的C(create).R(read).U(update).D(delete) 相关api... 前言 Session: 是Hibernate持久化操作的基 ...
- .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
不知不觉,“.NET平台开源项目速览“系列文章已经15篇了,每一篇都非常受欢迎,可能技术水平不高,但足够入门了.虽然工作很忙,但还是会抽空把自己知道的,已经平时遇到的好的开源项目分享出来.今天就给大家 ...
- Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验
Mac OS 虚拟机安装方法 最近把自己的电脑升级了一下SSD固态硬盘,总算是有容量安装Mac 虚拟机了!经过心碎的安装探索,尝试了国内外的各种安装方法,最后在youtube上找到了一个好方法. 简单 ...
随机推荐
- 三星framebuffer驱动代码分析
一.驱动总体概述 本次的驱动代码是Samsung公司为s5pv210这款SoC编写的framebuffer驱动,对应于s5pv210中的内部外设Display Controller (FIMD)模块. ...
- socket编程相关的结构体和字节序转换、IP、PORT转换函数
注意:结构体之间不能直接进行强制转换, 必须先转换成指针类型才可以进行结构体间的类型转换, 这里需要明确的定义就是什么才叫强制转换. 强制转换是将内存中一段代码以另一种不同类型的方式进行解读, 因此转 ...
- asp.net GridView控件的列属性
BoundField 默认的数据绑定类型,通常用于显示普通文本 CheckBoxField 显示布尔类型的数据.绑定数据为TRUE时,复选框数据绑定列为选中状态:绑定数据为FALSE时,则显示未选中状 ...
- java并发的理解
我认为并发大体上分为两种情况 1,多个线程或者进程访问公共资源,比如12306 2,多个线程访问同一个实例变量,比如tomcat 多个请求的线程访问同一个单例bean,如果bean是有状态的,就可能出 ...
- Android 中的 WebView实现 Html5 标签网页加载
自Android 4.4起,Android中的WebView开始基于Chromium(谷歌浏览器)支持浏览器的一系列功能,webkit解析网页各个节点,这个改变,使得WebView的性能大幅度提升,并 ...
- 如果页面引用了外部JS代码,会被IE缓存的解决方法
在使用jquery时特别常见,明明修改的js,但是经常不生效,因为缓存,解决办法就是清除ie缓存,每次去点ie选项,太麻烦,网上有人用批处理去,还是每次去点,在页面中加入下面几行,就可以禁用缓存 &l ...
- XHTML跟HTML的区别
其实二者并没有什么区别,只是THTML的要求更加严格,比如说 1.XHTML 元素必须被正确地嵌套. 错误:<p><span>this is example.</p> ...
- 揭开HTTP网络协议神秘面纱系列(三)
HTTP首部字段有四种类型:通用首部字段,请求首部字段,响应首部字段,实体首部字段. 通用首部字段: 首部字段 说明 Cache-Control 控制缓存的行为 Connection 逐跳首部.连接的 ...
- dede列表页分页地址优化(不同url相同内容问题解决)<转自http://www.966266.com>
<注明,完全转自http://www.966266.com/seoblog/?p=75> 存在问题 DEDE默认分类分页地址存在不同URL相同内容问题,导致内容重复,对SEO非常不利.情况 ...
- Maven学习之 Settings
虽然天天在用,但是没有系统的学习过,总觉得别扭. 只能用于Java项目. 约定: repository 翻译成 仓库 build 翻译成 构建 build system 翻译成 构建系统 build ...