在MVVM WPF中打开新窗口

我有button,我已经将此button绑定到ViewModel命令说OpenWindowCommand。 当我点击button,我想要打开新的窗口。 但是从视图模型创build窗口实例并显示窗口违反了MVVM。 我已经创build了像

interface IWindowService { void showWindow(object dataContext); } 

而WindowService像这样实现了这个接口

 class WindowService:IWindowService { public void showWindow(object dataContext) { ChildWindow window=new ChildWindow(); window.DataContext=dataContext; window.Show(); } } 

在这个类中我已经指定了ChildWindow。 所以这个类与显示ChildWindow紧密结合在一起。 当我想显示另一个窗口时,我必须使用相同的接口和逻辑来实现另一个类。如何使这个类是通用的,以便我可以传递任何窗口的实例,并且类将能够打开任何窗口? 我没有使用任何内置的MVVM框架。我已经阅读了很多关于StackOverflow的文章,但是我找不到任何解决scheme。

你说“创build窗口实例,并从视图模型显示窗口是违反MVVM”。 这是对的。

您现在正在尝试创build一个接口,该接口使用VM指定的一种视图。 这同样也是违规行为。 您可能已经将界面背后的创build逻辑抽象出来了,但是您仍然在从VM内请求视图创build。

虚拟机应该只关心创build虚拟机。 如果你确实需要一个新窗口来托pipe新的虚拟机,那么就像你所做的那样提供一个接口,但是不能接受一个视图。 你为什么需要这个观点? 大多数(VM优先)MVVM项目使用隐式数据模板来将视图与特定的VM相关联。 虚拟机一无所知。

喜欢这个:

 class WindowService:IWindowService { public void ShowWindow(object viewModel) { var win = new Window(); win.Content = viewModel; win.Show(); } } 

显然你需要确保你有VM-> View在app.xaml中设置的隐式模板才能工作。 这只是标准的VM第一MVVM。

例如:

 <Application x:Class="My.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:My.App.ViewModels" xmlns:vw="clr-namespace:My.App.Views" StartupUri="MainWindow.xaml"> <Application.Resources> <DataTemplate DataType="{x:Type vm:MyVM}"> <vw:MyView/> </DataTemplate> </Application.Resources> </Application> 

一种可能性是这样的:

 class WindowService:IWindowService { public void showWindow<T>(object DataContext) where T: Window, new() { ChildWindow window=new T(); window.Datacontext=DataContext; window.show(); } } 

那么你可以像这样做:

 windowService.showWindow<Window3>(windowThreeDataContext); 

有关新约束的更多信息,请参阅http://msdn.microsoft.com/en-gb/library/sd2w2ew5.aspx

注意: new() constraint只适用于窗口具有无参数构造函数的地方(但我想这在这种情况下不应该是个问题!)在更一般的情况下,请参阅创buildgenericstypes的实例? 为可能性。

在你的DataConext绑定的窗口中使用一个contentpresenter。 然后为你的DataContext定义一个Datatemplate,这样wpf就可以呈现你的DataContext。 类似于我的DialogWindow服务

所以你只需要一个带有ContentPresenter的ChildWindow:

 <Window x:Class="ChildWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight"> <ContentPresenter Content="{Binding .}"> </ContentPresenter> </Window> 

你可以写一个这样的函数:

 class ViewManager { void ShowView<T>(ViewModelBase viewModel) where T : ViewBase, new() { T view = new T(); view.DataContext = viewModel; view.Show(); // or something similar } } abstract class ViewModelBase { public void ShowView(string viewName, object viewModel) { MessageBus.Post( new Message { Action = "ShowView", ViewName = viewName, ViewModel = viewModel }); } } 

确保ViewBase有一个DataContext属性。 (你可以inheritanceUserControl)

一般来说,我会做一些消息总线,并让ViewManager侦听要求查看的消息。 ViewModels将发送一条消息,要求显示一个视图并显示数据。 ViewManager然后将使用上面的代码。

为了防止调用的ViewModel知道Viewtypes,可以将视图的string/逻辑名称传递给ViewManager,并让ViewManager将逻辑名称转换为types。

也许你可以通过窗口types。

尝试使用Activator.CreateInstance()

请参阅以下问题: 使用运行时确定的types实例化对象 。

解决scheme:

 // determine type here var type = typeof(MyClass); // create an object of the type var obj = (MyClass)Activator.CreateInstance(type); 

我发现接受的解决scheme非常有用,但是在实际尝试时,我发现它没有能力使用户控制(从VM – >视图映射产生的视图)停靠在​​托pipe窗口内以占据整个区域由它提供。 所以我扩展了这个解决scheme来包含这个能力:

 public Window CreateWindowHostingViewModel(object viewModel, bool sizeToContent) { ContentControl contentUI = new ContentControl(); contentUI.Content = viewModel; DockPanel dockPanel = new DockPanel(); dockPanel.Children.Add(contentUI); Window hostWindow = new Window(); hostWindow.Content = dockPanel; if (sizeToContent) hostWindow.SizeToContent = SizeToContent.WidthAndHeight; return hostWindow; } 

这里的技巧是使用DockPanel托pipe从VM转换来的视图。

然后,如果您希望窗口的大小与其内容的大小相匹配,则按照以下方法使用以前的方法:

 var win = CreateWindowHostingViewModel(true, viewModel) win.Title = "Window Title"; win.Show(); 

或者如果你有一个固定的窗口大小,如下所示:

 var win = CreateWindowHostingViewModel(false, viewModel) win.Title = "Window Title"; win.Width = 500; win.Height = 300; win.Show();