通过上篇文章<WPF-MVVM模式学习笔记2——MVVM简单样例>中举了一个例子,我对MVVM大概有了一个比较浅显的意思。同时,看过前两篇文章的人,也知道我的这个系列的文章大多数来源于其他的博客,我其实只是起了一个汇总的作用,毕竟我也是在学习,肯定是要去网络上学习别人的笔记喽。本篇文章将以温故而知新的方式再次去理解MVVM,力求对MVVM的认识再深一个层次。
1.再看"M-V-VM"
M:即Model,由现实世界抽象出来的模型。
V:即View,视图,界面,该界面与用户输入设备进行交互。
2.View与Model如何进行交互?
此时,Binding便可以发挥作用了,比如视图上的某一个文本框中的文本和Model中的"用户名"关联起来, 用户便可以通过操作该文本框来访问和修改Model的"用户名"了。但是在实际编程时,我们发现,Model中的属性(或者方法)往往不那么容易与View中的界面控件关联起来。比如定义了一个时间的类Time,该时间类Time具有年、月、日三个属性,如下
public class Time { private string year; public string Year { get { return this.year; } set { this.year = value; } } private string month; public string Month { get { return this.month; } set { this.month = value; } } private string day; public string Day { get { return this.day; } set { this.day = value; } } public Time(string year, string month, string day) { this.year = year; this.month = month; this.day = day; } }
同时还有一个界面TimeView,如下
我想实现按下按钮1,时间以年-月-日的形式展示;按下按钮2,时间以月-日-年的形式展示。此时就需要进行一些“额外的操作”,即模型Time中的数据经过一些额外的处理才能传给视图,反之亦然。现在,我们意识到这个View似乎需要一个"Helper"类来处理一些额外工作。这个Helper所包含的代码可以放在Model(例如这里的Time)外的很多地方,比如对应的View.cs中。而且我之前确实是这么做的,将绝大数处理逻辑放在所谓的CodeBehind中。
3.后来...
后来,在各种设计模式书籍中所看到的一样,为了将View和Model剥离开来,实现View可替换,便有了MVC。有了MVC以后,就开始有了MVP、MVVM等等M-V-XX。但是几乎(我还是加个几乎吧,因为其他设计模式我也没有接触过)所有这些设计模式都主要围绕两个问题:
一是Model与View之间的关系,完全隔离的?单向的还是双向的?
二是这个"XX"需要完成哪些功能,简单流程调度?复杂规则处理?
这些问题都没有关系, 是否采用某种模式取决于你的开发所处的环境(比如语言特性,框架特性)以及你的业务特性以及所面临的主要变化点等等。
4.但是...
但与MVC,MVP所不同的是,MVVM的引入不仅仅是技术上的原因(解除耦合应对变化等老生常谈),另外一个很大原因是:软件团队开发方式的改变。我们需要一种方式将View层的代码逻辑抽取出来,并View层很纯粹以便完全让美工去打造它。相应地,,需要将View层的相应逻辑抽取到一个代码层上,以便让程序员专注在这里。这就需要:
① 你拥有能够熟练运用Blend等工具能为程序员输出XAML的美工,他专注于纯粹的UI/UE,另外他还必须具有一定的"程序员"思维,以便输出的东西能很好地作为程序的一部分而运转起来,而不是仅仅"看上去"是那样的;
② 你需要能够脱离View层但仍能编写出高质量代码的程序员。
5. Binding、Command、AttachBehavior
回想一下, 我们之所以要在View(Xaml)背后写一些代码(C#),无非是想传递一些数据以及传递数据时的数据的处理或在用户与界面控件进行交互时执行一些操作。最简单的例子是在MVC中当界面发生交互时View去调用Controler中的某个方法,以便将该操作的相应"指示"传递到"后台"去。在以前的技术中, 这样的"衔接性"的代码是必须的。而在WPF中, 则可以通过另外的技术来进行层与层之间的"衔接", 这就是"Binding" 和"Command", 以及"AttachBehavior"。
通过Binding,我们可以实现数据的传递;通过Command,我们可以实现操作的调用。Binding和Command是可以写在XAML中的, 这样看来XAML后面对于的CS文件可以被完全抛弃或不予理会了,这样的XAML文件正是美工所需要的。而这些对于Binding以及Command的定义描述以及其他相关信息的代码应该放在那里呢,当然不是View, 更不是Model,是“ViewModel”。ViewModel是为这个View所量身定制的,它包含了Binding所需的相关信息,比如Converter以及为View的Binding提供DataContext,它包含了Command的定义以便View层可以直接使用, 另外,它还是一个变种的Controler, 它得负责业务流程的调度。
上图非常直观的展示View\ViewModel\Model的交互图,真的很佩服绘出这幅图的作者,太直观了。
那么AttachBehavior又是什么呢?一般情况下利用Command, Binding, AttachProperty等WPF特性, View和ViewModel之间能配合工作得很好。假设我们有一个Button,当该Button被点击的时候我们要完成一些操作,很简单,将该操作封装成一个Command并绑定到该Button上就可以了。但如果我们要在Button被Load的时候执行另外一些操作呢? 由于Button没有直接被Load事件所触发的Command, 所以不能使用Command了。不能直接将Load事件处理器写在Button所在的Xaml所对应的CS文件里, 这和我们刚才对MVVM的设计是相矛盾的。 一个不太好的方案是继承一下Button, 并撰写一个由Load所触发的Command,这可行, 但明显不好, 正如一个控件没有某个属性并且在不继承的情况下而采用AttachProperty一样,我们可以采用AttachBehavior。
6.收尾
到这里,又了解了MVVM,同时也知道了ViewModel的作用,那么久可以利用这个模式解决上面的时间显示的问题了,
添加类TimeViewModel,内容如下
public class TimeViewModel : NotificationObject { private Time time; public Time Time { get { return this.time; } set { this.time = value; this.RaisePropertyChanged(() => this.time); } } public TimeViewModel() { time = new Time("1991","02","23"); birthday = time.Year + "-" + time.Month + "-" + time.Day; } private string birthday; public string Birthday { get { return this.birthday; } set { this.birthday = value; this.RaisePropertyChanged("Birthday"); } } public bool CanSubmit { get { return true; } } public void Submit1() { Birthday = time.Year + "-" + time.Month + "-" + time.Day; } public void Submit2() { Birthday = time.Month + "-" + time.Day + "-" + time.Year; } }在这个ViewModel里,定义了一个用于显示日期格式的字符串Birthday,同时两个方法Submit1()和Submit2()分别是两个按钮绑定的Click事件。
此时View视图内容为下
<UserControl x:Class="MVVMDemo.View.TimeView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:vm="clr-namespace:MVVMDemo.ViewModel" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"> <Grid x:Name="gridLayout"> <Grid.DataContext> <vm:TimeViewModel /> </Grid.DataContext> <Grid.ColumnDefinitions> <ColumnDefinition Width="5*" /> <ColumnDefinition Width="5*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="5*" /> <RowDefinition Height="5*" /> <RowDefinition Height="5*" /> <RowDefinition Height="5*" /> </Grid.RowDefinitions> <TextBlock Text="Time:" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"/> <TextBlock Text="{Binding Path=Birthday,Mode=Default}" Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Left"/> <Button x:Name="Btn1" Content="按钮1:年-月-日" IsEnabled="{Binding CanSubmit}" Grid.Row="2" Grid.Column="0" Width="150" Height="50" VerticalAlignment="Center" HorizontalAlignment="Right"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <ei:CallMethodAction TargetObject="{Binding}" MethodName="Submit1"/> </i:EventTrigger> </i:Interaction.Triggers> </Button> <Button x:Name="Btn2" Content="按钮2:月-日-年" IsEnabled="{Binding CanSubmit}" Grid.Row="2" Grid.Column="1" Width="150" Height="50" VerticalAlignment="Center" HorizontalAlignment="Right"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <ei:CallMethodAction TargetObject="{Binding}" MethodName="Submit2"/> </i:EventTrigger> </i:Interaction.Triggers> </Button> </Grid> </UserControl>
编译运行程序(该工程点此下载)
(2.23就快到我的生日了,不知道还有没有人会记得呢,呵呵)
文章来源
http://www.csdn123.com/itweb.php?url=aHR0cDovL3d3dy5jbmJsb2dzLmNvbS9XaW5kb3dzLXBob25lL2FyY2hpdmUvMjAxMy8wOC8yNC8zMTE1ODIzLmh0bWw=
感谢这位朋友!!你讲的很易懂!!