谁应该控制MVVM应用程序中的导航?


33

例1:我的MVVM应用程序中显示了一个视图(出于讨论目的,请使用Silverlight),然后单击一个按钮,该按钮会将我带到新页面。

示例2:同一视图具有另一个按钮,单击该按钮后应在子窗口(对话框)中打开详细信息视图。

我们知道,将有ViewModel公开的Command对象绑定到按钮,这些对象具有响应用户单击的方法。但是,那又如何呢?我们如何完成该动作?即使我们使用所谓的NavigationService,我们在说什么呢?

更具体地说,在传统的“视图优先”模型(如基于URL的导航方案,例如Web或SL内置导航框架)中,Command对象必须知道接下来要显示的视图。当涉及由模式促进的关注点分离时,这似乎越界了。

另一方面,如果未将按钮连接到Command对象,并且其行为类似于超链接,则可以在标记中定义导航规则。但是我们是否希望Views控制应用程序流,并且导航不只是另一种业务逻辑吗?(在某些情况下我可以说是,在其他情况下我可以说不。)

对我来说,MVVM模式的乌托邦式实现(我听说其他人对此表示赞同)将以一种可以使应用程序无头运行(即无视图)的方式连接ViewModel。这为基于代码的测试提供了最大的表面积,并使Views成为应用程序的真实外观。而且我的ViewModel不在乎它是否显示在主窗口,浮动面板或子窗口中,应该吗?

根据这种方法,在运行时由其他机制来“绑定”应该为每个ViewModel显示的视图。但是,如果我们想与多个ViewModel共享一个View,反之亦然呢?

因此,鉴于需要管理View-ViewModel关系,以便我们知道在视图之间导航时需要显示的内容(包括显示子窗口/对话框),我们如何真正在MVVM模式中完成此操作?

Answers:


21

导航应始终在ViewModel中进行处理。

您认为MVVM设计模式的完美实现将使您处在正确的轨道上,这意味着您可以完全在没有Views的情况下运行应用程序,而如果Views控制着您的导航,则您将无法做到这一点。

我通常使用ApplicationViewModelShellViewModel来处理应用程序的整体状态。这包括CurrentPage(是ViewModel)和用于处理的代码ChangePageEvents。(它也经常用于其他应用程序范围的对象,例如CurrentUser或ErrorMessages)

因此,如果任何ViewModel在任何地方广播ChangePageEvent(new SomePageViewModel)ShellViewModel都会拾取该消息并将其切换CurrentPage到消息中指定的任何页面。

如果您有兴趣,我实际上写了一篇有关使用MVVM导航的博客文章。


2
有趣的方法。四个评论:1)Silverlight不支持DataTemplate上的DataType属性,因此在SL中无法将DataTemplate映射到ViewModel。2)这不能解决View和ViewModel之间的多对多可能性。3)它不处理子窗口(或者至少我不知道如何)。4)它要求您的Application / Shell ViewModel与它的子代(孙代等)之间紧密耦合。如果我的应用程序中有40个页面,则此ViewModel将很麻烦。
2011年

也就是说,绝对是要考虑的事情。
2011年

@SonOfPirate 1)Silverlight不支持隐式DataTemplate映射(尚未),但是它支持a DataTemplateSelector,这是我通常在Silverlight应用程序中使用的。2)我以前在很多情况下使用过此功能。例如,一个ViewModel可以具有多个View,或者一个View可以与多个ViewModel相关联。有关前者的示例,请参见rachel53461.wordpress.com/2011/05/28/…。对于以后,您只需要指定多个ViewModel映射到DataTemplateSelector中的同一视图即可。
雷切尔

3)这只是一个基本示例。如果您知道您的应用程序将是多个窗口,则显然可以更改ShellViewModel以处理多个窗口CurrentPages。4)再一次,那只是一个基本示例。实际上,我的PageViewModels都是基于某个基类的,因此ShellViewModel仅适用于基类或接口,例如IPageViewModel。确实,最大的混乱映射是DataTemplateSelector,它必须将40个View映射到40个ViewModel。
雷切尔

我希望@SonOfPirate能够回答您的一些问题。如果您还有其他人,请随时在聊天中查找我:)
Rachel

6

为了封闭起见,我想我会发布最终选择解决该问题的方向。

第一个决定是利用现成提供的Silverlight页面导航框架。该决定基于多个因素,其中包括:Microsoft正在将这种类型的导航转发到Windows 8 Metro应用程序中,并且与Phone 7应用程序中的导航一致。

为了使其工作,我接下来研究了ASP.NET MVC在基于约定的导航中所做的工作。框架控件使用URI定位要显示的“页面”。相似性提供了在Silverlight应用程序中使用基于约定的相似方法的机会。诀窍是使它们全部以MVVM方式协同工作。

解决方案是NavigationService。此服务提供了ViewModels可以用来启动页面更改的几种方法,例如NavigateTo和Back。请求新页面时,NavigationService使用MVVMLight Messenger功能发送CurrentPageChangedMessage。

包含Frame控件的视图将其自己的ViewModel设置为侦听此消息的DataContext。收到新视图的名称时,将通过应用我们的约定规则并将其设置为CurrentPage属性的映射函数放置。Frame控件的Source属性绑定到CurrentPage属性。因此,设置属性会更新“源”并触发导航。

回到NavigationService。NavigateTo方法接受目标页面的名称。为了确保ViewModels不存在UI问题,使用的名称是要显示的ViewModel的名称。我实际上创建了一个枚举,该枚举为每个可导航的ViewModel提供了一个字段,以作为一个助手,并消除了整个应用程序中的魔术字符串。我上面提到的映射函数将从名称中删除“ ViewModel”后缀,在名称后附加“ Page”,并将全名设置为“ Views {Name} Page.xaml”。

因此,例如,要导航到客户详细信息视图,我可以调用:

NavigationService.NavigateTo(ViewModels.CustomerDetails);

CustomerDetails的值为“ CustomerDetailsViewModel”,该值映射到“ Views \ CustomerDetailsPage.xaml”。

这种方法的优点在于,UI与ViewModels完全分离,但我们具有完整的导航支持。现在,我可以重新调整应用程序的外观,并且在我认为合适的情况下,无需任何代码更改。

希望解释有所帮助。


2

与Rachel所说的相似,我的主要是MVVM应用程序具有一个Presenter处理窗口或页面之间切换的功能。Rachel将此称为ApplicationViewModel,但以我的经验,它通常要做的不仅仅是绑定目标(例如接收消息,创建Windows等),因此从技术上讲,它更像是传统的PresenterController

在我的应用程序中,我Presenter以开头CurrentViewModel。该Presenter拦截之间的所有通信ViewViewModelViewModel交互过程中可以做的一件事是返回new ViewModel,这意味着Window应该显示一个新页面或一个新页面。该Presenter负责创建或覆盖的护理ViewViewModel和设置DataContext

动作的结果也可以是a ViewModel是“完成”,在这种情况下,Presenter检测到该错误并关闭窗口,或者将其弹出ViewModelVM堆栈并返回显示上一页。


演示者如何知道要显示的视图?
2011年

1
@SonOfPirate-通常通过WPF机制完成。在Presenter刚刚棒返回ViewModel在视觉树,WPF抓住合适的View,钩起来DataContext,并提出,在可视化树来代替。您可以使用DataTemplate声明ViewModel它们呈现的类型的s 来执行此操作,也可以创建自定义数据模板选择器。这仍然在WPF功能范围之内。
斯科特·惠特洛克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.