在Model View ViewModel中打开新窗口的最佳位置

9
我有一个MVVM应用程序。在其中一个ViewModel中,有'FindFilesCommand',它填充了一个ObservableCollection。然后我在同一ViewModel中实现了'RemoveFilesCommand'。这个命令会弹出一个窗口来获取更多的用户输入。
在保持MVVM范例的同时,最好的方法是在哪里/什么地方进行此操作?在ViewModel中执行以下操作似乎是错误的:
new WhateverWindow( ).Show( )
谢谢, Steve

我在这篇文章中已回答了一个非常类似的问题。 - Mike Fuchs
7个回答

2

我个人认为这种情况是主窗口视图模型想要向最终用户提供需要完成的任务。

它应该负责创建和初始化任务。视图应该负责创建和显示子窗口,并使用任务作为新实例化窗口的视图模型。

任务可以被取消或提交。当任务完成时,它会发出通知。

窗口使用通知来关闭自身。如果有后续工作,父视图模型使用通知来进行额外的工作。

我认为这是最接近自然/直观的代码后台方法的做法,但重构成将UI无关的关注点拆分为视图模型,而不引入其他概念开销,如服务等。

我在Silverlight中实现了这个功能。请访问http://www.nikhilk.net/ViewModel-Dialogs-Task-Pattern.aspx获取更多详细信息…… 我很乐意听取评论/进一步建议。


1
Onyx(http://www.codeplex.com/wpfonyx)将为此提供一个相当不错的解决方案。例如,看一下ICommonDialogProvider服务,它可以像这样从ViewModel中使用:
ICommonFileDialogProvider provider = this.View.GetService<ICommonDialogProvider>();
IOpenFileDialog openDialog = provider.CreateOpenFileDialog();
// configure the IOpenFileDialog here... removed for brevity
openDialog.ShowDialog();

这与使用具体的OpenFileDialog非常相似,但是它是完全可测试的。您真正需要的解耦程度将成为您的实现细节。例如,在您的情况下,您可能希望一个服务完全隐藏您正在使用对话框的事实。大致如下:

public interface IRemoveFiles
{
   string[] GetFilesToRemove();
}

IRemoveFiles removeFiles = this.View.GetService<IRemoveFiles>();
string[] files = removeFiles.GetFilesToRemove();

接下来,您需要确保视图(View)有一个 IRemoveFiles 服务的实现,对于这一点,有几个选项可供选择。

Onyx 还没有准备好发布,但是当前代码完全可以工作并且至少可以用作参考点。我希望尽快稳定 V1 接口,并在我们拥有良好文档和样例时发布。


嗨。'this.View.GetService()':这个this.View返回什么,它是否需要引用我的“view”命名空间中的任何类型?对我来说,这似乎会破坏MVVM - 请注意,我可能错了 - 我非常新手。 - Steve Dunn
它返回一个View实例,是Onyx框架的一部分。View是IServiceProvider,并且还提供对ViewElement(View所关联的元素)作为DependencyObject的访问。如果您从未使用过ViewElement,则不会违反MVVM原则。请查看Simple示例。 - wekempf

1
在Jaime Rodriguez和Karl Shifflet的Southridge房地产示例中,他们正在创建视图模型中的窗口,更具体地说,在绑定命令的执行部分:
    protected void OnShowDetails ( object param ) 
    {
        // DetailsWindow window = new DetailsWindow();
        ListingDetailsWindow window = new ListingDetailsWindow();
        window.DataContext = new ListingDetailsViewModel ( param as Listing, this.CurrentProfile ) ; 
        ViewManager.Current.ShowWindow(window, true); 
    } 

这是链接: http://blogs.msdn.com/jaimer/archive/2009/02/10/m-v-vm-training-day-sample-application-and-decks.aspx

我想这不是一个大问题。毕竟,Viewmodel充当视图和业务层/数据层之间的“粘合剂”,所以在我看来与View(UI)耦合是正常的...


谢谢回复。这是否意味着ViewModel现在引用了ListingDetailsWindow的View(以及ViewManager)? - Steve Dunn
1
不,ViewModel 理想情况下应该独立于 UI 技术。当然它是粘合剂,但它应该避免对具体视图的任何依赖,并尽可能避免对具体 UI 组件/机制的任何依赖。 - Falcon

0
我们正在做的事情类似于这个描述: http://www.codeproject.com/KB/WPF/DialogBehavior.aspx?msg=3439968#xx3439968xx ViewModel 有一个叫 ConfirmDeletionViewModel 的属性。一旦我设置了该属性,行为就会打开对话框 (模态或非模态),并使用 ConfirmDeletionViewModel。此外,我还传递了一个委托,在用户想要关闭对话框时执行。基本上,这是一个将 ConfirmDeletionViewModel 属性设置为 null 的委托。

0

我也在MVVM中遇到了这个问题。我的第一个想法是尝试找到不使用对话框的方法。使用WPF可以更容易地想出比对话框更好的方法。

当无法避免时,最好的选择似乎是让ViewModel调用一个共享类来获取用户信息。ViewModel应该完全不知道正在显示对话框。

因此,作为一个简单的例子,如果需要用户确认删除,则ViewModel可以调用DialogHelper.ConfirmDeletion(),它将返回用户是否选择是或否的布尔值。实际的对话框显示将在Helper类中完成。

对于更高级的对话框,返回大量数据,助手方法应返回包含对话框中所有信息的对象。

我同意它不是MVVM的最顺畅的适合方案,但我还没有找到更好的例子。


调用 "DialogHelper.ConfirmDeletion()" 实际上不会产生任何区别,我认为您仍然在代码中混合了纯 UI。毕竟,“DialogHelper”已经表明它是一个窗口帮助程序... - Tom Deleu
我同意,但我没有看到更好的方法来做到这一点。对话框必须从ViewModel中显示才能工作。我认为至少不要显式地在ViewModel中显示对话框是一个不错的步骤。这样可以轻松重用对话框并进行完全替换。 - timothymcgrath
我在考虑也许事件可能是正确的方法?但还不确定细节以及它如何与纯MVVM相适应。 - Steve Dunn

0

我必须说,服务是这里的最佳选择。

服务界面提供了一种返回数据的方法。然后,该服务的实际实现可以显示对话框或其他内容以获取接口所需的信息。

这种方式可以在测试中模拟服务接口,而ViewModel则毫不知情。就ViewModel而言,它请求某些信息的服务,并获得了所需的信息。


嗨。但是谁拥有这个服务呢?视图还是模型?在这两个地方似乎都不太合适。 - Steve Dunn
你说谁拥有这个服务?它是单例的。如果你考虑一下这个服务提供的内容,那么调用它的是虚拟机,因为需要确认删除等操作。 - Cameron MacFarland
抱歉,我的意思是“服务接口位于哪个命名空间中”。 - Steve Dunn
啊。不确定这是否重要,但您可以将其放在自己的命名空间中与其他服务一起。 - Cameron MacFarland

-1
对于这种类型的对话框,我将其定义为FindFilesCommand的嵌套类。如果基本对话框在许多命令中使用,则将其定义为可由这些命令访问的模块,并让命令相应地配置对话框。
命令对象足以显示对话框如何与软件的其他部分交互。在我的软件中,命令对象驻留在它们自己的库中,因此对话框对系统的其他部分是隐藏的。
在我看来,做任何更花哨的事情都是过度设计。此外,试图将其保持在最高级别通常涉及创建大量额外的接口和注册方法。这是很多编码但收益很少。
像任何框架一样,盲目的奉献会让你走进一些奇怪的小巷。当您遇到糟糕的代码时,需要使用判断力来看看是否有其他技术可用。再次在我看来,对话框应该紧密绑定并定义在使用它们的命令旁边。这样五年后,我可以回到代码的那个部分,并查看该命令正在处理的所有内容。

在极少数情况下,如果对多个命令有用的对话框,我会将其定义在所有命令共用的模块中。然而,在我的软件中,也许只有20个对话框中的1个是这样的。主要例外是文件打开/保存对话框。如果一个对话框被几十个命令使用,那么我会完全定义一个接口,创建一个实现该接口的表单并注册该表单。

如果您的应用程序需要进行国际化本地化,则需要确保考虑到此方案,因为所有表单都不在一个模块中。


嗨。我认为在 ViewModel 中具体的对话框会影响可测试性。 - Steve Dunn

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