这篇小记源自于codeproject上的一篇文章 http://www.codeproject.com/Articles/100175/Model-View-ViewModel-MVVM-Explained

关于MVVM,它是一个对WPF和silverlight有很多好处的模式,如果你的开发伴随着下面的问题,那么你可以尝试尝试MVVM。

  • 你是否是与一个设计者合作一个项目并且设计和开发都很复杂而且几乎是要同时开始工作?
  • 你是否需要对你的方案进行彻底的单元测试?
  • 在你的团队中,你是否需要一个可重用的模块?
  • 你是否想改变你的界面而不影响到后台逻辑?

需要了解的概念

Model 模型

  模型-领域模型,Model描述了真实的数据或者说我们要处理的信息。例如联系人(包含了名字,手机号码,地址等)。

关于Model,我们要记住的一个关键就是,它承载的是信息,而不是操作这些信息的行为或服务。它没有责任去格式化文本以便在屏幕上显示的更漂亮,或者调用远程服务填充list。业务逻辑通常是和Model分开的,并且封装在对该实体有关联的类里。当然有例外:一些Model可能包含验证逻辑。

View 视图

  View是我们最亲近的也是终端用户唯一交互的东西。它展示数据给用户,并且可以自由的制定展示的方式。View可以关联行为,这些行为可以操作Model属性。

在MVVM中,View是主动的。被动的View是无需了解Model的,并且被控制器完全的支配,MVVM中的View包含了行为、事件和数据绑定,所以需要了解Model和View-Model。虽然这些事件和行为可能映射了属性、方法调用和命令,它仍然要处理自己的事件,是不会完全交给视图-模型(ViewModel)的。

  需要记住的是,View不是用来保持其状态的,它是用来同步视图-模型(viewmodel)的。

View Model 视图-模型

  viewmodel是三层中的关键,因为它介绍了表现分层,或者说保持视图分离实体的一些细微的差别的概念。实体承载数据,视图展示格式化后的日期,控制器扮演着两者之间的联络人,而不是让实体去了解用户视图的日期,然后改变日期的格式用来显示。控制器可能从实体获取输入并且把输入置给实体,或者与一个服务交互从实体检索数据然后转变成属性到视图。

  视图-模型 也提供出方法,命令以及其它用来维持视图的状态,操作Model作为View上行为的结果和触发View上的事件。

  很明显,视图-模型(viewmodel)是用来管理联系人列表的,它还提供了一个删除命令和一个确定是否允许删除的标志(就是这样保持视图的状态的),通常标志(CanDelete)是作为命令的一部分的,这里是由于silverlight3没有原生支持命令绑定,silverlight4支持了。

看一个详细点的实现例子

从这张图上可以看出IConfig代表的是配置服务,IService则代表一些要实现的接口。

视图(View)与视图-实体(ViewModel) 

  • 视图和视图-实体的交互是通过数据绑定,方法调用,属性,事件和消息。
  • 视图-实体提供的不只是实体,还有其它属性(状态信息,比如“is busy”指示器)和命令。
  • 视图处理它自有的事件,然后通过命令和视图-实体进行映射。
  • 视图-实体上的实体和属性是通过视图上的双向绑定进行更新的。

视图-实体(ViewModel)与实体(Model)

  • ViewModel不是只是用来负责Model的
  • ViewModel可以提供Model,或者和它有关的属性用来数据绑定。
  • ViewModel可以包含服务接口,配置数据等,以便获取和操作它提供给View的属性。

View与ViewModel对应关系

  一般大多数开发者都认可一个View一个ViewModel,没有必要多个ViewModel对一个View,想一下关注点分离的概念,这就很容易理解。

一个View可能由其它views组成,它们都拥有自己的viewmodel。ViewModels在必要的时候也可能由其它viewmodels组成。

虽然一个view应该只有一个viewmodel,但是一个viewmodel可能被用于多个views(想象下向导功能,你会看到很多view,但是它们绑定的都是同一个viewmodel来驱动过程)。

一个基本的MVVM框架需要2方面

  1. 一个继承DependencyObject 的或者实现INotifyPropertyChanged 接口的类支持数据绑定。
  2. 一些命令的支持。

概念讲了这么多,还是用完整的例子来阐述吧

我们要实现一个展示列表,一个显示详细信息的框,一个删除按钮。上面就是呈现给客户的View。接下来我们需要一个Model。

/// <summary>
/// Represents a contact
/// </summary>
public class ContactModel : BaseINPC
{
private string _firstName; public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
} private string _lastName; public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
} public string FullName
{
get { return string.Format("{0} {1}", FirstName, LastName); }
} private string _phoneNumber; public string PhoneNumber
{
get { return _phoneNumber; }
set
{
_phoneNumber = value;
RaisePropertyChanged("PhoneNumber");
}
} public override bool Equals(object obj)
{
return obj is ContactModel && ((ContactModel) obj).FullName.Equals(FullName);
} public override int GetHashCode()
{
return FullName.GetHashCode();
}
}

它所继承的类 BaseINPC

 public abstract class BaseINPC : INotifyPropertyChanged
{
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged; if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
} public event PropertyChangedEventHandler PropertyChanged;
}

以上就是一个MVVM的model该有的样子,和平常的model相比,它多了步实现 INotifyPropertyChanged  接口,每当属性被赋值的时候执行RaisePropertyChanged事件以便通知View属性被更改了。

而Model和View-Model怎样结合?

就需要视图-模型(View Model),同样继承了BaseINPC。

 public class ContactViewModel : BaseINPC
{
public ContactViewModel()
{
//联系人集合
Contacts = new ObservableCollection<ContactModel>();
//实例化对数据进行CRUD的服务
Service = new Service();
//获取数据(模拟异步方式)
Service.GetContacts(_PopulateContacts);
//实例化一个删除命令,这里可以看到传了三个参数,
//操作数据的服务Service,确定按钮是否可以执行的方法
//删除动作执行后的数据重新获取方法
Delete = new DeleteCommand(
Service,
()=>CanDelete,
contact =>
{
CurrentContact = null;
Service.GetContacts(_PopulateContacts);
});
} private void _PopulateContacts(IEnumerable<ContactModel> contacts)
{
Contacts.Clear();
foreach(var contact in contacts)
{
Contacts.Add(contact);
}
} public IService Service { get; set; } public bool CanDelete
{
get { return _currentContact != null; }
} //提供给View绑定的联系人集合
public ObservableCollection<ContactModel> Contacts { get; set; } //提供给View的删除Button的绑定命令
public DeleteCommand Delete { get; set; } private ContactModel _currentContact;
//当前选中的联系人model
public ContactModel CurrentContact
{
get { return _currentContact; }
set
{
_currentContact = value;
//当联系人Model改变的时候通知View
RaisePropertyChanged("CurrentContact");
}
}
}

最后看View的XAML是如何绑定的

 <Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Contacts}" DisplayMemberPath="FullName"
SelectedItem="{Binding CurrentContact,Mode=TwoWay}"/>
<Button Grid.Column="1" Content=" Delete Selected "
Command="{Binding Delete}"
CommandParameter="{Binding CurrentContact}"> </Button>
</Grid>

上面的已经很简明了,不过在SL3里Button是不支持这种直接绑定命令的

SL3中的绑定方式

<UserControl x:Class="MVVMExample.ListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:MVVMExample">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Contacts}" DisplayMemberPath="FullName" SelectedItem="{Binding CurrentContact,Mode=TwoWay}"/>
<Button Grid.Column="1" Content=" Delete Selected " IsEnabled="{Binding CanDelete}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<local:CommandTrigger Command="Delete"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</UserControl>

多添加了个命名空间,用到了System.Windows.Interactivity,还要实现一个命令触发器

 public class CommandTrigger : TriggerAction<Button>
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof (string),
typeof (CommandTrigger),
null); public string Command
{
get
{
return (string) GetValue(CommandProperty);
}
set
{
SetValue(CommandProperty, value);
}
} protected override void Invoke(object parameter)
{
var dc = AssociatedObject.DataContext;
if (dc != null)
{
var commandProperty =
(from p in dc.GetType().GetProperties() where p.Name.Equals(Command) select p).FirstOrDefault(); if (commandProperty != null)
{
var command = commandProperty.GetValue(dc, null) as ICommand; if (command != null && command.CanExecute(null))
{
command.Execute(((ContactViewModel)dc).CurrentContact);
}
}
}
}
}

可以看到里面注册了一个Command依赖属性,XAML里也对此属性进行了赋值,触发此事件会进入上面的Invoke方法,找到命令执行。

源码下载:  SL4版本

SL3版本

如果你觉得有所帮助就顶一个吧,后面我会写些MVVMLight的小记,共勉。

MVVM小记的更多相关文章

  1. 【MVVMLight小记】一.快速搭建一个基于MVVMLight的silverlight小程序

    写了篇MVVM小记http://www.cnblogs.com/whosedream/p/mvvmnote1.html,说好要写点MVVMLight的东西,所以接着写,以便和大家共勉. 我假设你已经有 ...

  2. WPF ControllTemplate Triggers小记 - 简书

    原文:WPF ControllTemplate Triggers小记 - 简书 WPF中,样式模板中如果定义EventTrigger事件方式实现动画.那么需要注意两点: 1.对于绑定的属性的Event ...

  3. Vue.js 和 MVVM 小细节

    MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式,其核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自 ...

  4. 领域驱动和MVVM应用于UWP开发的一些思考

    领域驱动和MVVM应用于UWP开发的一些思考 0x00 起因 有段时间没写博客了,其实最近本来是根据梳理的MSDN上的资料(UWP开发目录整理)有条不紊的进行UWP学习的.学习中有了心得体会或遇到了问 ...

  5. MVVM框架从WPF移植到UWP遇到的问题和解决方法

    MVVM框架从WPF移植到UWP遇到的问题和解决方法 0x00 起因 这几天开始学习UWP了,之前有WPF经验,所以总体感觉还可以,看了一些基础概念和主题,写了几个测试程序,突然想起来了前一段时间在W ...

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

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

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

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

  8. MVVM设计模式和WPF中的实现(四)事件绑定

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

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

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

随机推荐

  1. CPU卡与M1卡的区别

    简单来讲CPU卡比M1卡更加安全.扩展性更好.支持更多应用   CPU卡 M1 操作系统 带有COS系统 无COS系统 硬件加密模块 硬件DES运算模块 无实现算法的硬件加密模块 算法支持 标准DES ...

  2. Mysql常用的一些技巧命令

    1.统计指定数据库下表的数量 mysql > use information_schema; mysql > SELECT count(TABLE_NAME) FROM informati ...

  3. x01.os.17: 换心术

    在 linux 中编译 linux, 于是 linux 便有了再生能力.这不同于自然界的缓慢进化,可用神速来形容.—— 和强大的 windows 相抗衡,便是证明! 我在 ubuntu 中的编译方法如 ...

  4. Struts2文件上传,以及各种注意事项

    首先肯定是要配置好struts2环境,这个在另一篇<struts2环境搭建,以及一个简单实例>里已经讲过了 首先是网页部分,upload_file.jsp <%@ page lang ...

  5. Linux内核源码树建立加载hello模块

    在加载模块之前,书中说要先建立内核源码树,那么,如何建立内核源码树呢? 首先,要先知道你的OS的内核版本,用uname -r可以查得到 在/url/src/目录下可以看到对应的版本目录 如果没有可以用 ...

  6. TCMalloc 对MYSQL 性能 优化的分析

    虽然经过研究发现TCMalloc不适合我们现有的游戏框架,但意外收获发现TCMalloc可以大幅度提高MYSQL 性能及内存占用,这里给出配置及测试的结果: 1.配置 关于TCMalloc的安装,在& ...

  7. PS色调均化滤镜的快捷实现(C#源代码)。

    photoshop色调均化功能通常是在进行修片处理前期比较常用的功能之一,其对扩展图像的对比度,增强视觉效果有一定的作用.在很多课本或者文章中,也称这种处理为灰度均衡化.直方图均衡化等等.算法原理都是 ...

  8. 阿里云+wordpress搭建个人博客网站【小白专用的图文教程】

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...

  9. Flex(flash)检测摄像头的3种状态(是否被占用,没安装摄像头,正常)

    在视频程序的编写过程中,我们经常要使用摄像头,在使用摄像头前有必要对摄像头的现有状态做个检测: 1.被占用 2.没安装摄像头 3.正常 camera=Camera.getCamera();       ...

  10. 把web项目部署到tomocat上

    版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 常识 1 War包 2 Tomcat服务器 配置Java运行环境 1 下载并安装JDK 2 设置JDK环境变量 3 验证是否JD ...