窗口.MVVM中的关闭事件处理程序

本文关键字:事件处理 程序 MVVM 窗口 | 更新日期: 2023-09-27 18:10:09

以下问题是基于这篇文章的评论:MVVM理解问题

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

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. 你会怎么做(更好)?

我可以使用一些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:

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

但是我看不出和原样品有什么不同。

IMHO,视图和视图模型之间的连接不能在视图模型测试中进行单元测试,所以我要么找到一种方法来测试视图,要么是徒劳的。

窗口.MVVM中的关闭事件处理程序

我认为这里有两个问题。首先,您可以通过使用交互性名称空间和命令来消除一些代码隐藏,有关参考,请参阅:

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>

在显示对话框时,需要考虑对话框是视图还是视图模型。当谈到确认窗口关闭时,我认为这是纯粹的观点。因此,您可以在关闭事件的代码后面显示它,而不会破坏MVVM。

关于第一个问题,我是发表评论的人,所以显然我的回答是"是":)

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

<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>

Close处理程序通过依赖注入框架调用一个对话框的创建,而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不能干净优雅地实现问题的情况,尽管不可否认在某些情况下会增加复杂性。