MVVM模式构建WPF.docx

上传人:b****4 文档编号:12228900 上传时间:2023-04-17 格式:DOCX 页数:33 大小:176.92KB
下载 相关 举报
MVVM模式构建WPF.docx_第1页
第1页 / 共33页
MVVM模式构建WPF.docx_第2页
第2页 / 共33页
MVVM模式构建WPF.docx_第3页
第3页 / 共33页
MVVM模式构建WPF.docx_第4页
第4页 / 共33页
MVVM模式构建WPF.docx_第5页
第5页 / 共33页
点击查看更多>>
下载资源
资源描述

MVVM模式构建WPF.docx

《MVVM模式构建WPF.docx》由会员分享,可在线阅读,更多相关《MVVM模式构建WPF.docx(33页珍藏版)》请在冰豆网上搜索。

MVVM模式构建WPF.docx

MVVM模式构建WPF

使用MVVM设计模式构建WPF应用程序

本文是翻译大牛JoshSmith的文章,WPFAppsWithTheModel-View-ViewModelDesignPattern,译者水平有限,如有什么问题请看原文,或者与译者讨论(非常乐意与你讨论)。

本文讨论的内容:

WPF与设计模式

MVP模式

对WPF来说为什么MVVM是更好的选择

用MVVM构建WPF程序

本文涉及的技术:

WPF、数据绑定

内容列表

有序与混乱

模型-视图-视图模型的演变

为什么WPF开发者喜欢MVVM

演示程序

中继命令逻辑

ViewModel类层级结构

ViewModelBase类

CommandViewModel类

MainWindowViewModel类

View对应ViewModel

数据模型和Repository

新增客户数据表单

所有客户视图

总结

开发UI,对一个专业软件并不容易。

它需要未知数据、交互式设计,可视化设计、联通性,多线程、国际化、验证、单元测试以及其他的一些东西才能完成。

考虑到UI要展示开发的系统并且必须满足用户对系统风格不可预知的变更,因此它是很多应用程序最脆弱的地方。

有很多的设计模式可以帮助解决UI不断变更这头难缠的野兽,但是恰当的分离和描述多个关注点可能很困难。

模式越复杂,之后用到的捷径越可能破坏之前正确的努力。

这并不总是设计模式的错。

有时使用要写很多的代码复杂设计模式,这是因为我们使用的UI平台并不适合简单是设计模式。

UI平台需要做的是很容易使用简单的,久经考验的,开发者认识的设计模式构建UI。

庆幸的是,WPF就是这样一个平台。

随着是使用WPF开发的比例不断升高,WPF社区发展了自己的模式与实践生态圈子。

在本文,我将讨论一些设计与实现客户端应用程序的WPF最佳实践。

利用WPF和MVVM设计模式衔接的一些核心功能,我将通过一个例子介绍,用“正确”的方式构建一个WPF程序是多么的简单。

datatemplates,commands,databinding,theresourcesystem以及MVVM模式怎么揉合到一起创建一个简单的、可测试的、健壮的框架,并且任何WPF程序都能使用,到文章最后,这一切都很清晰明了。

文中的例程可以作为现实中一个WPF应用程序的模版,并且使用MVVM设计模式作为其核心架构。

例程解决方案中的单元测试部分,展示了测试ViewModel类的功能是很容易的。

在深入本文之前,我们首先看一下我们要使用像MVVM这样的设计模式。

有序与混乱

没有必要在一个”Hello,World!

”的程序中使用设计模式。

任何一个合格的开发者看一眼就指导那几行代码是干什么的。

然而随着程序功能点的增加,随之代码的数量以及移动部件也会增多。

最终系统的复杂度以及不断出现问题,促使开发者组织他们的代码,以便它们更容易理解,讨论、扩展以及维护。

我们通过给代码中某些实体命以众所周知的名字,减少复杂系统认知误区。

我们给函数块命名主要依据系统中的功能角色。

开发者有意识的根据设计模式组织他们的代码,而不是根据设计模式自动去组织。

无论哪一种,都没有什么问题。

但是在本文中,我说明在WPF程序中明确使用MVVM模式的好处。

某些类的名称,包括MVVM模式中著名的术语,如果类是view的抽象类就以ViewModel结束。

这种方式有助于避免之前提到的认知误区。

相反,你也可以让那种受控的误区存在,这正是大部分软件开发项目的自热状态。

模型-视图-视图模型的演变

自从人们开始构建UI时,就有很多流行的设计模式让UI构建更容易。

比如,MVP模式在各种UI编程平台中都非常流行。

MVP是MVC模式的一种变体,MVC模式已经流行了几十年了。

以防你之前从没用过MVP模式,这里做一个简单的解释。

你在屏幕上看到的是View,它显示的数据是Model,Presenter就是把两者联系起来。

View依赖Presenter并通过Presenter展示Model数据,响应用户输入,提供数据验证(或许委托给Model去完成)以及其他的一些任务。

如果你想了解更过关于MVP模式,我建议你去读Jean-PaulBoodhoo的August2006DesignPatternscolumn。

2004年晚些时候,MartinFowler发表了一篇叫PresentationModel(PM)的模式。

PM模式和MVP类似,MVP是把一个View从行为和状态分离出来。

PM中令人关注的部分是创建view的抽象,叫做PresentationModel。

之后,View就仅仅是PresentationModel的展示了。

在Fowler的论文中,他展示了PresentationModel经常更新View,以便两个彼此同步。

同步逻辑组作为代码存在于PresentationModel类中。

2005年,JohnGossman,目前是微软WPF和Silverlight架构师,在他的博客上披露了Model-View-ViewModel(MVVM)模式。

MVVM和Fowler的PresentationModel是一致的,两个模式的特征都是View的抽象,都包含了View的行为和状态。

Fowler引入PresentationModel是作为创建独立平台的View的抽象,而Gossman引入MVVM是作为标准化的方法,利用WPF的核心特点去简化UI的创建。

从这种意义上来讲,我把MVVM作为一般PM模式的一个特例。

在GlennBlock一遍优秀的文章"Prism:

PatternsforBuildingCompositeApplicationswithWPF",于2008年9月微软大会发布,他解释了WPF微软组合程序开发向导。

术语ViewModel没有用到,然而PM却用来描述View的抽象。

这篇文章自始至终,都没没有出现我要将MVVM模式,以及View的抽象ViewModel。

我发现这个术语在WPF和Silverlight社区中比较流行。

不像MVP中的Presenter,ViewModel不需要引用View。

View绑定ViewModel的属性,ViewMode向Viewl暴露Model对象的数据以及其他的状态。

View和ViewModel之间的绑定很容易构造,因为ViewModel对象可以设置为View的DataContext。

如果ViewModel中的属性值发生改变,新值将通过绑定自动传送给View。

当用户点击View中的按钮时,ViewMode对于的Command将执行请求的动作。

ViewModel,绝不是View,去执行实体对象的修改。

View类并不知道Model类是否存在,同时ViewModel和Model也不知道View。

实际上,,Model完全不知道ViewModel和View存在,这是一个非常松耦合的设计,在很多方面都有好处,这不就你就会看到。

为什么WPF开发者喜欢MVVM

一旦开发者适应了WPF和MVVM,就很难区别两者。

因为MVVM非常适合WPF平台,并且WPF被设计使用MVVM模式更容易构建应用程序,MVVM就成了WPF开发者的通用语。

事实上,微软内部正在用MVVM开发WPF应用程序,像MicrosoftExpressionBlend,然而当时WPF平台的核心功能依然在开发之中。

WPF的很多方面,像控制模型以及数据模版,都利用了MVVM推荐的显示状态和行为分离技术。

MVVM之所以成为一个伟大设计模式,是因为WPF的一个最重要的特征数据绑定构造。

通过把Viewde属性绑定到ViewModel,你就可以得到两者松耦合的设计,并且完全去除ViewModel更新View的那部分代码。

数据绑定系统支持输入验证,并且输入验证提供了传递错误给View的标准方法。

另两个WPF的特点,数据模版和资源系统让MVVM模式更加可用。

数据模版把View应用在ViewModel对象上,以便其能够在UI上显示。

你可以在Xaml中声明模版,让资源系统在系统运行过程中自动定位并应用这些模版。

你可以从我2008年7月写的一篇文章,"DataandWPF:

CustomizeDataDisplaywithDataBindingandWPF.",获取更多关于绑定和数据模版的信息。

要不是WPF对Command的支持,MVVM模式就不会那么强大。

本文中,我会为你展示ViewModel怎样把Commands暴露给View,并且让View消费它的功能。

如果你对Command不是很熟悉,我推荐你读一下2008年9月BrianNoyes发布的文章,"AdvancedWPF:

UnderstandingRoutedEventsandCommandsinWPF"。

除了WPF(Silverlight2)本身让MVVM以一种自然的方式去构建程序之外,造成MVVM模式流行还有一个原因,那就是ViewModel类很容易进行单元测试。

从某种意义来讲,View和单元测试只是ViewModel两个不同类型的消费者。

拥有一套应用程序的单元测试,可以为提供更自由、快速的回归测试,而回归测试有助于降低之后应用的维护成本。

除了促进创建自动化回归测试外,ViewModel类的可测试性也有助于设计更容易分离的UI。

当你设计应用时,你可以通过想象某些东西是否要创建单元测试消费ViewModel,来确定它们是放到View里面还是ViewModel里面。

如果你可以为ViewModel写单元测试而不用创建任何UI控件,你也可以把ViewModel剥离出来,因为它不依赖任何具体可视化的组件。

最后,对于要和设计者合作的开发者来说,使用MVVM模式使得创建平滑的开发/设计工作流更加容易。

既然View可以是ViewModel的任意一个消费者,就很容易去掉一个View通过新增一个View去渲染ViewModel。

这个简单的步骤允许设计师构建快速原型以及评估UI设计。

这样开发团队可以关注创建健壮的ViewModel类,而设计团队可以关注设计界面友好的View。

要融合两个团队输出只需要在View的xaml上进行正确的绑定即可。

演示程序

到此为止,我们回顾了MVVM的历史以及具体操作理论。

我也说明了它在WPF开发者中间如此流行的原因。

现在是时候继续我们的步伐,看一下MVVM模式在实际中的应用。

这篇文章中的演示程序以各种方式使用MVVM设计模式,它提供了丰富的例子,帮助在上下文中理解MVVM的概念。

我用VS2008SP1创建的这个演示程序,框架是Microsoft.NETFramework3.5SP1。

单元测试是用的VisualStudiounittesting。

应用可以包含任意数量的“Workspace”,每一个都可以由用户点击左侧导航区的命令链接打开。

所有的Workspace寄宿在主区域TabControl中,用户可以通过点击workspace的tabitem上关闭按钮关闭workspace。

应用程序有两个可用的workspace:

"AllCustomers"和"NewCustomer"。

运行程序,打开一些workspace,UI看起来如图1所示。

图1Workspaces

一次只有一个“AllCustomers“Workspace的实例可以打开,但是可以打开多个NewCustomerWorkspace。

当用户决定创建一个新的客户时,她必须填完图2所示的数据输入表单。

图2新客户数据输入表单

填完数据输入表单的所有有效值点击“Save”按钮,新客户的名称将会出现在tabitem上面,同时新客户也会增加到客户列表中。

应用程序不支持删除或者编辑客户,但是这和其它功能类似,很容易在已有的程序架构上去实现。

现在你已经对演示程序有了更深层次的理解了,接下来我们研究它是如何设计以及实现的。

中继命令逻辑(RelayingCommandLogic)

除了类构造器里调用初始化组件标准的样板代码,应用中的每一View的codebehind文件都是空的。

实际上你可以移除View的codebehind文件,程序让人能够争正确的编译和运行。

尽管View中没有事件处理方法,但是当用户点击按钮时,程序依然能够响应并满足用户的请求。

之所以这样,是因为UI上Hyperlink、Button以及MenuItem控件的Command属性被绑定了。

绑定机制确保当用户在控件上点击时,由ViewModel暴露的ICommand对象能够执行。

你可以把command对象看作一个适配器,这个适配器让command对象很容易消费在View中声明的ViewModel功能。

当ViewModel暴露ICommad类型的实例属性,被暴露的Command对象使用ViewModel中的对象去完成它的工作。

其中一个可能的实现模式是在ViewModel内创建一个私有嵌套类,以便command能够访问包含在ViewModel中的私有成员,而不至于污染命名空间。

嵌套类实现了ICommand接口,包含在ViewModel中对象的引用注入到其构造器中。

但是为ViewModel暴露的每个Command创建实现ICommad的嵌套类,会增加ViewModel类的大小。

更多的代码意味着存在BUGS潜力更大。

在演示程序中,RelayCommand类解决了这个问题。

RelayCommand允许通过把委托传给其构造器,以实现对命令逻辑的注入。

这种方式允许在ViewMode类中可以简单明了的实现Command。

RelayCommand是DelegateCommand的一个简单的变体,DelegateCommand可以在MicrosoftCompositeApplicationLibrary找到。

RelayCommand类代码如图3所示。

图3RelayCommand类

publicclassRelayCommand:

ICommand

{

#regionFields

readonlyAction_execute;

readonlyPredicate_canExecute;

#endregion//Fields

#regionConstructors

publicRelayCommand(Actionexecute)

:

this(execute,null)

{

}

publicRelayCommand(Actionexecute,PredicatecanExecute)

{

if(execute==null)

thrownewArgumentNullException("execute");

_execute=execute;

_canExecute=canExecute;

}

#endregion//Constructors

#regionICommandMembers

[DebuggerStepThrough]

publicboolCanExecute(objectparameter)

{

return_canExecute==null?

true:

_canExecute(parameter);

}

publiceventEventHandlerCanExecuteChanged

{

add{CommandManager.RequerySuggested+=value;}

remove{CommandManager.RequerySuggested-=value;}

}

publicvoidExecute(objectparameter)

{

_execute(parameter);

}

#endregion//ICommandMembers

}

作为接口ICommad实现一部分,事件CanExecuteChanged有一些值得关注的特征。

它委托订阅CommandManager.RequerySuggested事件。

这样以确保无论何时调用内置命令时,WPF命令架构都能调用所有能够执行的RelayCommand对象。

RelayCommand_saveCommand;

publicICommandSaveCommand

{

get

{

if(_saveCommand==null)

{

_saveCommand=newRelayCommand(param=>this.Save(),

param=>this.CanSave);

}

return_saveCommand;

}

}

ViewModel类层级图

大部分ViewModel类有共同的特征,他们要实现INotifyPropertyChanged接口,需要显示一个友好的名字,以之前说道Workspace为例,它需要能够关闭(即从UI上移除)。

要解决这个问题,自然就需要创建一个或二个ViewModel基类,以便新的ViewModel类能够从基类集成通用的功能。

所有的ViewModel类形成如图4的层级图。

图4继承层级图

为你的ViewModel创建一个基类并不是必须。

如果你喜欢在类中通过组合几个小一点的类以获得那些功能,而不是用继承的方式,这并没有什么问题。

就像任何其他的设计模式一样,MVVM是一套指导方针,而不是规则。

ViewModelBase类

ViewModelBase是层级中的根类,这就是它要实现通用INotifyPropertyChanged接口以及有一个DisplayName属性的原因。

INotifyPropertyChanged接口包含一个叫PropertyChanged的事件。

无论何时ViewModel对象的属性的发生改变时,它都会触发PropertyChanged事件,把新值通知给WPF绑定系统。

根据通知,绑定系统检索属性,UI组件上绑定的属性将接受新值。

为了让WPF知道是那一个属性发生了改变,PropertyChangedEventArgs类暴露了一个string类型的属性PropertyName。

你一定要为事件参数传递正确的属性名,否则WPF将会为新值检索出一个错误的属性。

ViewModelBase一个值得关注的地方就是它为给定的属性名提供了验证,验证属性是否存在ViewModel对象上。

重构时,这非常有用。

因为通过VS2008重构功能去改变属性名,不会更新源代码中字符串,而这些字符串正好包含属性名(其实不应该包含)。

在事件参数中传递不正确的属性名,触发PropertyChanged事件时,可能会导致微小的BUGs,并且这些BUGs很难追踪,因此这个细微的特征将会节省大量的时间。

ViewModelBase中增加了这个有用的特征,其代码如下:

图5属性验证

//InViewModelBase.cs

publiceventPropertyChangedEventHandlerPropertyChanged;

protectedvirtualvoidOnPropertyChanged(stringpropertyName)

{

this.VerifyPropertyName(propertyName);

PropertyChangedEventHandlerhandler=this.PropertyChanged;

if(handler!

=null)

{

vare=newPropertyChangedEventArgs(propertyName);

handler(this,e);

}

}

[Conditional("DEBUG")]

[DebuggerStepThrough]

publicvoidVerifyPropertyName(stringpropertyName)

{

//Verifythatthepropertynamematchesareal,

//public,instancepropertyonthisobject.

if(TypeDescriptor.GetProperties(this)[propertyName]==null)

{

stringmsg="Invalidpropertyname:

"+propertyName;

if(this.ThrowOnInvalidPropertyName)

thrownewException(msg);

else

Debug.Fail(msg);

}

}

CommandViewModel类

CommandViewModel是最简单的ViewModelBase子类,它暴露了一个类型为ICommad的Command属性。

MainWindowViewModel通过Commands属性暴露了CommandViewModel对象的一个集合。

主窗口左手侧的导航区域,显示了MainWindowViewModel暴露每个CommandViewModel对象链接,像“Viewallcustomers”和“Createnewcustomer”。

当用户点击链接,将会执行相应的Command,在主窗口的TabControl中打开一个workspace。

CommandViewModel类的定义如下所示:

publicclassCommandViewModel:

ViewModelBase

{

publicCommandViewModel(stringdisplayName,ICommandcommand)

{

if(command==null)

thrownewArgumentNullException("command");

base.DisplayName=displayName;

this.Command=command;

}

publicICommandCommand{get;privateset;}

}

在MainWindowResources.xaml文件中存在一个key为CommandsTemplate的数据模版,主窗口(MainWindow)使用这个模版渲染之前提到的CommandViewModel对象集合。

这个模版是简单在ItemsControl里把每个CommandViewModel对象渲染成一个链接,每个链接的Command属性绑定到CommandViewModel对象的Command属性。

数据模版Xaml如图6所示:

图6渲染Command列表

--InMainWindowResources.xaml-->

--

Thistemplateexplainshowtorenderthelistofcommandson

theleftsideinthemainwindow(the'ControlPanel'area).

-->

Key="CommandsTemplate">

展开阅读全文
相关搜索


copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1