在MVVM中打开一个新窗口

15

假设我有一个MainWindow和一个MainViewModel,在这个例子中我没有使用MVVM LightPrism
在这个MainWindow中,我想点击一个MenuItemButton来打开一个NewWindow.xaml而不是UserControl
我知道如何在ContentControlFrame中使用UserControl打开一个新的UserControl

<ContentControl Content="{Binding Path=DisplayUserControl,UpdateSourceTrigger=PropertyChanged}" />

代码

public ViewModelBase DisplayUserControl
{
    get
    {
        if (displayUserControl == null)
        {
            displayUserControl = new ViewModels.UC1iewModel();
        }
        return displayUserControl;
    }
    set
    {
        if (displayUserControl == value)
        {
            return;
        }
        else
        {
            displayUserControl = value;
            OnPropertyChanged("DisplayUserControl");
        }
    }
}

MainWindowResourceDictionary中,我有:

<DataTemplate DataType="{x:Type localViewModels:UC1ViewModel}">
    <localViews:UC1 />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:UC2ViewModel}">
    <localViews:UC2 />
</DataTemplate>

问题在于我想要打开一个新的窗口,而不是一个UserControl。因此我使用了类似以下的代码:

private ICommand openNewWindow;

public ICommand OpenNewWindow
{
    get { return openNewWindow; }
}

public void DoOpenNewWindow()
{
    View.NewWindowWindow validationWindow = new View.NewWindow();
    NewWindowViewModel newWindowViewModel = new NewWindowViewModel();
    newWindow.DataContext = ewWindowViewModel;
    newWindow.Show();
}

接着将 OpenNewWindow 绑定到一个 MenuItemButton 上。
我知道这不是正确的方法,但正确的方法是什么呢?

谢谢!


为什么你认为那不是正确的方式?你有一个执行操作的 ICommand 绑定。 (这似乎正确吗?) - sircodesalot
1
测试此ViewModel可能会有问题,因为它依赖于NewWindowViewModel类,因为我在MainViewModel中的DoOpenNewWindow()方法中创建了一个实例。 - xargs
1个回答

27

这种类型的应用程序需要解决两个问题。

首先,您不希望View-Model直接创建和显示UI组件。使用MVVM的动机之一是在View-Model中引入可测试性,而使此类出现新窗口会使该类更难测试。

您需要解决的第二个问题是如何解决应用程序中的依赖关系,或者在这种情况下 - 如何将View-Model“连接”到相应的View?使用DI容器提供了一个可维护的解决方案。关于此主题的非常好的参考资料是Mark Seemann的Dependency Injection in .NET。他实际上也讨论了如何解决第一个问题!

为了解决前面的问题,您需要在代码中引入间接层,以使View-Model不依赖于创建新窗口的具体实现。下面的代码提供了一个非常简单的示例:

public class ViewModel
{
    private readonly IWindowFactory m_windowFactory;
    private ICommand m_openNewWindow;

    public ViewModel(IWindowFactory windowFactory)
    {
        m_windowFactory = windowFactory;

        /**
         * Would need to assign value to m_openNewWindow here, and associate the DoOpenWindow method
         * to the execution of the command.
         * */
        m_openNewWindow = null;  
    }

    public void DoOpenNewWindow()
    {
        m_windowFactory.CreateNewWindow();
    }

    public ICommand OpenNewWindow { get { return m_openNewWindow; } }
}

public interface IWindowFactory
{
    void CreateNewWindow();
}

public class ProductionWindowFactory: IWindowFactory
{

    #region Implementation of INewWindowFactory

    public void CreateNewWindow()
    {
       NewWindow window = new NewWindow
           {
               DataContext = new NewWindowViewModel()
           };
       window.Show();
    }

    #endregion
}

请注意,在您的视图模型构造函数中使用IWindowFactory的实现,并将创建新窗口的委托对象指定为该对象。这样可以在测试期间替换生产实现为另一个实现。


非常好的解决方案,我知道我必须在这里处理 DI。我想问你,这个 IWindowFactory 是工厂模式的方法吗?所以基本上是委托给工厂来创建那个 ViewModel? - xargs
IWindowFactory 的目的只是将窗口的实际创建委托给一个抽象层(在测试期间可以随后替换)。从隐藏实际创建的实现的角度来看,它确实是一种工厂模式方法。 - Lawrence
1
@Lawrence 这是否意味着您需要为每个要创建的窗口单独创建一个工厂(即 XWindowFactoryYWindowFactory)?还是您创建一个单一的工厂,公开像 CreateNewXWindowCreateNewYWindow 这样的方法? - Lennart
1
@Lennart:这取决于您如何设计应用程序。该模式仅允许您将“打开窗口”的实现与其调用方式分离。如果您通常希望从同一视图模型中打开XWindowYWindow,则在相同的工厂接口上公开执行此操作的方法可能是合适的。 - Lawrence
你能否将CreateNewWindow放在IMainView类中呢? - rollsch
IWindowFactory 创建的 Window 不应该通常需要 ViewModel 本身作为数据上下文而不是创建一个新的吗?我期望 CreateNewWindow 需要一个 Object,它将被应用到 Window 作为其 DataContext - Étienne Laneville

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接