MVVM中的Window.Closing事件处理程序

时间:2015-08-27 15:21:22

标签: c# wpf mvvm

以下问题基于此帖中的评论:MVVM Understanding Issues

我说这是代码隐藏,不违反视图和视图模型的关注点分离:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Closing += MainWindow_Closing;
    }

    void MainWindow_Closing(object sender, CancelEventArgs e)
    {
        var canExit = ViewModel.ShowConfirmExitDlg();
        if (!canExit) e.Cancel = true;
    }
}

评论是:

  

代码隐藏中的任何东西都不能进行单元测试,并且可以调用   创建一个对话框是逻辑,因此不应该在   图

我有两个问题:

  1. 这是否会破坏MVVM的分离?
  2. 你会怎样做(更好)
  3.  

    我可以使用一些EventTriggers和CallMethod动作从xaml调用viewmodel方法,但它没有任何区别。

    我可以使用事件聚合器:

    public partial class MainWindow : Window
    {
        private readonly IEventAggregator _eventAggregator;
    
        public MainWindow(IEventAggregator eventAggregator)
        {
            _eventAggregator = eventAggregator;
            InitializeComponent();
    
            Closing += MainWindow_Closing;
        }
    
        void MainWindow_Closing(object sender, CancelEventArgs e)
        {
            var evt = new MainWindowClosingEvent();
            _eventAggregator.Publish(evt);
            e.Cancel = evt.IsCancel;
        }
    }
    

    并在viewmodel中处理事件但是它带来了什么值?我仍然不能单元测试取消Windows关闭事件,但我已经介绍了发布和订阅,这也值得unittestig。它是另一层间接

    也许我可以将事件路由到viewmodel:

    public MainWindow()
    {
       InitializeComponent();
       Closing += ViewModel.OnWindowClosing;
       //or
       Closing += (o, e) => ViewModel.OnWindowClosing(e);
    }
    

    但我没有看到与原始样本有太大差异。

    恕我直言,view和viewmodel之间的连接无法在viewmodel测试中进行单元测试,所以我要么找到一种方法来测试视图,要么就是疯狂追逐。

2 个答案:

答案 0 :(得分:4)

正如我所看到的,这里有两个问题。首先,您可以通过使用交互命名空间和命令来消除一些代码隐藏,以供参考:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

<i:Interaction.Triggers>
  <i:EventTrigger EventName="Closing">
       ICommand goes here - bind to your VM
  </i:EventTrigger>
</i:Interaction.Triggers>

在显示对话框时,您需要考虑对话框是视图还是视图模型。当谈到确认窗户关闭时,我认为这是纯粹的观点。因此,您可以在Closing事件的代码隐藏中显示,而无需IMHO打破MVVM。

答案 1 :(得分:2)

关于第一个问题,我是发表评论的人,所以很明显我的回答是“是”:)

至于第二种,交互触发器是我自己通常实现的方式(尽管在情况决定时我也使用了附加行为):

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <cmd:EventToCommand Command="{Binding ClosingCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
    <i:EventTrigger EventName="Closed">
        <cmd:EventToCommand Command="{Binding ClosedCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

Closing处理程序通过依赖注入框架调用对话框的创建,而Close处理程序使主视图模型自毁:

public ICommand ClosingCommand { get { return new RelayCommand<CancelEventArgs>(OnClosing); } }
private void OnClosing(CancelEventArgs args)
{
    #if !DEBUG
    var locman = Injector.Get<LocalizationManager>();
    var dlg = Injector.Get<CustomDialogViewModel>();
    dlg.Caption = locman[LogOffCaption];
    dlg.Message = locman[LogOffPrompt];
    dlg.OnCancel = (sender) =>
    {
        args.Cancel = true;
        sender.Close();
    };
    dlg.Show();
    #endif
}

public ICommand ClosedCommand { get { return new RelayCommand(OnClosed); } }
private void OnClosed()
{
    this.Dispose();
}

这是一个非常简单的示例,但很明显,通过注入本地化管理器和对话框视图模型的模拟实例,然后直接从测试框架调用命令处理程序,可以很容易地测试此代码。

值得指出的是,在所有情况下打破纯MVVM并不一定是坏事。 Josh Smith在撰写他关于MVVM的原创文章时似乎非常支持没有代码隐藏,但到了“高级MVVM”时,他似乎采取了更为温和的立场,并指出“实用的开发人员采取中间道路并使用良好的判断确定哪些代码属于“。在将WPF集成到全栈体系结构的七八年中,我个人从未遇到过纯MVVM无法干净优雅地实现问题的情况,尽管在某些情况下会增加复杂性。你自己的里程会有所不同。