使用 MVVM 在 WPF 中创建新窗口的最佳方法
Posted
技术标签:
【中文标题】使用 MVVM 在 WPF 中创建新窗口的最佳方法【英文标题】:The best approach to create new window in WPF using MVVM 【发布时间】:2011-01-07 17:07:28 【问题描述】:在邻居帖子中:How should the ViewModel close the form? 我已经发布了如何使用 MVVM 关闭窗口的愿景。现在我有一个问题:如何打开它们。
我有一个主窗口(主视图)。如果用户单击“显示”按钮,则应显示“演示”窗口(模式对话框)。使用 MVVM 模式创建和打开窗口的更好方法是什么?我看到了两种通用方法:
第一个(可能是最简单的)。事件处理程序“ShowButton_Click”应该在主窗口后面的代码中实现,如下所示:
private void ModifyButton_Click(object sender, RoutedEventArgs e)
ShowWindow wnd = new ShowWindow(anyKindOfData);
bool? res = wnd.ShowDialog();
if (res != null && res.Value)
// ... store changes if neecssary
-
如果我们应该更改“显示”按钮状态(启用/禁用),我们将需要添加管理按钮状态的逻辑;
源代码与“旧式”WinForms 和 MFC 源代码非常相似 - 我不确定这是好是坏,请告知。
还有什么我错过的吗?
另一种方法:
在 MainWindowViewModel 中,我们将实现“ShowCommand”属性,该属性将返回命令的 ICommand 接口。依次命令:
将引发“ShowDialogEvent”; 将管理按钮状态。这种方法更适合 MVVM 但需要额外的编码:ViewModel 类不能“显示对话框”所以 MainWindowViewModel 只会引发“ShowDialogEvent”,我们需要在 MainWindowView 的 MainWindow_Loaded 方法中添加事件处理程序,像这样:
((MainWindowViewModel)DataContext).ShowDialogEvent += ShowDialog;
(ShowDialog - 类似于 'ModifyButton_Click' 方法。)
所以我的问题是: 1. 你还有其他方法吗? 2. 您认为列出的其中一项是好是坏? (为什么?)
欢迎任何其他想法。
谢谢。
【问题讨论】:
我在this post 中回答了一个类似的问题,涉及一个非常简单的行为。 'ViewModel 类不能“显示对话框”' 【参考方案1】:一些 MVVM 框架(例如MVVM Light)使用Mediator pattern。 因此,要打开一个新窗口(或创建任何视图),某些特定于视图的代码将订阅来自中介者的消息,而 ViewModel 将发送这些消息。
像这样:
订阅
Messenger.Default.Register<DialogMessage>(this, ProcessDialogMessage);
...
private void ProcessDialogMessage(DialogMessage message)
// Instantiate new view depending on the message details
在视图模型中
Messenger.Default.Send(new DialogMessage(...));
我更喜欢在单例类中进行订阅,只要应用程序的 UI 部分存在,它就会“存在”。 总结一下:ViewModel 传递诸如“我需要创建一个视图”之类的消息,UI 会监听这些消息并对其进行操作。
当然,没有“理想”的方法。
【讨论】:
您好,arconaut,您介意清除一下,您实际上将订阅代码放在哪里?在视图后面的代码中? 它可以在一些“静态”视图的代码隐藏中,例如主窗口。或者在负责实例化视图的单独类中更好。【参考方案2】:我最近也在考虑这个问题。如果您在项目中使用Unity 作为“容器”或任何依赖注入的东西,这是我的一个想法。我想通常你会覆盖App.OnStartup()
并在那里创建你的模型、视图模型和视图,并给每个适当的参考。使用 Unity,您可以为容器提供对模型的引用,然后使用容器来“解析”视图。 Unity 容器会注入您的视图模型,因此您永远不会直接实例化它。一旦你的观点得到解决,你就可以打电话给Show()
。
在我观看的示例视频中,Unity 容器被创建为OnStartup
中的局部变量。如果您在 App 类中将其创建为公共静态只读属性会怎样?然后,您可以在主视图模型中使用它来创建新窗口,自动注入新视图所需的任何资源。类似App.Container.Resolve<MyChildView>().ShowDialog();
。
我想您可以在测试中以某种方式模拟对 Unity 容器的调用结果。或者,也许您可以在 App 类中编写像 ShowMyChildView()
这样的方法,它基本上就是我上面描述的。模拟对App.ShowMyChildView()
的调用可能很容易,因为它只会返回bool?
,嗯?
嗯,这可能并不比使用new MyChildView()
更好,但这是我的一个小想法。我以为我会分享它。 =)
【讨论】:
经过一些经验,Unity的使用是我所看到的最好的。 小心,因为这违反了 MVVM,因为您在 ViewModel 中引用视图 (MyChildView
)。不光是view的类型,直接就是instance(你叫ShowDialog()
)就可以了。
也许我不理解你的解决方案,因为在我看来,孩子的父母不清楚,如果它被发送到 App 对象。【参考方案3】:
我有点晚了,但我发现现有的答案不够。我会解释为什么。一般来说:
从 View 访问 ViewModel 就可以了, 从 ViewModel 访问 View 是错误的,因为它引入了循环依赖并使得 ViewModel 难以测试。本尼·乔比根的回答:
App.Container.Resolve<MyChildView>().ShowDialog();
这实际上并不能解决任何问题。您正在以紧密耦合的方式从 ViewModel 访问您的视图。与new MyChildView().ShowDialog()
的唯一区别是您通过了一层间接。我看不出比直接调用 MyChildView ctor 有什么优势。
如果你使用界面作为视图会更干净:
App.Container.Resolve<IMyChildView>().ShowDialog();`
现在 ViewModel 没有与视图紧密耦合。但是我发现为每个视图创建界面是非常不切实际的。
阿尔科诺特的回答:
Messenger.Default.Send(new DialogMessage(...));
这样更好。似乎 Messenger 或 EventAggregator 或其他 pub/sub 模式是 MVVM 中所有事物的通用解决方案 :) 缺点是更难调试或导航到 DialogMessageHandler
。这太间接了恕我直言。例如,您将如何读取对话框的输出?通过修改DialogMessage?
我的解决方案:
你可以像这样从 MainWindowViewModel 打开窗口:
var childWindowViewModel = new MyChildWindowViewModel(); //you can set parameters here if necessary
var dialogResult = DialogService.ShowModal(childWindowViewModel);
if (dialogResult == true)
//you can read user input from childWindowViewModel
DialogService 只接受对话框的 ViewModel,因此您的 ViewModel 完全独立于 View。在运行时,DialogService 可以找到合适的视图(例如使用命名约定)并显示它,或者可以在单元测试中轻松地模拟它。
在我的例子中,我使用这个接口:
interface IDialogService
void Show(IDialogViewModel dialog);
void Close(IDialogViewModel dialog);
bool? ShowModal(IDialogViewModel dialog);
MessageBoxResult ShowMessageBox(string message, string caption = null, MessageBoxImage icon = MessageBoxImage.No...);
interface IDialogViewModel
string Caption get;
IEnumerable<DialogButton> Buttons get;
其中 DialogButton 指定 DialogResult 或 ICommand 或两者。
【讨论】:
感谢分享您的解决方案。有什么方法可以将您的解决方案放在一起并与我分享?我正在尝试使用 Visual Basic 来实现它,并且有一些投诉。至少我可以确保我没有遗漏任何东西。【参考方案4】:看看我目前在 Silverlight 中显示模态对话框的 MVVM 解决方案。 它解决了您提到的大多数问题,但它完全从特定于平台的事物中抽象出来并且可以重用。此外,我没有使用任何代码隐藏,仅与实现 ICommand 的 DelegateCommands 绑定。 Dialog 基本上是一个 View - 一个单独的控件,它有自己的 ViewModel,它从主屏幕的 ViewModel 显示,但通过 DelagateCommand 绑定从 UI 触发。
在此处查看完整的 Silverlight 4 解决方案Modal dialogs with MVVM and Silverlight 4
【讨论】:
【参考方案5】:我使用一个控制器来处理视图之间传递的所有信息。所有视图模型都使用控制器中的方法来请求更多信息,这些信息可以实现为对话框、其他视图等。
看起来像这样:
class MainViewModel
public MainViewModel(IView view, IModel model, IController controller)
mModel = model;
mController = controller;
mView = view;
view.DataContext = this;
public ICommand ShowCommand = new DelegateCommand(o=>
mResult = controller.GetSomeData(mSomeData);
);
class Controller : IController
public void OpenMainView()
IView view = new MainView();
new MainViewModel(view, somemodel, this);
public int GetSomeData(object anyKindOfData)
ShowWindow wnd = new ShowWindow(anyKindOfData);
bool? res = wnd.ShowDialog();
...
【讨论】:
感谢您的回答,但您能否澄清一下,MainViewModel 构造函数中的“视图”变量是什么?您是否从您的 ModelView 中引用了视图(这破坏了 MVVM 方法)?另一个问题:如何使用 Controller.OpenMainView 打开窗口?我看到您正在创建“MainViewModel”的实例,但我没有看到视图是在哪里创建的......?谢谢。 修复了缺失的引用。视图、模型和视图模型必须在某处创建和连接。作为一个例子,我在控制器中完成了它,但它也可以在 IoC 容器中处理。为什么你认为从视图模型中引用视图会破坏 MVVM?。 感谢您的回答。请参阅文章:msdn.microsoft.com/en-us/magazine/dd419663.aspx“与 MVP 中的 Presenter 不同,ViewModel 不需要引用视图”这就是为什么我想避免从 ModelView 引用视图。 请注意,我没有引用真实的视图,只是一个 IView 接口,大部分时间只包含 DataContext 和 Close()。如果我不在视图模型中分配数据上下文并且数据绑定关闭对我来说没有意义,我发现使用 IoC 容器会不必要地复杂。 我只是重写 App.OnStartup() 方法,在那里创建我的主视图并将其传递给视图模型。我观看了 Jason Dolinger 的 MVVM 视频,他就是这样做的。将接口引用(实际上仍然是引用,对吗?)传递给视图模型似乎并不好。【参考方案6】:我的方法与阿德里安的方法相似。但是,在我的情况下,控制器永远不会与具体的视图类型一起使用。 Controller 与 View 完全解耦 - 与 ViewModel 相同。
在 WPF Application Framework (WAF) 的 ViewModel 示例中可以看到这是如何工作的。
。
最好的问候,
jbe
【讨论】:
以上是关于使用 MVVM 在 WPF 中创建新窗口的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章
遵循 MVVM 模式在 WPF 应用程序中处理导航的最佳方法是啥?
在 WPF PRISM/MVVM 应用程序中避免内存泄漏的最佳方法是啥