有几种方法可以实现。
您可以定义一个对话框/导航/窗口服务接口,该接口在ViewModels项目中定义。您需要决定ViewModels将如何表达它们想要打开哪个窗口。我通常使用IDialogViewModel接口,其中一些ViewModel实现,并将ViewModel的实例传递到服务中,但您可以使用枚举、字符串等任何你想要的方式,以便您的实现可以映射到将要打开的真实窗口。
例如:
public interface IDialogService
{
bool? ShowDialog(object dialogViewModel);
}
希望打开新窗口的视图模型将接收该服务的实例,并使用它来表达打开窗口的意图。在您的视图项目中,您需要定义一个类型来实现您的服务接口,并将打开窗口的实际逻辑放在其中。
以此示例为例:
public class DialogService : IDialogService
{
private Stack<Window> windowStack = new Stack<Window>();
public DialogService(Window root)
{
this.windowStack.Push(root);
}
public bool? ShowDialog(object dialogViewModel)
{
Window dialog = MapWindow(dialogViewModel);
dialog.DataContext = dialogViewModel;
dialog.Owner = this.windowStack.Peek();
this.windowStack.Push(dialog);
bool? result;
try
{
result = dialog.ShowDialog();
}
finally
{
this.windowStack.Pop();
}
return result;
}
}
您的主要项目将负责创建和注入对话服务到需要它的ViewModels中。在示例中,应用程序将创建一个新的对话服务实例,并将MainWindow传递给它。
另一种实现方式是使用某种形式的消息模式(链接1 链接2)。此外,如果您想要简单的方法,还可以使您的ViewModels在需要打开Windows时引发事件,并让Views订阅这些事件。
编辑
我在我的应用程序中使用的完整解决方案通常会更加复杂,但基本思路是相同的。我有一个基准的DialogWindow,它期望一个实现了IDialogViewModel接口的ViewModel作为DataContext。此接口抽象了您在对话框中期望的一些功能,例如接受/取消命令以及关闭事件,因此您也可以从ViewModel中关闭窗口。DialogWindow基本上由ContentPresenter组成,其Content属性绑定到DataContext,并在DataContext更改时挂钩关闭事件(以及其他一些内容)。
每个“对话框”由一个IDialogViewModel和一个关联的View(UserControl)组成。为了映射它们,我只需在应用程序的资源中声明隐式DataTemplates。在我展示的代码中,唯一的区别是不会有MapWindow方法,窗口实例总是DialogWindow。
我使用一种额外的技巧来在对话框之间重复使用布局元素。一种方法是将它们包含在DialogWindow中(接受/取消按钮等)。我喜欢保持DialogWindow的整洁(因此我甚至可以用它来创建“非对话框”对话框)。我为ContentControl声明了一个包含公共接口元素的模板,当我声明一个View-ViewModel映射模板时,我使用带有我的“对话框模板”的ContentControl包装View。然后,您可以为DialogWindow拥有尽可能多的“Master templates”,如“向导样式”(例如)。