使用MVVM在WPF中创建新窗口的最佳方法

时间:2010-01-21 11:55:19

标签: c# .net wpf mvvm

在邻居帖子中:How should the ViewModel close the form? 我发布了如何使用MVVM关闭Windows的愿景。现在我有一个问题:如何打开它们。

我有一个主窗口(主视图)。如果用户单击“显示”按钮,则应显示“演示”窗口(模态对话框)。使用MVVM模式创建和打开窗口的首选方法是什么?我看到两种一般方法:

第一个(可能是最简单的)。事件处理程序“ShowButton_Click”应该在主窗口的代码中实现,如下所示:

        private void ModifyButton_Click(object sender, RoutedEventArgs e)
        {
            ShowWindow wnd = new ShowWindow(anyKindOfData);
            bool? res = wnd.ShowDialog();
            if (res != null && res.Value)
            {
                //  ... store changes if neecssary
            }
        }
  1. 如果我们“显示”按钮状态应该更改(启用/禁用),我们将需要添加将管理按钮状态的逻辑;
  2. 源代码与“旧式”WinForms和MFC源非常相似 - 我不确定这是好还是坏,请指教。
  3. 我错过了什么?
  4. 另一种方法:

    在MainWindowViewModel中,我们将实现“ShowCommand”属性,该属性将返回命令的ICommand接口。 Comman反过来:

    • 将引发“ShowDialogEvent”;
    • 将管理按钮状态。

    这种方法更适合MVVM但需要额外的编码:ViewModel类不能“显示对话框”,所以MainWindowViewModel只会引发“ShowDialogEvent”,MainWindowView我们需要在其MainWindow_Loaded方法中添加事件处理程序,像这样的东西:

    ((MainWindowViewModel)DataContext).ShowDialogEvent += ShowDialog;
    

    (ShowDialog - 类似于'ModifyButton_Click'方法。)

    所以我的问题是:  你看到其他方法了吗?  你认为其中一个是好还是坏? (为什么?)

    欢迎任何其他想法。

    感谢。

6 个答案:

答案 0 :(得分:17)

某些MVVM框架(例如MVVM Light)使用Mediator pattern。 因此,要打开一个新窗口(或创建任何视图),某些特定于视图的代码将订阅来自中介的消息,而ViewModel将发送这些消息。

像这样:

Subsription

Messenger.Default.Register<DialogMessage>(this, ProcessDialogMessage);
...
private void ProcessDialogMessage(DialogMessage message)
{
     // Instantiate new view depending on the message details
}

在ViewModel中

Messenger.Default.Send(new DialogMessage(...));

我更喜欢在单例类中进行订阅,只要应用程序的UI部分可以“生存”。 总结一下:ViewModel传递“我需要创建视图”之类的消息,UI会监听这些消息并对其进行操作。

当然,没有“理想”的方法。

答案 1 :(得分:16)

我最近也在考虑这个问题。如果你在项目中使用Unity作为'容器'或依赖注入的任何东西,我有一个想法。我想通常你会覆盖App.OnStartup()并创建你的模型,视图模型,并在那里查看,并给每个人提供适当的参考。使用Unity,您可以为容器提供对模型的引用,然后使用容器“解析”视图。 Unity容器会注入您的视图模型,因此您永远不会直接实例化它。解决后,您可以在其上调用Show()

在我观看的示例视频中,Unity容器在OnStartup中创建为本地变量。如果您在App类中将其创建为公共静态只读属性,该怎么办?然后,您可以在主视图模型中使用它来创建新窗口,自动注入新视图所需的任何资源。类似于App.Container.Resolve<MyChildView>().ShowDialog();

我想你可以在测试中以某种方式模拟调用Unity容器的结果。或者,也许您可​​以在App类中编写类似ShowMyChildView()的方法,这基本上就是我上面所描述的。模拟对App.ShowMyChildView()的调用可能很容易,因为它只返回bool?,是吗?

嗯,这可能不仅仅比使用new MyChildView()更好,但这是我的一个小想法。我以为我会分享它。 =)

答案 2 :(得分:4)

我有点晚了,但我觉得现有的答案不够。我会解释原因。一般来说:

  • 可以从View访问ViewModels,
  • 从ViewModels 访问视图是错误的,因为它引入了循环依赖并使ViewModel难以测试。

Benny Jobigan的anwer:

App.Container.Resolve<MyChildView>().ShowDialog();

这实际上并没有解决任何问题。您正在以一种紧密耦合的方式从ViewModel访问您的View。与new MyChildView().ShowDialog()的唯一区别在于您通过了一层间接。我没有看到直接调用MyChildView ctor的任何优势。

如果您为视图使用了接口,那将会更清晰:

App.Container.Resolve<IMyChildView>().ShowDialog();`

现在ViewModel没有与视图紧密耦合。但是我发现为每个视图创建界面是非常不切实际的。

arconaut的anwer:

Messenger.Default.Send(new DialogMessage(...));

它更好。似乎Messenger或EventAggregator或其他发布/订阅模式是MVVM中每个人的通用解决方案:)缺点是调试或导航到DialogMessageHandler更难。这太间接了。例如,您如何从Dialog中读取输出?通过修改DialogMessage?

我的解决方案:

你可以像这样从MainWindowViewModel打开窗口:

var childWindowViewModel = new MyChildWindowViewModel(); //you can set parameters here if necessary
var dialogResult = DialogService.ShowModal(childWindowViewModel);
if (dialogResult == true) {
   //you can read user input from childWindowViewModel
}

DialogService只使用对话框的ViewModel,因此您的视图模型完全独立于视图。在运行时,DialogService可以找到适当的视图(例如使用命名约定)并显示它,或者可以在单元测试中轻松模拟它。

在我的情况下,我使用这个接口:

interface IDialogService
{
   void Show(IDialogViewModel dialog);
   void Close(IDialogViewModel dialog); 
   bool? ShowModal(IDialogViewModel dialog);
   MessageBoxResult ShowMessageBox(string message, string caption = null, MessageBoxImage icon = MessageBoxImage.No...);
}

interface IDialogViewModel 
{
    string Caption {get;}
    IEnumerable<DialogButton> Buttons {get;}
}

其中DialogBu​​tton指定DialogResult或ICommand或两者。

答案 3 :(得分:2)

查看我当前的MVVM解决方案,以便在Silverlight中显示模态对话框。 它解决了您提到的大部分问题,但它完全从平台特定事物中抽象出来,可以重复使用。此外,我没有使用代码隐藏仅与实现ICommand的DelegateCommands绑定。 Dialog基本上是一个View - 一个单独的控件,它有自己的ViewModel,它从主屏幕的ViewModel显示,但是通过DelagateCommand绑定从UI触发。

在此处查看完整的Silverlight 4解决方案Modal dialogs with MVVM and Silverlight 4

答案 4 :(得分:1)

我使用一个控制器来处理视图之间传递的所有信息。所有视图模型都使用控制器中的方法来请求更多信息,这些信息可以作为对话框,其他视图等实现。

它看起来像这样:

class MainViewModel {
    public MainViewModel(IView view, IModel model, IController controller) {
       mModel = model;
       mController = controller;
       mView = view;
       view.DataContext = this;
    }

    public ICommand ShowCommand = new DelegateCommand(o=> {
                  mResult = controller.GetSomeData(mSomeData);
                                                      });
}

class Controller : IController {
    public void OpenMainView() {
        IView view = new MainView();
        new MainViewModel(view, somemodel, this);
    }

    public int GetSomeData(object anyKindOfData) {
      ShowWindow wnd = new ShowWindow(anyKindOfData);
      bool? res = wnd.ShowDialog();
      ...
    }
}

答案 5 :(得分:0)

我的方法类似于adrianm。但是,在我的情况下,Controller永远不会使用具体的View类型。 Controller与View完全分离 - 与ViewModel完全相同。

如何在WPF Application Framework (WAF)的ViewModel示例中看到它的工作原理。

最诚挚的问候,

JBE