在MVVM WPF中打开新窗口

时间:2014-09-15 10:15:52

标签: c# wpf mvvm

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

这样的界面
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的文章,但我找不到任何解决方案。

6 个答案:

答案 0 :(得分:41)

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

您现在正在尝试创建一个接口,该接口采用VM指定的视图类型。这也是一种违规行为。您可能已经抽象出了界面背后的创建逻辑,但您仍然在虚拟机内请求查看创建。

VM应该只关心创建VM。如果您确实需要一个新窗口来托管新VM,那么请提供一个已完成的界面,但不提供视图的界面。你为什么需要这个观点?大多数(VM优先)MVVM项目使用隐式数据窗口将视图与特定VM相关联。 VM对它们一无所知。

像这样:

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

显然,您需要确保在app.xaml中设置了VM-> View隐式模板才能实现此功能。这只是标准的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>

答案 1 :(得分:6)

一种可能性就是:

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仅适用于窗口具有无参数构造函数的情况(但我认为在这种情况下这不应该成为问题!)在更一般的情况下,请参阅{{3}为了可能性。

答案 2 :(得分:3)

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

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属性。 (您可以继承UserControl)

一般情况下,我会制作某种消息总线并让ViewManager监听要求查看的消息。 ViewModels将发送一条消息,要求显示视图和要显示的数据。然后ViewManager将使用上面的代码。

为了防止调用ViewModel了解View类型,您可以将视图的字符串/逻辑名称传递给ViewManager,并让ViewManager将逻辑名称转换为类型。

答案 3 :(得分:3)

在Window中使用contentpresenter,将DataConext绑定到。 然后为您的DataContext定义一个Datatemplate,以便wpf可以呈现您的DataContext。类似于我的DialogWindow Service

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

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

</ContentPresenter>
</Window>

答案 4 :(得分:1)

我发现接受的解决方案非常有用,但在实际尝试时,我发现它无法在托管窗口内建立UserControl(由VM生成的视图 - &gt;视图映射),以便占据了它提供的整个区域。所以我扩展了解决方案以包含这个能力:

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来托管从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();

答案 5 :(得分:0)

也许你可以传递窗口类型。

尝试使用Activator.CreateInstance()

请参阅以下问题: Instantiate an object with a runtime-determined type

chakrit的解决方案:

// determine type here
var type = typeof(MyClass);

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