有几种方法。
您可以定义在 ViewModels 项目中定义的对话框/导航/窗口服务接口。您将需要决定 ViewModel 将如何表达他们想要打开的窗口。我通常使用我的一些 ViewModel 实现的 IDialogViewModel 接口,并将 ViewModel 的实例传递给服务,但您可以使用枚举、字符串,任何您想要的,因此您的实现可以映射到将是的真实窗口打开。
例如:
public interface IDialogService
{
bool? ShowDialog(object dialogViewModel);
}
想要打开新窗口的 ViewModel 将接收该服务的实例并使用它来表达打开窗口的意图。在您的 Views 项目中,您将定义一个实现您的服务接口的类型,以及打开窗口背后的真实逻辑。
按照示例:
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;
}
}
您的主项目将负责在需要它的 ViewModel 中创建和注入对话服务。在示例中,应用程序将创建一个新的对话服务实例,并将 MainWindow 传递给它。
一种类似的方法是使用某种形式的消息传递模式(link1 link2)。此外,如果您想要一些简单的东西,您还可以让您的 ViewModel 在他们想要打开 Windows 时引发事件并让 View 订阅它们。
编辑
我在我的应用程序中使用的完整解决方案通常有点复杂,但基本思路就是这样。我有一个基本的 DialogWindow,它需要一个将 IDialogViewModel 接口实现为 DataContext 的 ViewModel。该接口抽象了一些您期望在对话框中实现的功能,例如接受/取消命令以及关闭事件,因此您也可以从 ViewModel 关闭窗口。DialogWindow 基本上包含在 ContentPresenter 中,其中 Content 属性绑定到 DataContext 并在 DataContext 更改(以及其他一些事情)时挂钩 close 事件。
每个“对话框”都包含一个 IDialogViewModel 和一个关联的视图 (UserControl)。为了映射它们,我只需在 App 的资源中声明隐式 DataTemplates。在我展示的代码中,唯一的区别是不会有 MapWindow 方法,窗口实例始终是 DialogWindow。
我使用了一个额外的技巧来重用对话框之间的布局元素。方法是将它们包含在 DialogWindow 中(接受/取消按钮等)。我喜欢保持 DialogWindow 干净(所以我可以使用它来“非对话”对话框)。我使用通用界面元素为 ContentControl 声明了一个模板,当我声明一个 View-ViewModel 映射模板时,我用我的“对话框模板”应用了 ContentControl 来包装 View。然后,您可以根据需要为您的 DialogWindow 拥有尽可能多的“主模板”(例如,像“向导一样”的模板)。