在WPF的MVVM模式中,处理对话框是更复杂的操作之一 . 由于您的视图模型对视图一无所知,因此对话通信可能很有趣 . 我可以公开一个ICommand,当视图调用它时,会出现一个对话框 .
有没有人知道处理对话结果的好方法?我说的是关于Windows对话框,比如MessageBox .
我们这样做的方法之一是在视图模型上有一个事件,当需要对话框时视图会订阅 .
public event EventHandler<MyDeleteArgs> RequiresDeleteDialog;
这没关系,但这意味着视图需要代码,这是我想远离的东西 .
23 回答
我建议放弃1990年代的模态对话框,而是将控件实现为覆盖(画布绝对定位),其中可见性与VM中的布尔值相关联 . 更接近ajax类型控件 .
这非常有用:
如:
这是我如何实现一个用户控件 . 单击“x”会关闭usercontrol代码后面的一行代码中的控件 . (因为我在一个dll中的.exe和ViewModels中有我的视图,所以对于操作UI的代码我并不感到难过 . )
你应该使用调解员 . Mediator是一种常见的设计模式,在其某些实现中也称为Messenger . 它是Register / Notify类型的范例,使您的ViewModel和Views能够通过低耦合的消息传导机制进行通信 .
您应该查看google WPF Disciples组,然后只搜索Mediator . 你会对答案感到满意......
但是你可以从这开始:
http://joshsmithonwpf.wordpress.com/2009/04/06/a-mediator-prototype-for-wpf-apps/
请享用 !
编辑:您可以在此处查看MVVM Light Toolkit对此问题的答案:
http://mvvmlight.codeplex.com/Thread/View.aspx?ThreadId=209338
一个好的MVVM对话框应该:
仅使用XAML声明 .
从数据绑定中获取所有这些行为 .
不幸的是,WPF不提供这些功能 . 显示对话框需要对ShowDialog()进行代码隐藏调用 . 支持对话框的Window类不能在XAML中声明,因此不能轻易地将数据绑定到DataContext .
为了解决这个问题,我编写了一个XAML存根控件,它位于逻辑树中,并将数据绑定中继到一个Window,并处理显示和隐藏对话框 . 你可以在这里找到它:http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
它只是简单地使用,并且不需要对ViewModel进行任何奇怪的更改,也不需要事件或消息 . 基本调用如下所示:
您可能想要添加一个设置显示的样式 . 我在文章中解释它 . 我希望这可以帮助你 .
我使用this方法与MVVM进行对话 .
我现在要做的就是从我的视图模型中调用以下内容 .
我目前的解决方案解决了您提到的大部分问题,但它完全从平台特定事物中抽象出来,可以重复使用 . 此外,我没有使用代码隐藏仅与实现ICommand的DelegateCommands绑定 . Dialog基本上是一个View - 一个单独的控件,它有自己的ViewModel,它从主屏幕的ViewModel显示,但是通过DelagateCommand绑定从UI触发 .
在这里查看完整的Silverlight 4解决方案Modal dialogs with MVVM and Silverlight 4
在学习(仍在学习)MVVM时,我真的很挣这个概念 . 我决定了什么,以及我认为其他人已经决定但我不清楚的是:
我最初的想法是不应该允许ViewModel直接调用对话框,因为它没有业务决定对话框应该如何显示 . 因此我开始考虑如何传递消息,就像我在MVP中那样(即View.ShowSaveFileDialog()) . 但是,我认为这是错误的做法 .
ViewModel可以直接调用对话框 . 但是,当您测试ViewModel时,这意味着对话框将在测试期间弹出,或者一起失败(从未真正尝试过) .
因此,需要进行的是测试是使用对话框的“测试”版本 . 这意味着,对于您所拥有的对话框,您需要创建一个接口,并模拟对话框响应或创建一个具有默认行为的测试模拟 .
您应该已经使用某种服务定位器或IoC,您可以根据上下文为您提供正确的版本 .
使用这种方法,您的ViewModel仍然是可测试的,并且根据您模拟对话框的方式,您可以控制行为 .
希望这可以帮助 .
花了一些时间后,我终于提出了以下解决方案 . 这种方法的一些关键优势是:
实现MVVM Light自己的
IDialogService
.查看没有't need to add MVVM Light'参考 .
VM甚至不需要
PresentationFramework
参考 .使用MVVM Light自己的Messenger通道,因此表示层和VM层是分离的 .
支持具有返回值的对话框,例如是/否问题或确定/取消情况 .
支持自定义对话框 .
这是
IDialogService
的实现(进入 ViewModel 项目):这是表示层(进入 View 项目)
有两种很好的方法可以做到这一点,1)对话服务(简单,干净),2)视图辅助 . View辅助提供了一些简洁的功能,但通常不值得 .
对话服务
a)一个对话框服务接口,如via构造函数或一些依赖容器:
interface IDialogService { Task ShowDialogAsync(DialogViewModel dlgVm); }
b)您的IDialogService实现应打开一个窗口(或将一些控件注入活动窗口),创建一个对应于给定dlgVm类型名称的视图(使用容器注册或约定或具有类型的ContentPresenter)相关的DataTemplates) . ShowDialogAsync应该创建一个TaskCompletionSource并返回它的.Task proptery . 当您想要关闭时,DialogViewModel类本身需要一个可以在派生类中调用的事件,并在对话框视图中观察以实际关闭/隐藏对话框并完成TaskCompletionSource .
b)要使用,只需在您的某个DialogViewModel派生类的实例上调用await this.DialogService.ShowDialog(myDlgVm) . 等待返回后,查看您在对话框VM上添加的属性,以确定发生了什么;你甚至不需要回调 .
查看已获得帮助
这使您可以在viewmodel上侦听事件 . 这可以全部包含在混合行为中,以避免代码落后和资源使用,如果你是如此倾向(FMI,子类“行为”类,以查看类固醇上的一种Blendable附加属性) . 现在,我们将在每个视图上手动执行此操作:
a)使用自定义有效负载(DialogViewModel派生类)创建OpenXXXXXDialogEvent .
b)让视图在其OnDataContextChanged事件中订阅该事件 . 如果旧值!= null并且在Window的Unloaded事件中,请务必隐藏和取消订阅 .
c)当事件触发时,让视图打开您的视图,该视图可能位于页面上的资源中,或者您可以按照惯例在其他位置找到它(例如在对话框服务方法中) .
这种方法更灵活,但需要更多的工作才能使用 . 我不太习惯 . 例如,一个很好的优点是能够将视图物理地放置在选项卡中 . 我已经使用算法将其放在当前用户控件的边界中,或者如果不够大,则遍历可视树,直到找到足够大的容器 .
这允许对话框靠近他们实际使用的位置,只调暗与当前活动相关的应用程序部分,让用户在应用程序内移动而不必手动推送对话框,甚至有多个准对象模式对话框在不同的选项卡或子视图上打开 .
使用freezable命令
我认为对话框的处理应该是视图的责任,视图需要有代码来支持它 .
如果更改ViewModel - View交互以处理对话框,则ViewModel依赖于该实现 . 解决此问题的最简单方法是使View负责执行任务 . 如果这意味着显示一个对话框然后很好,但也可能是状态栏中的状态消息等 .
我的观点是,MVVM模式的重点在于将业务逻辑与GUI分离,因此您不应该在业务层(ViewModel)中混合使用GUI逻辑(以显示对话框) .
一个有趣的替代方法是使用负责显示视图(对话框)的控制器 .
如何工作由 WPF Application Framework (WAF) 显示 .
为什么不在VM中引发事件并在视图中订阅事件?这将使应用程序逻辑和视图保持分离,并且仍允许您使用子窗口进行对话 .
我已经实现了一个侦听来自ViewModel的Message的Behavior . 它基于Laurent Bugnion解决方案,但由于它不使用代码并且更可重用,我认为它更优雅 .
How to make WPF behave as if MVVM is supported out of the box
我认为视图可以有代码来处理视图模型中的事件 .
根据事件/场景,它还可以具有订阅视图模型事件的事件触发器,以及响应中要调用的一个或多个动作 .
我有相同的情况,并将MessageBox包装到设计器的隐形控件中 . 细节在我的博客中
http://geekswithblogs.net/mukapu/archive/2010/03/12/user-prompts-messagebox-with-mvvm.aspx
同样可以扩展到任何模态对话框,文件浏览控件等 .
我在这个问题的答案中描述了我自己的窗口加载器:
Managing multiple WPF views in an application
Karl Shifflett创建了一个示例应用程序,用于使用服务方法和Prism InteractionRequest方法显示对话框 .
我喜欢这种服务方法 - 它不太灵活,因此用户不太可能破坏某些东西:)它也与我的应用程序的WinForms部分一致(MessageBox.Show)但是如果你打算展示很多不同的对话框,那么InteractionRequest就是一个更好的方式去 .
http://karlshifflett.wordpress.com/2010/11/07/in-the-box-ndash-mvvm-training/
我知道这是一个老问题,但是当我进行这个搜索时,我发现了很多相关问题,但我没有找到一个非常明确的回答 . 所以我自己实现了dialogbox / messagebox / popin,我分享了!
我认为它是"MVVM proof",我试着让它变得简单和正确,但我是WPF的新手,所以请随意发表评论,甚至提出拉取请求 .
https://github.com/Plasma-Paris/Plasma.WpfUtils
你可以像这样使用它:
或者像这样,如果你想要更复杂的popin:
它显示的是这样的事情:
在询问how the view model for a task or dialog should look like时,我正在思考类似的问题 .
我目前的解决方案如下:
当视图模型决定需要用户输入时,它会提取
SelectionTaskModel
的实例以及用户可能的选择 . 基础设施负责调出相应的视图,在适当的时候将根据用户的选择调用Choose()
函数 .我在同样的问题上挣扎 . 我想出了一种在View和ViewModel之间进行相互通信的方法 . 您可以启动从ViewModel向View发送消息,告诉它显示消息框,然后它将报告结果 . 然后ViewModel可以响应从View返回的结果 .
我在my blog中证明了这一点:
我写了一篇关于这个主题的相当全面的文章,并为MVVM Dialogs开发了一个pop-in库 . 严格遵守MVVM不仅可行,而且在正确实施时非常干净,并且可以轻松扩展到不依赖于它的第三方库:
https://www.codeproject.com/Articles/820324/Implementing-Dialog-Boxes-in-MVVM
对不起,但我必须要插入 . 在Prism项目中找到Prism.Wpf.Interactivity命名空间之前,我已经完成了几个建议的解决方案 . 您可以使用交互请求和弹出窗口操作来滚动自定义窗口,或者在“通知和确认”弹出窗口中内置更简单的需求 . 这些创建真正的窗口并按此管理 . 您可以在对话框中传递具有所需依赖项的上下文对象 . 自从我找到它以来,我们在我的工作中使用了这个解我们这里有很多资深开发人员,没有人能想出更好的东西 . 我们之前的解决方案是将对话框服务放入叠加层并使用演示者类来实现它,但是您必须为所有对话框视图模型等设置工厂 .
这不是微不足道的,但也不是非常复杂 . 它是内置于Prism,因此最好(或更好)实践恕我直言 .
我2美分!
编辑:是的我同意这不是一个正确的MVVM方法,我现在使用类似于blindmeis建议的东西 .
你能做到这一点的方法之一是
在主视图模型中(打开模态的位置):
在你的模态窗口视图/ ViewModel中:
XAML:
视图模型:
或类似于此处发布的内容WPF MVVM: How to close a window