一. 摘要
随着WPF/Silverlight等技术的出现,一种新的模式出现在大家面前,那就是MVVM,提到这个模式,大家也许感觉很迷惑,也许会不屑于故,也许你会说我现在做项目都形成了自己的框架了,为什么还要花费额外的时间和精力去学习这个MVVM模式呀?这个模式到底能给我们带来什么样的好处?那么我们本篇也会简单提及,由于这个会在WPF 基础到企业应用系列索引 里详细讲解,所以今天只是走马观花了解一下基本概念。
众所周知,这个模式大量用在WPF/Silverlight上,但没有听说过用在Windows Form、ASP.NET等传统技术上。正是基于这个原因,我也对此进行了一些研究,做了一些Demo,但总体感觉在其他技术上不太适合,就一直没有管它了,直至有一天看到Bechir Bejaoui 也有这种思想,并且他打算在WinForm和ASP.NET项目当中使用,这使我感到有必要写一篇这方面的文章看看到底是否可行,正是基于这个原因,今天我们就用一个简单的例子来谈谈它在WinForm平台的应用,看看大家对这个有什么看法.
二. 本文提纲
· 1.摘要
· 2.本文提纲
· 3.MVVM基本介绍
· 4.WPF/Silverlight项目中如何使用
· 5.WinForm/ASP.NET上使用实践
· 6.本文总结
三. MVVM基本介绍
WPF和Silverlight来了,它带来了3D、动画、音频视频……这导致了UI的变化将更加细节化和可定制化。同时,在技术层面上,WPF和Silverlight也带来了诸如Binding、 Dependency Property、Routed Events、Command、Attached Behavior(间接实现)、DataTemplate、ControlTemplate等新特性。我们怎样才能立足于原有的技术框架、把WPF/Silverlight的新特性揉合进去,以应对客户日益复杂且多变的需求呢?那么MVVM模式就是一个不错的选择。
在MVVM模式中,你需要一个为View量身定制的model,那么这个model实际上就是上图ViewModel。ViewModel包含所有UI所需要的接口和属性,这样只需要通过Binding使他们进行关联,就可以使二者之间达到松散耦合,所以这样一来,UI就可以由UI专业人员用design和blend来实现(当然很多效果还是需要用传统的制图软件),代码人员也可以专心写他的逻辑也业务代码,所以这样分工和协作变得更轻松、更愉快了,更漂亮的是View完全可以由(Unit/Automatic Test)所取代,所以单元测试也变得如此简单,这对于我们的开发人员和测试人员无疑是一个很好的解脱,同时也提高了系统的可测性、稳定性和维护性。数据绑定系统同时也提供了标准化的方式传输到视图的验证错误的输入的验证(但是个人觉得不是很好用,所以我们在实际的项目当中会写一套自己的验证框架)。
当然是用这个模式的时候,我们还要注意很多细节,这个是我们必须面对的,比如我们怎么实现ViewModel之间的通信、怎样用Attached Behavior实现特定命令操作、validation的自定义设置、延迟加载、性能优化、与传统技术的交互等等问题。
四. WPF/Silverlight项目中如何使用
1.我们的WPF/Silverlight项目
前面讲了一些MVVM的基本概念,那么我们在WPF和Silverlight中究竟怎么使用呢?那么下面就是我们在项目当中的具体使用,第一幅图就是整个项目的结构图,第二副图是View和ViewModel以及一些常用的UI操作(当然这里也可以更加细节化,具体更加实际决定) 。
上图就是整个项目的结构图
上图是View和ViewModel以及一些常用的UI操作
由于这个不是今天的重点,所以就不在这里阐述,以后有空会单独讲解。
2.我对架构的一些看法
前两天我在一篇叫“ [征集].NET软件设计经验----期待你的参与”的帖子中回复了我对项目的一点微薄见解,或许很多是自己的一些错误观念,但是不交流的话,永远就不知道自己是对是错了,所以这里也贴出来,当然架构设计也不要过度,只要适宜即可,这也是我现在最大的体会。
回顾过去,由于项目和公司原因,自己零零碎碎的什么都在做,什么都不精,也不敢在这里谈经验,只是自己的体会,望各位高手见谅,其他平台很菜,就不说了,对.Net平台感觉刚入门,从WinForm、ASP.NET(ASP.NET MVC一个项目没做完就被终止)到WPF和Silverlight,以至于到现在做的Windows Azure,不论是采用什么技术,最基本的东西都不会变,比如对数据库、文件和其他设备的访问、对日志和异常的处理、对数据结构的处理、对报表的展现、对打印的实现、对性能的提升、对数据的处理、对用户友好等等。
那么这些项目也为我们积累了不少经验,有技术上的也有其他方面的:
* 为了应付项目需求的不断变化和项目的可扩展性,我们也会引入OO和设计模式;
* 为了解除各模块和组件的耦合,我们也会利用IOC的思想解耦;
* 为了让逻辑代码清晰且没有其他代码的干扰,我们也会采用AOP的方式进行代码重组;
* 为了解决诸如莫名奇妙的内存错误、Invoke异常,我们也会去研究晦涩难懂的CLR&IL,问题出了,你负责这个项目,必须得解决。
* 为了使项目的开发速度更快且更方便,我们也会引入ORM思想来加快项目的开发速度和可维护性;
* 为了更好组织各层开发,隔开耦合,我们也会采用MVC、MVP、MVVM模式;
* 为了提升用户的响应速度,我们会采用AJAX的方式来实现;采用异步编程去解决用户漫长的等待问题;
* 为了降低系统的负载同时提高用户的响应能力,我们也会采用MSMQ或者SSB来组织消息队列;
* 为了企业业务整合,用户只需要登录一次就可以访问所有相互信任的应用系统,我们也会写一套自己的SSO;
* 各种应用程序、各种服务的交错会让我们感到手足无措,为了规范各系统的接口,提供一个统一的交互平台,我们也会采用SOA;
* 针对大量数据量实时处理、较高计算,解除用户等待的漫长等待,为了降低服务器的负担和提高速度,我们也会自己写一套缓存,并保证缓存的正确的更新与去除;
* 为了把产品做好,我们也会不断优化技术直至达到期望的效果;
* 为了能做好外包项目,我们会不需要任何高深技术,首先给客户期望的效果;
* 为了能得到客户满意老板好评,我们也会学会如何交流,要明白最终是要得到客户的满意度,要得到money;
其实归根到底就是要分清关系,理清思绪,既要处理好与机器的关系,也要处理好与人的关系,只有这样才能把产品或者项目做成功,项目做成功了,才有后面的发展和晋升,我也在不断学习当中,始终感觉自己是菜鸟,回头望去猛然发现自己又回到了原点,只有不断学习,不断进步了!
五,WinForm/ASP.NET上使用实践
1, 第一步
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、处理整个应用程序的作用。
2,第二步
第二步就是该建立我们的ViewModel了,我们在WPF中通常是一个View有一个定制的ViewModel相对应(或者多个View对应一个ViewModel),我们今天做的这个WinForm的例子也不例外,请看下面ViewModel类图:
正如我们上图看到的那样, View model 类包含了一个_form1的变量,这个变量的目的就是通过form1来操作它的子控件,然后我们就可以对它及它的子控件进行一些操作,已完成我们需要的业务,正如WPF/Silverlight中的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();
}
}
}
}
}
3,第三步
第三步是可选的步骤,你可以不用这个Attribute ,同样可以实现我们所要的功能,但加上它以后可以增强程序的灵活性。我们通过反射的形式来读取值,然后标示View是否被激活,如果是,则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; }
}
4,第四步
第四步很重要,Winform中不像WPF和Silverlight那样灵活且没有那么多的延伸特性和附加功能,所以只能通过外界代码的方式手动绑定View和ViewModel
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());
}
}
}
}
5,第五步
第五步我们需要给Form1类加上一个Attribute,其目的是增加View和ViewModel的灵活性,使他们可以松散耦合、灵活配置,如下代码:
using System;
using System.Windows.Forms;
using MVVMInWinForm.Attribute;
namespace MVVMInWinForm
{
//通过Attributes的方式来标记,true表示关联,false表示不关联,具体由主程序控制
[ViewModel(true)]
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}
现在我们就可以运行程序,在两个输入框分别输入中文名字和英文名字,然后按下“显示”按钮,就得到如下的结果:
[ViewModel(true)] 上面执行的结果是正确的,那么现在改为[ViewModel(false)] 呢? 先看下面代码和运行效果:
//通过Attributes的方式来标记,true表示关联,false表示不关联,具体由主程序控制
[ViewModel(false)]
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
执行的结果是没有命令被触发, 因为View Model 没有被激活,也就是说View和ViewModel没有进行关联。 在ASP.NET中实现此模式我就不多进行阐述了,如果大家有兴趣也可以做一个Demo试试,原理基本一样。
六.本文总结:
今天我们从MVVM基本介绍讲起,然后概览了我们在WPF/Silverlight项目中如何使用这个模式以及在WinForm/ASP.NET上的使用实践,通过今天的这篇文章,我个人觉得MVVM模式是WPF/Silverlight下的产物,所以运用起来也非常得心应手,在其他平台上使用有诸多局限。在WinForm/ASP.NET上的使用并不能带来什么好处,反而增加了工作量和成本系数,所以我个人还是不太推荐这样使用,不知道大家有什么看法,写篇文章也是怀着技术交流的心态发布出来,由于是自己对这些技术的使用总结和心得体会,错误之处在所难免,所以希望大家能够多多指点,这样也能纠正我的错误观点,以便和各位共同提高!
由于思维发散的缘故,每写一篇文章,从构思到Demo和文章前后要几天时间,不知道大家在写文章有这样的体会没有,也许是自己知识浅薄的缘故才会这样吧。最后如果大家感兴趣,也可以关注WPF 基础到企业应用系列索引这个系列文章,我也会不定期的逐渐更新,谢谢各位的关注,也欢迎和各位交流讨论。