一、
什么是MVVM模式
MVVM是Model-View-ViewModel的简写。微软的WPF带来了新的技术体验,如Silverlight、音频、视频、3D、动画……。这导致了软件UI层更加细节化、可定制化。同时,在技术层面,WPF也带来了诸如Binding、Dependency
Property、Routed Events、Command、DataTemplate、ControlTemplate等新特性。MVVM(Model-View-ViewModel)框架的由来便是MVP(Model-View-Presenter)模式与WPF结合的应用方式时发展演变过来的一种新型架构框架。它立足于原有MVP框架并且把WPF的新特性揉合进去,以应对客户日益复杂的需求变化。
二、 为什么要有MVVM模式
因为MVVM模式解决了在日常开发中Model与View之间相互通信之间存在的问题,如转换类型等额外操作。
记得几年前,本人接触MVC设计模式的时候,那时候感觉前台后台之间工作与呈现是如此的井然有序。开发扩展的时候需要的只是添加新的View,新的Model和相应的Controller代码。后期开发维护实在是容易。
时间回溯到几个月前,本人用WPF开发了一个软件系统。这个系统算是使用WPF各种技术的总结。但是没有引入任何模式。在开发完成以后,xaml以及xaml后的cs代码里堆积了大量的内容。导致维护的时候需要在设计视窗里通过点击控件区域定位到代码,再通过代码找到后台事件,再通过后台事件找到处理方法,这一大长串难以分离的耦合困区。每个页面的xaml堆积到最多几千行,后台的cs代码也是几千行,维护较为困难,同时再开发扩展的时候也有诸多不变性。
后来网上找到了很多实例,其中MVVM首当其冲进入我的眼帘。这个模式吸收了MVC模式的精华,同时又针对于WPF有特定的实现方法,可以做到代码井然有序。
如图1,展现了MVVM是如何做到井然有序的。
图1
现在这附图说明了MVVM模式的实现关系。在开发中View集成了用户的操作以及数据的展现方式。很大程度上解决了直接编写代码存在的两个问题:
1. View中很多控件的数据类型和Model中的属性不相同,例如开发中,性别这种,Model中很可能就放置一个bool类型的变量。但是在前台的展现View中,用户看到的应该是“男”和“女”。这就需要一种转化。这种转化放在View中?不合适,界面上不应该出现逻辑代码。放在Model中?不合适,这会出现更多的属性,方法,导致Model臃肿庞大。
2. 在WPF开发中事件和命令同样都可以让一个UI正常的工作。我们知道Winform是事件驱动的。所以理所当然使用事件更容易理解和实现。但是带来的问题是后期的庞大与多种多样的事件。这些事件真的不能复用?不是。
这两种问题很大的催生出Model和View中间的一个辅助角色ViewModel。它需要帮助View转化相应的数据给Model或者从Model处转化成View可以显示的内容。同时它也需要将View的多种命令绑定给Model中的处理方法上。这些命令可以复用,当其他View需要的时候,同样可以调用命令中绑定的方法。ViewModel可以看成一个变种的Controller。
三、 实现原理
解决了上一节提出的两个问题,实际上就解决了ViewModel的全部工作原理。
首先,从Binding问题入手。在View中的控件存在一个属性,叫做“DataContext”。这个是控件数据使用的源头。DataContext属性会给控件指定一个后台模型,使得该控件使用的数据都是来自于这个模型类。所以,ViewModel应该充当这个后台模型的作用,给View的控件提供显示数据。同时,ViewModel的数据应该是来自于背后的Model所提供的。所以,简要的说,根据View中显示的数据是何种Model,来定义ViewModel。举个例子,如果View构造了一个TextBlock控件,想要显示的仅仅是Model中的一个string。那么在ViewModel中,应该引用这里的Model,这样作为View部分,就可以调用这个Model的某个string属性了。再举个实际一些的例子,如果View中构建了一个ListView,这里展现出一系列的商品名称。对于Model来说,每个实例是一个商品。那么ViewModel中应该是一个Model的集合,是保存了所有需要ListView显示出来的Model集合。
其次,解决Command问题。WPF中已经构建了实现了ICommand的类RoutedCommand和RoutedUICommand。针对于不同的View事件,单独使用哪一种都不是全权之策。因此,需要定义一个实现了ICommand接口的类。目前网上有现成的DelegateCommand和RelayCommand两种解决方法。他们的共同点都是实现了ICommand接口,同时对于不同的事件,都可以绑定Model不同的处理方法。其区别是:i)DelegateCommand使用了一个RaiseCanExecuteChanged方法,需要开发者手动来触发控件可执行判断。而RelayCommand中对于此处的触发判断是代理给CommandManager自己判断了。更加方便;ii)DelegateCommand因为是开发者手动控制的,所以资源占用低,而RelayCommand在各种命令触发的时候都需要判断一下。所以资源占用也相对较高。这一点尤为能体现在复杂的系统中。所以使用哪一个都是看开发人员自己选择。当然也可以自己手动写一些更加适合自己的XXXCommand。其实现原理就是实现了ICommand接口。另外,使用委托的方法,将无返回值的Execute使用Action委托,有返回值的CanExecute使用Func委托。
四、 实现过程
上面说了这么多,必须得实际操练一下才能深切体会MVVM。
这里就以之前商品列表作为背景。View界面我打算演示一个ListView用来展现一个列表。然后下方有一个按钮,单击列表中的某一项商品,然后点击下方的按钮让商品价格加1元。界面如下图:
当选中某个项,点击“Add 1¥”以后,那个商品会随之加1元。(价格显示美元符是因为本地区域性设置,可更改,这里不做说明)。如图
好,下面来实现这个例子。
首先,先观察一下我的例子结构:
这里每个文件做个说明:
1.Milk.cs这个是一个Model,里面含有商品应有的信息,如ID、类型、价格等。
2.NotificationObject.cs这其实是一个公共类,其提供的方法就是将需要提醒前台自动更新数值的属性在必要的时候更新数值。
3.MilkListViewModel.cs这是一个ViewModel,这里为前台的列表准备了一个ObservableCollection,并且为前台提供Command绑定支持。简单的来说,它将自己的某种对商品集合准备方法实例化为一个命令类型的属性,为前台提供绑定。
4.RelayCommand.cs这是网上广为流传的一个MVVM实现所用的ICommand接口实现。原理之前已经说了,网上也有DelegateCommand.cs的源码。都很好用。
5.MainWindow.xaml这里充当View,布置了显示商品的各种UI。通过Binding来绑定数据源和命令。不过应该注意,必须是DataContext是编写的ViewModel时,才能绑定那个ViewModel的数据和方法命令。 |