问题引入
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. sqlalchemy学习-- 重要参数

    Base = declarative_base 基类: 1.存储表 2.建立class-Table 的映射关系 engine = create_engine('mysql://root:root@lo ...

  2. JAVAWEB 一一 userweb1(原生,非servlet版)

    创建数据库和表 首先,创建一个web项目 然后引入jar包 创建jsp页面 创建包 创建接口 实现类 详细内容 首先创建一个登陆页面 login.jsp <%@ page language=&q ...

  3. Structs复习 OGNL

    Dominmodel只有传 User.age 类似的这种Structs才能帮创建对象 Dominmodel User里必须有空的构造方法 OGNL:OBJECT GRAPHIC NAVAGATION ...

  4. windows下一分钟配置ngnix实现HLS m3u8点播

    1. 下载 nginx-1.5.10 for windows 2. 修改配置文件nginx-1.5.10\conf\nginx.conf,增加以下行到最后一个"}"的前一行: lo ...

  5. python 如何注释

    一.单行注释     单行注释以#开头,例如:    print 6 #输出6 二.多行注释     (Python的注释只有针对于单行的注释(用#),这是一种变通的方法)     多行注释用三引号' ...

  6. Python之路 - 网络编程之粘包

    Python之路 - 网络编程之粘包 粘包

  7. python基础学习 Day19 面向对象的三大特性之多态、封装 property的用法(1)

    一.课前内容回顾 继承作用:提高代码的重用性(要继承父类的子类都实现相同的方法:抽象类.接口) 继承解释:当你开始编写两个类的时候,出现了重复的代码,通过继承来简化代码,把重复的代码放在父类中. 单继 ...

  8. php mysql 查询判断周几

    $where .= " and (DAYOFWEEK( from_unixtime(`px_time`, '%Y-%m-%d')) = 1)";  //周日从1开始

  9. ie兼容,手机端兼容问题

    兼容性: 1.ie6,7不能兼容border-radius:若需要可以用图片的方式进行模拟. 2.ie6, 7中如果兄弟元素没有给左浮动,而本身给了右浮动,将会出现塌陷(也就是掉下去):如需要可以将右 ...

  10. orthodb

    1.数据库 orthodb数据: odb10v0_levels.tab.gz: NCBI taxonomy nodes where Ortho DB orthologous groups (OGs) ...