MVVM实践教程
算算,从事Silverlight和WPF的开发也有1年多的时间了,虽然时间不算长,虽然还没有突出的成就,但是感觉也还算⼀般。 但是,从头⾄今都没有去认真研究和使⽤过MVVM,虽然它被认为是Silverlight和WPF开发的最佳架构实践。
我想这⾥⾯还是有⼀些原因,就像⼀般开始我们始终都不会看好单元测试。直到有⼀天你体会到它的魅⼒,它的好处。
最近的项⽬,却不得不采⽤MVVM的模式:UI没有定,甚⾄服务端的Service都没有定,但是不能等到这些都做好才开始展开我们的开发⼯作。
于是,痛下决⼼研究MVVM的模式,在学习过程中,发现⼀些问题。MVVM的使⽤不仅仅是因为它需要新的思维,使View和ViewModel之间的交互变得更⿇烦。这其中还有另外的原因,那就是参考资料不全。
⼀般⽹上的资料都是⼀⽚简单的教程,⼤体都是怎么使⽤命令,怎么使⽤Binding的⽅式来建⽴应⽤程序。这些⼏乎都出⾃John Smith的那篇WPF的⽂章。
⽽实际上,在开发中,还会遇到其他问题,例如页⾯切换,UI事件,并且简单的⽰例我们往往弄不清楚各个模块之间的职责和联系。因为不能有效实践,从⽽被认为会影响开发效率⽽不被采⽤。
这⾥,我将⾃⼰学习总结的知识整理成⼀篇完整的⽰例,有提供源代码,源代码包含⽐较完整的⽰例,不仅包含数据绑定和命令的使⽤,还包含UI事件和UI的切换。相信能解释⼤多数遇到的问题。 当然,如果你有更好的实践和建议,欢迎讨论……………
1.MVVM设计模式简介
MVVM的设计模式最早于2005年由微软的WPF和Silverlight架构师John Gossman在他的博客中提到。以下是这篇⽂章的链接: MVVM设计模式基于MVC这种将UI和逻辑分离的结构思想。传统的.NET平台下软件开发如ASP.NET和WPF/Silverlight⼤多数是基于CodeBehind这样的⽅式,我们往往将所有的代码全部写在后台代码⽂件中,例如UI操作,业务逻辑操作,IO,数据服务的调⽤等等。这虽然表⾯上有利于“开发效率”,实际上项⽬结构不清晰,各个模块之间紧密耦合,不利于扩展,不利于测试。
MV-X的思想,为.NET平台下的架构提供⼀种很好的实践。使我们可以构建更利于扩展,结构清晰,职责分明,易测试的软件项⽬。 但是⽬前MVVM模式还没有⼀个标准的实践,微软也还没有给出相对标准的⽅案。⽬前社区讨论的主要是MVVM的思想。在实际开发过程中形成了⼏种不同的风格。其中以Josh Smith的⽂章影响⽐较⼤: 本篇提到的也主要是参考John Smith的思想。2.采⽤MVVM设计模式的好处
在Silverlight或者WPF中采⽤MVVM的架构可以获得以下好处: 1. 项⽬可测试更⾼,从⽽可以执⾏单元测试
2. 将UI和业务的设计完全分开,View和UnitTest只是ViewModel的两个不同形式的消费者 3. 有助于我们区别并哪些是UI操作,哪些是业务操作,⽽不是将他们全混在CodeBehind中 3.项⽬结构介绍
以下是⽰例项⽬的结构截图:以下是各模块之间的联系:
4.WpfMVVMSample.Foundation 提供⼀些基础类定义。5.Model的职责
Model主要提供基础实体的属性以及每个属性的验证逻辑。
Model不包含数据的调⽤,但是可以包含简单的⾮数据调⽤的操作,如产⽣序列号或者合并字段。 对于WCF产⽣的客户端代理类,Models中应有与之相对应的类结构定义。 Model不依赖于任何项⽬。6.IService和Services以及ServiceTest
IService是所有⽹络数据服务或者IO操作的服务接⼝。 IService中的数据访问⽅式以异步为主,见参考⽰例。
Service是真实的数据服务访问类,是IService的实现。ServiceTest是⽤于测试ViewModel的IService的实现7.ViewModel的职责
ViewModel是MVVM架构中最重要的部分,ViewModel中包含属性,命令,⽅法,事件,属性验证等逻辑。为了与View以及Model更好的交互来满⾜MVVM架构,ViewModel的设计需要注意⼀些事项或者约束:
ViewModel的属性:ViewModel的属性是View数据的来源。这些属性可由三部分组成: ⼀部分是Model的复制属性。
另⼀部分⽤于控制UI状态。例如⼀个弹出窗⼝的控件可能有⼀个IsClose的属性,当操作完成时可以通过这个属性更改通知View做相应的UI变换或者后⾯提到的事件通知。
第三部分是⼀些⽅法的参数,可以将这些⽅法的参数设置成相应的属性绑定到View中的某个控件,然后在执⾏⽅法的时候获取这些属性,所以⼀般⽅法不含参数。
ViewModel的命令:ViewModel中的命令⽤于接受View的⽤户输⼊,并做相应的处理。我们也可以通过⽅法实现相同的功能。
ViewModel的事件: ViewModel中的事件主要⽤来通知View做相应的UI变换。它⼀般在⼀个处理完成之后触发,随后需要View做出相应的⾮业务的操作。所以⼀般ViewModel中的事件的订阅者只是View,除⾮其他⾃定义的⾮View类之间的交互。
ViewModel的⽅法:有些事件是没有直接提供命令调⽤的,如⾃定义的事件。这时候我们可以通过CallMethodAction来调⽤ViewModel中的⽅法来完成相应的操作。8.View及Codebehind
View中使⽤Command:View中的Button等控件可以直接绑定Command属性调⽤ViewModel中的Command
View中使⽤CallMethodAction :⼀些不⽀持Command的控件,可以⽤⼀个CallMethodAction触发器来执⾏ViewModel中的⽅法。注意的是⽅法当中往往包含⼀些参数,这些参数⼀般可以通过给ViewModel设置相应的属性来绑定到相关的输⼊控件,如TextBox。
View中使⽤DataTrigger:除了模型属性,还有⼀部分是状态属性,这往往是ViewModel通过属性更改的⽅式通知View做出相关的UI操作,例如触发⼀段动画,或者切换控件状态等等。这个时候可以使⽤⼀些触发器,当状态值不同时做出相应的UI变换。
View的CodeBehind中初始化⼦View的ViewModel上下⽂:View⼀般由⽗View调⽤,所以View的ViewModel⼀般由⽗View来初始化。⽐如当点击⼈脉按钮,需要显⽰⼈脉的View的时候,就由主框架初始化⼈脉的ViewModel,并显⽰⼈脉的View。
View的CodeBehind中订阅⼦View的UI事件:除了通过状态属性的变更触发View中的触发器,看⼀种选择是在View的CodeBehind中订阅ViewModel的UI事件。
9.View及ViewModel交互模式总结
由以上解析我们可以总结出View和ViewModel的交互模式:1. ⽗View在CodeBehind中初始化⼦ViewModel2. ⽗View在CodeBehind中订阅⼦ViewModel的UI事件
3. ⽗View将⼦ViewModel赋值给⼦View的DataContext,并显⽰⼦View
4. ⽗View调⽤⼦ViewModel获取数据的⽅法,⼦ViewModel调⽤数据服务获取数据5. ViewModel的数据通过Binding传递给View
6. View接受⽤户输⼊,并通过Command或者CallMethodAction交给ViewModel做业务处理7. ViewModel处理完成之后触发UI事件或者更改状态属性通知⽗View8. ⽗View做出View变换⾄新的界⾯10. 依赖注⼊
ViewModel的职责是提供数据给View,并调⽤底层的数据服务。
为了解除ViewModel和BP的耦合,增加⼀层IService的接⼝定义,这也使得我们可以构造不同的IService实现来测试ViewModel。但是View 在使⽤ViewModel的时候,ViewModel必须要使⽤IService对象,所以这⾥采⽤依赖注⼊。通过依赖注⼊来完全解除View以及ViewModel对于Service的依赖。⽰例项⽬源代码下载:
打⼀⼴告:
正在准备⼀次北京的技术沙龙活动,⽬前正在筹备阶段,有兴趣的朋友可以报名,规格在20到50⼈之间。讨论内容范围待定。以交友为主,技术会友!QQ群:51825601(北京.NET之友技术沙龙)
备注:此群主要⽤于组织沙龙活动,仅限北京
欢迎各位朋友捧场!O(∩_∩)O哈哈~