MVVM小记
这篇小记源自于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方面
- 一个继承
DependencyObject的或者实现INotifyPropertyChanged接口的类支持数据绑定。 - 一些命令的支持。
概念讲了这么多,还是用完整的例子来阐述吧

我们要实现一个展示列表,一个显示详细信息的框,一个删除按钮。上面就是呈现给客户的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版本
如果你觉得有所帮助就顶一个吧,后面我会写些MVVMLight的小记,共勉。
MVVM小记的更多相关文章
- 【MVVMLight小记】一.快速搭建一个基于MVVMLight的silverlight小程序
写了篇MVVM小记http://www.cnblogs.com/whosedream/p/mvvmnote1.html,说好要写点MVVMLight的东西,所以接着写,以便和大家共勉. 我假设你已经有 ...
- WPF ControllTemplate Triggers小记 - 简书
原文:WPF ControllTemplate Triggers小记 - 简书 WPF中,样式模板中如果定义EventTrigger事件方式实现动画.那么需要注意两点: 1.对于绑定的属性的Event ...
- Vue.js 和 MVVM 小细节
MVVM 是Model-View-ViewModel 的缩写,它是一种基于前端开发的架构模式,其核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自 ...
- 领域驱动和MVVM应用于UWP开发的一些思考
领域驱动和MVVM应用于UWP开发的一些思考 0x00 起因 有段时间没写博客了,其实最近本来是根据梳理的MSDN上的资料(UWP开发目录整理)有条不紊的进行UWP学习的.学习中有了心得体会或遇到了问 ...
- MVVM框架从WPF移植到UWP遇到的问题和解决方法
MVVM框架从WPF移植到UWP遇到的问题和解决方法 0x00 起因 这几天开始学习UWP了,之前有WPF经验,所以总体感觉还可以,看了一些基础概念和主题,写了几个测试程序,突然想起来了前一段时间在W ...
- MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息
MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...
- MVVM模式解析和在WPF中的实现(五)View和ViewModel的通信
MVVM模式解析和在WPF中的实现(五) View和ViewModel的通信 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 M ...
- MVVM设计模式和WPF中的实现(四)事件绑定
MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...
- MVVM模式解析和在WPF中的实现(三)命令绑定
MVVM模式解析和在WPF中的实现(三) 命令绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...
随机推荐
- Sqlite学习笔记(四)&&SQLite-WAL原理
Sqlite学习笔记(三)&&WAL性能测试中列出了几种典型场景下WAL的性能数据,了解到WAL确实有性能优势,这篇文章将会详细分析WAL的原理,做到知其然,更要知其所以然. WAL是 ...
- W3School-CSS测验
The only way to survive was to enjoy the good moments and not dwell too much on the bad. 生活,就应该享受美好的 ...
- C语言 时间函数的学习和总结
一直都是以简单的time_t t,time(&t),ctime(&t)来表示时间,后来要以时间为日志文件的名字时,就有点蒙逼了.学习一下. tm结构: struct tm { ...
- python抓取网页中图片并保存到本地
#-*-coding:utf-8-*- import os import uuid import urllib2 import cookielib '''获取文件后缀名''' def get_file ...
- Altium Designer 出现错误提示(警告)adding items to hidden net GND/VCC
一般出现这个提示,不是错误. 可以取消net 网格标号 这样就不会报这个警告了. 还可以设置规则,不让它报告. 点击确定,但是再次打开工程时有得警告这个错误了.我想,还是取消NET标注.
- To create my first app in iOS with Xcode(在Xcode创建我的第一个iOS app )
To create my first app in iOS create the project. In the welcome window, click “Create a new Xcode p ...
- 零拷贝传输(zero-copy transfer)——sendfile()
做Web服务器时通常需要将文件传送出去,其中一种方法是通过定义一个buffer每次读取文件发送给接收端.大多数服务器会选择sendfile的方式,nginx实现时就是采用这种方式.对于并发搞得服务器性 ...
- 淘宝美工一站式:淘宝ps高级美工技巧视频教程,HTML代码学习【教程下载
视频免费下载地址:http://www.fu83.cn/thread-243-1-1.html
- POJ1985Cow Marathon[树的直径]
Cow Marathon Time Limit: 2000MS Memory Limit: 30000K Total Submissions: 5117 Accepted: 2492 Case ...
- Object.Destroy慎用
Object.Destory Destory(Object)并没有立刻,马上,及时的删除这个Object. 举例 在使用NGUI的Table或Grid进行布局时,就需要注意了:尽量不要使用Destro ...