Model-View-ViewModel (MVVM) 设计模式描述了构建 WPF 或 Silverlight 应用程序的常用方法。它还是一款构建应用程序的强大工具,以及一种与开发人员讨论应用程序设计的通用语言。虽然 MVVM 确实很有用,但它发展时间不长,用户尚未形成正确的认识。在MVVM模式中,你需要一个为View量身定制的model,那么这个model实际上就是上图ViewModel。ViewModel包含所有UI所需要的接口和属性,这样只需要通过Binding使他们进行关联,就可以使二者之间达到松散耦合,所以这样一来,UI就可以由UI专业人员用design和blend来实现(当然很多效果还是需要用传统的制图软件),代码人员也可以专心写他的逻辑也业务代码,所以这样分工和协作变得更轻松、更愉快了,更漂亮的是View完全可以由(Unit/Automatic Test)所取代,所以单元测试也变得如此简单,这对于我们的开发人员和测试人员无疑是一个很好的解脱,同时也提高了系统的可测性、稳定性和维护性。数据绑定系统同时也提供了标准化的方式传输到视图的验证错误的输入的验证(但是个人觉得不是很好用,所以我们在实际的项目当中会写一套自己的验证框架)。
当然是用这个模式的时候,我们还要注意很多细节,这个是我们必须面对的,比如我们怎么实现ViewModel之间的通信、怎样用Attached Behavior实现特定命令操作、validation的自定义设置、延迟加载、性能优化、与传统技术的交互等等问题。
WPF项目
前面讲了一些MVVM的基本概念,那么我们在WPF中究竟怎么使用呢?那么下面就是我们在项目当中的具体使用,第一幅图就是整个项目的结构图,第二副图是View和ViewModel以及一些常用的UI操作(当然这里也可以更加细节化,具体更加实际决定) 。
上图就是整个项目的结构图
上图是View和ViewModel以及一些常用的UI操作
WinForm/ASP.NET上使用实践
WinForm和ASP.NET在使用这个模式的时候颇为类似,所以我们今天就用WinForm进行演示,为了能够更好的表达今天的主题,我们尽量把UI和逻辑做简单一些,这样便于我们要阐述的思想也能大道至简,首先我们先建立一个WinForm的项目,然后加入两个TextBox,五个Label,一个Button,控件的摆放位置如下所示:
然后我们新建三个文件夹,Common文件夹放一些公用的类,这里只放了ViewModelAttribute 类,其目的在于关联ViewModel和View的标示;View文件夹放置我们刚才新建的窗体,主要做UI的相关操作;ViewModel主要是放置一些ViewModel类(这些类是和View最好一一对应,提供一个定制化的Context).ClassDiagram1.cd是本应用程序的类图关系,Program.cs则承担了启动应用程序、关联View和ViewModel、处理整个应用程序的作用。
第二步就是该建立我们的ViewModel了,我们在WPF中通常是一个View有一个定制的ViewModel相对应(或者多个View对应一个ViewModel),我们今天做的这个WinForm的例子也不例外,请看下面ViewModel类图:
正如我们上图看到的那样, View model 类包含了一个_form1的变量,这个变量的目的就是通过form1来操作它的子控件,然后我们就可以对它及它的子控件进行一些操作,已完成我们需要的业务,正如WPF中的ViewModel 一样,它的主要任务是提供给View的一个定制化的Model,所以我们要在里面实现业务的操作以及提供View所需要的属性和接口。代码很简单,我这里就不多费口水,代码如下:
using System; using System.Windows.Forms; namespace MVVMInWinForm { public class ViewModel { //Form1的实例设置为View Form1 _form1; public ViewModel(Form1 form1) { _form1 = form1; /*通过搜索整个页面的Controls,然后对各个控件的事件进行订阅 */ foreach (Control item in _form1.Controls) { if (item is Button) { (item as Button).Click+=new EventHandler(ViewModel_Click); } } //启动窗体 Application.Run(_form1); } /// <summary> /// 具体的事件处理代码 /// </summary> protected void ViewModel_Click(object sender, EventArgs args) { string result =string.Empty; //具体操作 foreach (Control item in _form1.Controls) { if (item is TextBox) { result += (item as TextBox).Text; } } foreach (Control item in _form1.Controls) { /* 显示操作后的结果*/ if (item is Label && item.TabIndex ==5) { (item as Label).Text = result.ToString(); } } } } }
第三步是可选的步骤,你可以不用这个Attribute ,同样可以实现我们所要的功能,但加上它以后可以增强程序的灵活性。我们通过反射的形式来读取值,然后标示View是否被激活,如果是,则View和ViewModel进行关联,否则反之;
第四步很重要,Winform中不像WPF和Silverlight那样灵活且没有那么多的延伸特性和附加功能,所以只能通过外界代码的方式手动绑定View和ViewModel
[global::System.AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]sealed class ViewModelAttribute : System.Attribute
{
public ViewModelAttribute()
{
}
public ViewModelAttribute(bool Activated) {
this.Activated = Activated;
}
public bool Activated { get; set; }
}
using System;using System.Reflection;using System.Windows.Forms;using MVVMInWinForm.Attribute;namespace MVVMInWinForm{
static class Program
{
[STAThread]
static void Main() {
Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false);
Initialize();
}
private static void Initialize() {
Type t = typeof(Form1);
//通过Type获取Attributes的值
object[] attributes = t.GetCustomAttributes(typeof(ViewModelAttribute),
false);
//如果ViewModelAttribute的Activated值为true,把相关的View和ViewModel关联起来,否则不关联 if (attributes.Length > 0 && (attributes[0] as ViewModelAttribute).Activated == true)
{
Form1 form1 = new Form1(); ViewModel viewModel = new ViewModel(form1);
}
else
{
Application.Run(new Form1());
}
}
}
}
using System;using System.Windows.Forms;
using MVVMInWinForm.Attribute;namespace MVVMInWinForm{
//通过Attributes的方式来标记,true表示关联,false表示不关联,具体由主程序控制
[ViewModel(true)]
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}