WPF从ViewModel打开一个新视图

时间:2012-12-02 16:40:53

标签: c# wpf xaml mvvm

这是我的第一个WPF-MVVM应用程序,这是我的结构:

  1. app.xaml的一个项目打开应用程序并覆盖OnStartup以解析MainWindow。 (由于参考,我这样做了);
  2. 我的观点的一个项目;
  3. 我的ViewModels的一个项目;
  4. 我的模特的一个项目。
  5. 我有以下问题:我在MainWindowView,我点击按钮显示另一个视图。我应该如何从我的MainWindowViewModel打开另一个视图,而我的View Project引用了ViewModel Project,我无法用ViewModel Project引用View Project {1}}?

    顺便说一句,我正在Unity使用dependency injection

    那么,你能帮助我吗?

3 个答案:

答案 0 :(得分:9)

有几种方法。

您可以定义ViewModels项目中定义的对话框/导航/窗口服务界面。您需要决定ViewModel如何表达他们想要打开的窗口。我通常使用IDialogViewModel接口,我的一些ViewModel实现,并将ViewModel的实例传递给服务,但你可以使用枚举,字符串,无论你想要什么,所以你的实现可以映射到真正的窗口,这将是打开。

例如:

public interface IDialogService
{
    bool? ShowDialog(object dialogViewModel); 
}

想要打开新Windows的ViewModel将接收该服务的实例,并使用它来表达打开Window的意图。在Views项目中,您将定义一个实现服务接口的类型,并打开Window后面的真实逻辑。

以下示例:

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中创建和注入对话框服务。在该示例中,App将创建一个新的对话服务实例,将MainWindow传递给它。

类似的方法是使用某种形式的消息传递模式(link1 link2)。 此外,如果你想要一些简单的东西,你也可以让你的ViewModel在他们想要打开Windows并让视图订阅它们时引发事件。

修改

我在我的应用程序中使用的完整解决方案通常有点复杂,但基本上就是这个想法。我有一个基本的DialogWindow,它需要一个实现IDialogViewModel接口的ViewModel作为DataContext。此接口抽象出您在对话框中期望的一些功能,例如接受/取消命令以及关闭事件,因此您也可以从ViewModel关闭窗口。 DialogWindow基本上包含一个ContentPresenter,它将Content属性绑定到DataContext,并在更改DataContext时挂钩close事件(以及其他一些事情)。

每个“对话框”包含一个IDialogViewModel和一个关联的View(UserControl)。为了映射它们,我只是在App的资源中声明隐式DataTemplates。在我展示的代码中,唯一的区别是没有方法MapWindow,窗口实例总是一个DialogWindow。

我使用了一个额外的技巧来重用对话框之间的布局元素。方法是将它们包含在DialogWindow中(接受/取消按钮等)。我喜欢保持DialogWindow干净(所以我可以将它用于“非对话框”对话框)。我使用公共接口元素声明ContentControl的模板,当我声明View-ViewModel映射模板时,我使用ContentControl包装View并应用了我的“对话框模板”。然后,您可以根据需要为DialogWindow提供尽可能多的“主模板”(例如,像“向导之类”)。

答案 1 :(得分:3)

直接进场

如果我理解正确,应用程序启动时通过Unity解决了MainWindowView,这解决了它对MainWindowViewModel的依赖?

如果这是您正在使用的流程,我建议您继续使用相同的路径,让MainWindowView通过按钮的简单点击处理程序处理新视图的打开。在此处理程序中,您可以解析新视图,该视图将解析该视图的视图模型,然后您又回到MVVM域中以获取新视图。

这个解决方案很简单,对大多数小型应用程序来说都可以正常使用。

更复杂的应用程序的更重要的方法

如果您不想要那种视图优先流程,我建议您介绍一些协调视图和查看模型的控制器/演示器。演示者负责决定是否/何时实际打开/关闭视图等。

这是一个非常重的抽象,它更适合更复杂的应用程序,因此请确保从中获得足够的好处,以证明增加的抽象/复杂性。

以下是此方法的代码示例:

public partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        var container = new UnityContainer();

        container.RegisterType<IMainView, MainWindow>();
        container.RegisterType<ISecondView, SecondWindow>();
        container.RegisterType<IMainPresenter, MainPresenter>();
        container.RegisterType<ISecondPresenter, SecondPresenter>();

        var presenter = container.Resolve<IMainPresenter>();

        presenter.ShowView();
    }
}

public interface IMainPresenter
{
    void ShowView();
    void OpenSecondView();
}

public interface ISecondPresenter
{
    void ShowView();
}

public interface ISecondView
{
    void Show();
    SecondViewModel ViewModel { get; set; }
}

public interface IMainView
{
    void Show();
    MainViewModel ViewModel { get; set; }
}

public class MainPresenter : IMainPresenter
{
    private readonly IMainView _mainView;
    private readonly ISecondPresenter _secondPresenter;

    public MainPresenter(IMainView mainView, ISecondPresenter secondPresenter)
    {
        _mainView = mainView;
        _secondPresenter = secondPresenter;
    }

    public void ShowView()
    {
        // Could be resolved through Unity just as well
        _mainView.ViewModel = new MainViewModel(this);

        _mainView.Show();
    }

    public void OpenSecondView()
    {
        _secondPresenter.ShowView();
    }
}

public class SecondPresenter : ISecondPresenter
{
    private readonly ISecondView _secondView;

    public SecondPresenter(ISecondView secondView)
    {
        _secondView = secondView;
    }

    public void ShowView()
    {
        // Could be resolved through Unity just as well
        _secondView.ViewModel = new SecondViewModel();
        _secondView.Show();
    }
}

public class MainViewModel
{
    public MainViewModel(MainPresenter mainPresenter)
    {
        OpenSecondViewCommand = new DelegateCommand(mainPresenter.OpenSecondView);
    }

    public DelegateCommand OpenSecondViewCommand { get; set; }
}

public class SecondViewModel
{
}

<!-- MainWindow.xaml -->
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Command="{Binding OpenSecondViewCommand}" Content="Open second view" />
    </Grid>
</Window>

<!-- SecondWindow.xaml -->
<Window x:Class="WpfApplication1.SecondWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="SecondWindow" Height="350" Width="525">
    <Grid>
        <TextBlock>Second view</TextBlock>
    </Grid>
</Window>

This article提供了与之前在制作中使用的类似的解决方案。

答案 2 :(得分:0)

要从MainWindowView打开新窗口,您需要将Frame组件或整个窗口的引用传递给MainWindowViewModel对象(您可以在将命令绑定到转换按钮或其他内容时执行此操作,将它们作为对象传递,在那里你可以导航到新页面,但是,如果在转换时你需要在ViewModel中做任何特殊的事情,你可以使用经典的ButtonClick事件或w / e MainWindowView.cs中将为您执行导航,这对于基本过渡是可以的。

P.S。我不确定为什么你为ViewModels / Views / Models使用不同的项目。