知识预备

阅读本文,我假定你已经具备以下知识:

  • 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初体验的更多相关文章

  1. 【全面解禁!真正的Expression Blend实战开发技巧】第七章 MVVM初体验-在DataGrid行末添加按钮

    原文:[全面解禁!真正的Expression Blend实战开发技巧]第七章 MVVM初体验-在DataGrid行末添加按钮 博客更新较慢,先向各位读者说声抱歉.这一节讲解的依然是开发中经常遇到的一种 ...

  2. 【腾讯Bugly干货分享】基于 Webpack & Vue & Vue-Router 的 SPA 初体验

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57d13a57132ff21c38110186 导语 最近这几年的前端圈子,由于 ...

  3. 【Knockout.js 学习体验之旅】(1)ko初体验

    前言 什么,你现在还在看knockout.js?这货都已经落后主流一千年了!赶紧去学Angular.React啊,再不赶紧的话,他们也要变out了哦.身旁的90后小伙伴,嘴里还塞着山东的狗不理大蒜包, ...

  4. Knockout.js初体验

    前不久在网上看到一个轻量级MVVM js类库叫Knockout.js,觉得很好奇,搜了一下Knockout.js相关资料,也初体验了一下,顿时感觉这个类库的设计很有意思.接下来就搞清楚什么是Knock ...

  5. 基于 Webpack & Vue & Vue-Router 的 SPA 初体验

    基于 Webpack & Vue & Vue-Router 的 SPA 初体验 本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com ...

  6. "xaml+cs"桌面客户端跨平台初体验

    "Xaml+C#"桌面客户端跨平台初体验 前言   随着 .Net 5的到来,微软在 .Net 跨平台路上又开始了一个更高的起点.回顾.Net Core近几年的成果,可谓是让.Ne ...

  7. cucumber java从入门到精通(1)初体验

    cucumber java从入门到精通(1)初体验 cucumber在ruby环境下表现让人惊叹,作为BDD框架的先驱,cucumber后来被移植到了多平台,有cucumber-js以及我们今天要介绍 ...

  8. SSH初体验系列--Hibernate--2--crud操作

    Ok,今天比较详细的学习一下hibernate的C(create).R(read).U(update).D(delete) 相关api... 前言 Session: 是Hibernate持久化操作的基 ...

  9. .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验

    不知不觉,“.NET平台开源项目速览“系列文章已经15篇了,每一篇都非常受欢迎,可能技术水平不高,但足够入门了.虽然工作很忙,但还是会抽空把自己知道的,已经平时遇到的好的开源项目分享出来.今天就给大家 ...

  10. Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验

    Mac OS 虚拟机安装方法 最近把自己的电脑升级了一下SSD固态硬盘,总算是有容量安装Mac 虚拟机了!经过心碎的安装探索,尝试了国内外的各种安装方法,最后在youtube上找到了一个好方法. 简单 ...

随机推荐

  1. [2015hdu多校联赛补题]hdu5302 Connect the Graph

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5302 题意:给你一个无向图,它的边要么是黑色要么是白色,且图上的每个点最多与两个黑边两个白边相连.现在 ...

  2. eclipse使用技巧、快捷键

    1.alt+/  自动提示符,可以快速补整,提高效率.  输入Sysout,再按下alt+/,就可以打印了.  输入main,再按下alt+/,可以直接显示main方法. 2.ctrl+左键,快速进入 ...

  3. Codeforces Round #382 (Div. 2) 继续python作死 含树形DP

    A - Ostap and Grasshopper zz题能不能跳到  每次只能跳K步 不能跳到# 问能不能T-G  随便跳跳就可以了  第一次居然跳越界0.0  傻子哦  WA1 n,k = map ...

  4. 友盟分享SDK集成步骤

    1.官方注册appID. 2.menifest添加和声明umeng相关的activity以及appKey. 3. // 首先声明一个controller变量,由友盟服务工厂类直接取得友盟社交服务. m ...

  5. flex 正则 验证

    验证数字:^[-]*$ 验证n位的数字:^\d{n}$ 验证至少n位数字:^\d{n,}$ 验证m-n位的数字:^\d{m,n}$ 验证零和非零开头的数字:^(|[-][-]*)$ 验证有两位小数的正 ...

  6. GATK使用说明-GRCh38(Genome Reference Consortium)(二)

    Reference Genome Components 1. GRCh38 is special because it has alternate contigs that represent pop ...

  7. jQuery中.attr()和.prop()的区别

    之前学习jQuery的时候,学习到了两种取得标签的属性值的方法:一种是elemJobj.attr(),另一种是elemJobj.prop().而在学习JS的时候,只有一种方法elemObj.getAt ...

  8. Til the Cows Come Home

    Description Bessie is out in the field and wants to get back to the barn to get as much sleep as pos ...

  9. sublime构建执行go程序真爽

    1.安装gosublime插件 2.直接在sublime下调试运行共程序,不用去cmd了: 选择编译系统,编译,出现下面的模拟命令行,直接执行go的命令即可,比如go run process.go,结 ...

  10. 2.Unable to instantiate Action, templateAction, defined for 'template_list' in namespace '/'templateAction

    1.错误说没有命名空间'templateAction,但是在struts里写了这个,名字跟Action的名字是一样的,为什么会报这个错误 2.反复检查路径和名字,都没有问题 3.发现没有对其进行注入操 ...