问题引入
1 场景一:团队辛辛苦苦完成了一个项目,抱着激动的心情去给用户做demo,而用户给你的反馈是UI很不满意,要重新修改,否则拒绝验收。大规模修改UI,晴天霹雳!
2 场景二:产品在一家客户上线运行反应不错,公司准备扩大营销市场,寻求更多的客户,此时,不同客户对UI纷纷提出修改意见,众口难调,但是老总发话,客户是上帝!
问题出来了,按照传统的开发模式是基于CodeBehind这样的方式,UI总是和业务逻辑紧密耦合在一起, UI修改,无法避免的业务逻辑修改随之而来,这无非就是我们老生常谈的解耦问题,有没有办法做到UI层剥离出逻辑层呢?MVVM模式为你排忧解难。

一 什么是MVVM模式
MVVM(Model-View-ViewModel)是专为WPF和SilverLight设计的开发模式, 与之类似的有Asp.net程序对应的MVC模式, WinForm程序对应的MVP, 关于MVC, MVP此处不展开论述,详情参考http://msdn.microsoft.com/zh-cn/library/dd381412(v=vs.98).aspx。 但在MVC和MVP模式中, View层都具有很多代码逻辑, 最简单的例子是在MVC中当界面发生交互时View去调用Controler中的某个方法,所以 并没有真正意义上实现View与ViewModel完全分离。
WPF真正引人入胜、使之与WinForm泾渭分明的特点就是——“数据驱动界面”,何为“数据驱动界面” , 与传统的“事件驱动见面”相比较,数据编程了核心,UI处于从属地位;数据是底层、是心脏,数据变了作为表层的UI就会跟着变、将数据展现给用户;如果用户修改了UI元素上的值,相当于透过UI元素直接修改了底层的数据;围绕着这个核心,WPF准备了很多概念相当前卫的技术,其中包括为界面准备的XAML、为底层数据准备的Dependency Property & Binding和为消息传递准备的Routed Event & Command。 
Binding和Command技术的出现,也为MVVM模式成为WPF平台下一个优秀的开发模式奠定了基础。通过Binding,可以绑定一个View的Property到ViewModel, ViewModel 对象被设置为视图的 DataContex,如果属性值在 ViewModel 更改,这些新值自动传播到通过数据绑定的视图,实现在ViewModel里可以不通过编写任何逻辑代码就直接更新View,做到View与ViewModel之间的完全松耦合,关于Binding,想了解更多可参见 Data and WPF: Customize Data Display with Data Binding and WPF "。 同样,如果没有WPF中的Command, MVVM也很难展示出它的强大力量,ViewModel可将Command暴露给View, 使得View可以消费command中对应的逻辑功能,对于不熟悉command的朋友,可以参考这篇文章Advanced WPF: Understanding Routed Events and Commands in WPF。
下面简要介绍一下MVVM每个模块的主要职责
1) View主要用于界面呈现,与用户输入设备进行交互,在code-Behind中还可以些一些UI的逻辑的,比如一些丰富的动画效果,或者直接设置某个元素的样式等,此外,设置View层的DataContext为对于的ViewModel层的逻辑也是写在code-Behind中。
2) ViewModel是MVVM架构中最重要的部分,ViewModel中包含属性,命令,方法,事件,属性验证等逻辑,用于逻辑实现,负责View与Model之间的通信。
3) Model就是我们常说的数据模型,用于数据的构造,数据驱动, 主要提供基础实体的属性以及每个属性的验证逻辑。
MVVM中各个模块的交互方式如图所示:

二 为什么要使用MVVM模式
MVVM模式的引入能给我们带来什么优势呢?相信这是大多数学习MVVM的人关心的一个主要问题。
首先我们应该清楚地认识到,MVVM不是适用于任何的项目开发,一个项目是否要上一套框架取决于项目本身的规模和性质,盲目的使用开发模式可能会引起过度开发,通常情况下,企业级的WPF应用软件建议使用,主要优势下面将展开详细阐述。
1团队层面 统一了项目团队的思维方式,也改变了开发方式,由于View与ViewModel之间的松耦合关系,我们可以轻易做到开发团队与设计团队的明确分工,开发团队可以专注于创建功能强大的 ViewModel 类,而设计团队能够熟练运用Blend等工具能为程序员输出用户友好的试图View的XAML文件。而且,随着项目的进行,不断会有新的成员加入,一个清晰的项目设计模式,能够很大程度地减少他熟悉项目的所需时间,并能够规范的进行接下来的开发维护工作。
2 架构层面 项目架构更加稳定,模块之间松散的耦合关系使得模块之间的相互依赖性大大降低,这也就意味着项目的扩展性得到了提高,即使以后需要加一些新的模块,或者实现模块的注入,我们也能做到最小的改动,从而保证项目的稳定。
3 代码层面MVVM的引入也使得项目本身变得模块清晰化,条理化,有助于我们更好地区分哪些逻辑是属于UI操作,哪些逻辑是业务操作,增强了代码的可读性、可测性。对于ViewModel层,Views和Unit tests是两个不同类型的消费者,应用程序中的主要交互逻辑处于ViewModel层,这样,在完成ViewModel之后,我们完全可以有理由相信,我们可以对ViewModel进行单元测试,因为它不依赖于任何UI控件,从这个角度看,似乎UnitTest相比于View而言具备更大的消费能力。

三 详解ViewModel
 ViewModel是MVVM架构中最重要的部分,负责View与Model直接的通信,对于ViewModel的理解是掌握MVVM的关键,下面我们针对ViewModel进行详细剖析。 
1 ViewModel的属性ViewModel的属性是View数据的来源,但ViewModel层不能是Model层的简单封装,ViewModel层也不能是View层的简单映射。ViewModel的属性可由三部分组成:一部分是Model的复制属性;另一部分用于控制UI状态。例如Button属性的Disable属性,当操作完成时可以通过这个属性更改通知View做相应的UI变换或者后面提到的事件通知;第三部分是一些方法的参数,可以将这些方法的参数设置成相应的属性绑定到View中的某个控件,然后在执行方法的时候获取这些属性,所以一般方法不含参数。
2 ViewModel的命令 ViewModel中的命令用于接受View的用户输入,并做相应的处理。我们也可以通过方法实现相同的功能。
3 ViewModel的事件  ViewModel中的事件主要用来通知View做相应的UI变换。它一般在一个处理完成之后触发,随后需要View做出相应的非业务的操作。所以一般ViewModel中的事件的订阅者只是View,除非其他自定义的非View类之间的交互。
4 View及ViewModel交互模式
在View与ViewModel模型之间进行双向的联系的主要方式是通过数据绑定。当正确地使用该设计模式后,每一个View除了纯净的XAML和非常少量的后置代码外不会再包含任何东西,彻底地做到了界面展示和业务逻辑的分离,让程序员更加专注于代码的编写。
ViewModel也能用来容纳View的状态以及执行View需要的任何命令。
因为WPF内置了Command模式,对于像Button控件之类的UI元素来说都有一个Command的属性,它是WPF所定义的ICommand类型。可以把这些命令放到ViewModel中并以公有属性的形式暴露出来,这样就可以让View对其进行绑定。这极其强大,因为它可以把ModelView中的可执行代码绑定到窗体的Button上。

四 MVVM实践
理论知识已经准备充分,现在是检验真理的时刻,以下是使用了Model-View-ViewModel 设计模式的s世上最简单的WPF应用程序例子,简单加法计算器。
1 代码结构如下图:

2 CaculatorModel类:
public class CaculatorModel
    {
        public int Num1 { get; set; }
        public int Num2 { get; set; }
        public int Result { get; set; }
}

3 ICommand类型的基类DelegateCommand

using System;
using System.Windows.Input;
namespace MVVMDemo.Commands
{
    public class DelegateCommand:ICommand
    {
        public DelegateCommand(Action<object> executeCommand, Func<object, bool> canExecuteCommand)
        {
            this.executeCommand = executeCommand;
            this.canExecuteCommand = canExecuteCommand;
        }
        // The specific ExecuteCommand aciton will come from the ViewModel, the same as CanExecuteCommand
        private Action<object> executeCommand;

public Action<object> ExecuteCommand
        {
            get { return executeCommand; }
            set { executeCommand = value; }
        }

private Func<object, bool> canExecuteCommand;

public Func<object, bool> CanExecuteCommand
        {
            get { return canExecuteCommand; }
            set { canExecuteCommand = value; }
        }

public event EventHandler CanExecuteChanged;

public bool CanExecute(object parameter)
        {
            if (CanExecuteCommand != null)
            {
                return this.CanExecuteCommand(parameter);
            }
            else
            {
                return true;
            }
        }

public void Execute(object parameter)
        {
            if (this.ExecuteCommand != null) this.ExecuteCommand(parameter);
        }

public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, EventArgs.Empty);
            }
        }
    }
}
注:ICommand中有两个方法CanExecute和Execute必须实现,这两个方法分别对应着当Command调用时判断是否能执行和具体执行逻辑。

4 ViewModelBase类

using System.ComponentModel;

namespace MVVM.ViewModel
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

public void RaisePropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}
注:ViewModelBase实现了接口INotifyPropertyChanged, 在该接口中有一个PropertyChanged事件, 当ViewModel中的Property改变时,允许触发PropertyChanged事件,继而重新绑定数据到UI上。
5 CaculatorViewModel类
using System.Windows.Input;
using MVVM.Model;
using MVVMDemo.Commands;

namespace MVVM.ViewModel
{
    public class CaculatorViewModel:ViewModelBase
    {
        #region Fields

private int num1;
        private int num2;
        private int result;
        private CaculatorModel model;

#endregion

#region Properties

public int Num1
        {
            get 
            {
                return num1;
            }
            set
            {
                num1 = value;
                this.RaisePropertyChanged("Num1");
            }
        }

public int Num2
        {
            get
            {
                return num2;
            }
            set
            {
                num2 = value;
                this.RaisePropertyChanged("Num2");
            }
        }

public int Result
        {
            get
            {
                return result;
            }
            set
            {
                result = value;
                this.RaisePropertyChanged("Result");
            }
        }

#endregion

#region Commands

public ICommand CaculateCommand{get;set;}
        public ICommand ClearCommand { get; set; }

#endregion

#region Methods

public void Add(object param)
        {
            Result = Num1 + Num2;
        }

public void Clear(object param)
        {
            Result = 0;
            Num1 = 0;
            Num2 = 0;
        }

public void InitilizeModelData()
        {
            // In gernal, the data comes from database
            var model = new CaculatorModel()
            {
                Num1 = 1,
                Num2 = 1,
                Result = 2
            };

Num1 = model.Num1;
            Num2 = model.Num2;
            Result = model.Result;
        }

public CaculatorViewModel()
        {
            CaculateCommand = new DelegateCommand(Add, null);
            ClearCommand  = new DelegateCommand(Clear, null);

InitilizeModelData();
        }

#endregion
    }
}
6 简单计算器的UI

该View所对应的XAML文件如下:
<Window x:Class="MVVM.View.CaculatorView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="CaculatorView" Height="300" Width="682">
    <Grid Width="596">
        <TextBox Height="23" HorizontalAlignment="Left" Margin="41,90,0,0" Name="txtNum1" VerticalAlignment="Top" Width="120" Text="{Binding Num1}"/>
        <TextBox Height="25" HorizontalAlignment="Left" Margin="195,88,0,0" Name="txtNum2" VerticalAlignment="Top" Width="120" Text="{Binding Num2}"/>
        <Label Content="+" Height="28" HorizontalAlignment="Left" Margin="167,88,0,0" Name="label1" VerticalAlignment="Top" />
        <TextBox Height="25" HorizontalAlignment="Left" Margin="364,88,0,0" Name="textBox5" VerticalAlignment="Top" Width="120"  Text="{Binding Result}"/>
        <Button Content"=" Height="23" HorizontalAlignment="Left" Margin="328,90,0,0" Name="button1" VerticalAlignment="Top" Width="28" Command="{Binding CaculateCommand}" />
        <Button Content="Clear" Height="26" HorizontalAlignment="Left" Margin="501,88,0,0" Name="button2" VerticalAlignment="Top" Width="45" Command="{Binding ClearCommand}" />
    </Grid>
</Window>
6. View的Code-Behind
using System.Windows;
using MVVM.ViewModel;

namespace MVVM.View
{
    /// <summary>
    /// CaculatorView.xaml 的Ì?交?互£¤逻?辑-
    /// </summary>
    public partial class CaculatorView : Window
    {
        public CaculatorView()
        {
            InitializeComponent();
            this.DataContext = new CaculatorViewModel();
        }
    }
}
注:这里讲View的DataContext设为CaculatorViewModel实例,至此,View和ViewModel建立关联。

什么是MVVM模式的更多相关文章

  1. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  2. MVVM模式解析和在WPF中的实现(五)View和ViewModel的通信

    MVVM模式解析和在WPF中的实现(五) View和ViewModel的通信 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 M ...

  3. MVVM模式解析和在WPF中的实现(三)命令绑定

    MVVM模式解析和在WPF中的实现(三) 命令绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  4. MVVM模式和在WPF中的实现(二)数据绑定

    MVVM模式解析和在WPF中的实现(二) 数据绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  5. MVVM模式和在WPF中的实现(一)MVVM模式简介

    MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...

  6. [转载]MVVM模式原理分析及实践

    没有找到很好的MVVM模式介绍文章,简单找了一篇,分享一下.MVVM实现了UI\UE设计师(Expression Blend 4设计界面)和软件工程师的合理分工,在SilverLight.WPF.Wi ...

  7. dynamic-css 动态 CSS 库,使得你可以借助 MVVM 模式动态生成和更新 css,从 js 事件和 css 选择器的苦海中脱离出来

    dynamic-css 使得你可以借助 MVVM 模式动态生成和更新 css,从而将本插件到来之前,打散.嵌套在 js 中的修改样式的代码剥离出来.比如你要做元素跟随鼠标移动,或者根据滚动条位置的变化 ...

  8. mvc mvp mvvm模式的区别

    mvc模式中,Model不依赖于View,但是View是依赖于Model的,m和v没有进行完全的分离,三者之间是单向的操作 mvp模式中,m和v之间的交互是双向的,m和v完全分离,m和v的交互是通过P ...

  9. 转:界面之下:还原真实的 MVC、MVP、MVVM 模式

    前言 做客户端开发.前端开发对MVC.MVP.MVVM这些名词不了解也应该大致听过,都是为了解决图形界面应用程序复杂性管理问题而产生的应用架构模式.网上很多文章关于这方面的讨论比较杂乱,各种MV*模式 ...

  10. 由项目浅谈JS中MVVM模式

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.    背景 最近项目原因使用了durandal.js和knock ...

随机推荐

  1. 18.异常.md

    目录 1.try...catch 2.异常了的继承机制 2.1基本概念 2.2常用异常 2.3多异常捕获 2.4获取异常信息 2.5finally回收资源 2.6Checked异常和Runtime异常 ...

  2. Bad owner or permissions on .ssh/config的解决

    出处:http://blog.csdn.net/notzuonotdied/article/details/69668519 在.ssh目录,执行以下命令行: sudo chmod 600 confi ...

  3. Applese走迷宫-bfs

    链接:https://ac.nowcoder.com/acm/contest/330/C来源:牛客网 题目描述 精通程序设计的 Applese 双写了一个游戏. 在这个游戏中,它被困在了一个 n×mn ...

  4. js高级-递归调用

    函数调用自身 求1-100的和 var sum = 0; for(var i = 1; i<=100; i++){ sum += i } console.log(sum) //自己写的递归 va ...

  5. cdnbest如何配置ssl证书

    cdnbest添加ssl证书有三种方式: 一.第一种在站点设置中添加: 点打开,加入证书后点提交 可以点检测功能检查证书是否有效,打勾说明证书是有效的 二. 第二种是在域名记录里添加: 如下图点击,添 ...

  6. cdnbest补充api

    1.应用防火墙---防CC 添加|修改 请求地址: {api_dir}/firewall/anticc 请求方式: PUT 请求参数: frcquency string 触发频率 例:low(低) | ...

  7. maven项目下出现java.lang.ClassNotFoundException: ContextLoader异常

    原因:出现此异常是因为tomcat的webapp目录下没有lib文件. 解决方案: 1.右键点击项目--选择Properties选择Deployment Assembly,在右边点击Add按钮,在弹出 ...

  8. 【翻译】View Frustum Culling --2 Geometric Approach – Extracting the Planes

    在上一篇中,我们知道了视锥体的形状,并且也确定了我们进行裁剪时的步骤.那我们接下来要走的就是确定视锥体的六个平面: near, far, top, bottom, left and right 2.计 ...

  9. IMPDP NETWORK_LINK参数

    在<[IMPDP]同一数据库实例不同用户间数据迁移复制—— NETWORK_LINK参数>(http://space.itpub.net/519536/viewspace-631571)文 ...

  10. 在Plesk安装PHP的Memcached扩展

    默认情况下,Plesk的PHP没有Memcached扩展,需要自己安装. Plesk-without-memcached,在Plesk下安装PHP Memcached扩展 PHP Memcache是​ ...