在WPF中使用MVVM,我应该从View的代码后台还是ViewModel中启动子窗口?

19
我对这个问题感到困惑已经有一段时间了。我正在使用MVVM模式编写一个相当大的RibbonWindow WPF应用程序。屏幕顶部有一个RibbonBar菜单,其余部分显示各种视图。一些视图包含其他视图,其中一些具有启动子窗口的按钮。
到目前为止,我一直在从View的代码后台文件中进行操作,但我知道当使用MVVM时这些文件应该为空。我可以将子窗口启动代码移到ViewModel中,但是我需要一个引用主RibbonWindow(设置为子窗口所有者),这似乎不太合适。
如果您能提供关于如何使用MVVM实现此功能的任何建议或提示,我将不胜感激。

4
我不同意在“视图代码后文件应为空”的说法。虽然很多东西不应该放在代码后,但它仍然有价值。我会将它用于与视图层特定的任何事物,比如管理控件焦点。我也会用它来打开WPF中的后续窗口。 - Joel Cochran
1
+1 给 Joel。MVVM 实现意味着 VIEW SPECIFIC 代码是唯一应该位于代码后面的代码。您的视图模型不应该处理诸如 VSM 状态更改之类的事情(除非这些更改是数据驱动的,在这种情况下,我将在 VM 级别公开行为或触发器中包装它)。 - Scott Silvi
谢谢你们的评论,真的为我解决了一些问题。 - Sheridan
6个回答

20

我通常通过创建某种类型的WindowViewLoaderService来处理这个问题。当你的程序初始化时,你可以通过以下代码注册你的窗口和ViewModel:

WindowViewLoaderService.Register(TypeOf(MainWindowView), TypeOf(MainWindowViewModel));
WindowViewLoaderService.Register(TypeOf(MyWindowView), TypeOf(MyWindowViewModel));

那么当你可以从ViewModel调用此服务时,您只需要引用另一个ViewModel即可。例如,如果您在MainWindowViewModel中,则可能会有以下代码:

var myChildWindowVM = new MyWindowViewModel();
WindowViewLoaderService.ShowWindow(myChildWindowVM);
WindowViewLoaderService会查找与传递给它的ViewModel相关联的View。它将创建该View,将其DataContext设置为传递的ViewModel,然后显示该View。
这样,您的ViewModels就不会知道任何Views。
您可以轻松地自己编写此类服务。它只需要使用ViewModelType作为键、ViewType作为值的Dictionary。Register方法向字典添加内容,ShowWindow方法根据传入的ViewModel查找正确的View,创建View,设置DataContext,然后调用Show方法。
大多数MVVM框架都会为您提供类似的功能。例如,Caliburn就有一个名为ViewLocator的优秀解决方案,它只需使用命名约定即可。以下链接总结了相关信息:http://devlicio.us/blogs/rob_eisenberg/archive/2010/07/04/mvvm-study-segue-introducing-caliburn-micro.aspx 另一方面,Cinch称之为WPFUIVisualizerService,您可以在此处实际操作并了解更多信息:http://www.codeproject.com/KB/WPF/CinchIII.aspx 这些应该可以帮助您开始。

1
+1 感谢您详细的回复。虽然我觉得这种方法很有趣,但我的应用程序已经相当成熟了。视图使用未命名的“DataTemplate”资源和“ContentControl”,它们的“Content”属性绑定到其他ViewModel中的ViewModel属性。更改这个需要很多工作,但这并非不可能...如果一天有更多时间就好了! - Sheridan
1
只是让你知道通常不是一种或另一种方法。我几乎总是使用DataTemplates来做到这一点。然而,正如你指出的那样 - 当你需要打开一个新窗口(或对话框)时,这种方法就行不通了。当你考虑一下,你用DataTemplate所做的事情与ViewLocator所做的事情是一样的。你基本上是根据它的DataType“注册”ViewModel到View... 无论如何,我只是想让你知道我认为你做得很正确,唯一的问题是当不能使用DataTemplates时。 - Matt West
谢谢您的额外评论 - 我想我一定会进一步研究这个问题。 - Sheridan
你如何/在哪里定义WindowViewLoaderService?它是单例吗?由App类拥有?还是其他的? - Dave

6
首先要说的是,“在代码后台没有任何代码”实际上是一个“谬论”。如果你想要务实,而且你发现有一些代码(尽可能少最好)会让你的生活更轻松并解决你的问题,那么你应该去做。
然而,在这种情况下,有一些松散耦合的方法来解决。您可以拥有一个为您执行交互的服务。您从ViewModel启动用户交互,服务负责此操作(例如显示ChildWindow),并将用户响应返回给您。该服务可以很容易地进行模拟测试。它也可以被单独测试。
那就是说,如果您想自己做事情。如果您想要一个框架来为您完成繁重的工作,您可以查看Prism提供的InteractionRequest功能。这是MSDN文章,讨论高级MVVM场景,其中包括{{link1:用户交互模式}}部分。这就是我做的方式,非常简单,优雅和直接。希望这可以帮助您 :)

1
+1 感谢您的评论和有用的链接。我曾经调查过Prism,发现我不需要它提供的功能,决定编写一个自定义的轻量级框架...但仍在努力完善中。 - Sheridan

3
为了更进一步解释Matt的答案,你可以将所有的视图都作为用户控件。然后创建一个ViewContainer,这是一个带有数据模板的窗口(如你所描述的)。然后,只需向窗口服务发送您要打开的视图模型,该服务将设置DataContext。服务将打开窗口,内容控件将为视图模型解析正确的视图。
这意味着所有的注册都在XAML中完成,窗口服务只需要知道如何打开和关闭窗口即可。

1
这是一篇旧帖子,但或许会对某些人有所帮助:我使用MVVM,从ViewModel向View抛出事件以打开子窗口。唯一的代码就是处理事件、打开窗口、设置子窗口的所有者,基本上就这些了。在ViewModel中,如果事件处理程序为空,则视图没有订阅它,也不会触发。VM不知道View的存在。代码也很简单,只需要几行就可以完成。

0
在这种情况下,View 应该处理子窗口的打开。然而,ViewModel 可能会驱动窗口的创建,但是通过调用 View 来创建新的窗口。这将保留 MVVM 模式的逻辑:ViewModel 有“大脑”,但不涉及特定窗口的创建。

但是视图如何打开一个新的“窗口”? - Sheridan

0

ViewModel 只用于呈现系统状态和 UI 逻辑。一个 ViewModel 可能被多个视图引用。它不知道 UI 特定代码,如父/子关系、位置、布局、大小等。因此最好在 View 的代码后台使用 ViewModel 的状态更改事件或命令事件和事件参数弹出子窗口。这样你就可以在 UI 层指定哪一个是父视图。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接