【问题标题】:Window.Closing event handler in MVVMMVVM 中的 Window.Closing 事件处理程序
【发布时间】:2015-08-27 15:21:22
【问题描述】:

以下问题基于此帖子中的评论: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;
    }
}

cmets 是:

代码隐藏中的任何内容都不能进行单元测试,并且调用 对话框的创建是合乎逻辑的,因此不应该在 查看

我有两个问题:

  1. 这会破坏 MVVM 的 conern 分离吗?
  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;
    }
}

并在视图模型中处理事件,但它会带来任何价值吗?我仍然无法对取消窗口关闭事件进行单元测试,但我已经介绍了发布和订阅,这也值得单元测试。这是另一层间接性

也许我可以将事件路由到视图模型:

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

但我看不出与原始样本有太大区别。

恕我直言,视图和视图模型之间的连接无法在视图模型测试中进行单元测试,所以我要么找到一种方法来测试视图,要么就是胡闹。

【问题讨论】:

  • 您是想从您的视图模型中关闭窗口,还是只是将您的窗口正在关闭的视图模型传递给您?
  • 通常可以通过点击X按钮,或者按alt+f4等来关闭窗口

标签: c# wpf mvvm


【解决方案1】:

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

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 事件的代码隐藏中展示这一点,恕我直言,不会破坏 MVVM。

【讨论】:

  • 感谢您的意见。实际上,我没有在代码隐藏中创建对话框,我只是在调用 viewmodel。 Viewmodel 调用 DialogService,可以在测试中模拟。当谈到 EventTriggers 时,我在我的问题中提到了它们作为一种可能性
  • 没问题。我认为在这种情况下,对于关闭确认对话框,在视图中创建它会很好。在虚拟机中创建它没有任何理由/优势,在我看来,尝试测试关闭确认对话框似乎过分了。所以,你问了 2 个问题——它是否破坏了 MVVM,以及我该如何做得更好。好吧,我认为以上内容涵盖了这两点。 :)
【解决方案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 无法干净优雅地实现问题的情况,尽管在某些情况下以增加复杂性为代价。您自己的里程会有所不同。

【讨论】:

  • 谢谢马克,您的解释。你有什么特别的原因,为什么你更喜欢EventToCommand而不是CallMethodAction?对我来说,Command 唤起了应该发生的事情,事件处理程序表明事情已经发生了
  • 我可能是错的,但我不认为 CallMethodAction 允许您传递事件参数,在Closing 的情况下用户取消需要?虽然这可能不是最好的例子,但它对于一般的 GUI 命令肯定更有意义,因为它允许您轻松地将命令绑定指向其他地方,而使用 CallMethodAction 您必须在处理程序本身中实现该逻辑(当然很容易做到,但这是另一个不必要的层)。最后是 CanExecute 的问题;同样,您可以通过其他方式执行此操作,但 ICommand 支持开箱即用。
  • 仅供参考:CallMethodAction:其签名与事件处理程序的签名匹配的公共方法。
  • 如何使用 CanExecute 和 Closing 事件?在这种情况下,如果ClosingCommand.CanExecute 返回 false,它将禁用 MessageBox,但无论如何窗口都会关闭。这很令人困惑。直观地说:Closing cannot execute 表示关闭不应该继续。无论如何,如果没有其他人回答,我会将其标记为答案
  • 啊!好的,我对传递的论点当然是错误的,谢谢澄清。你对 CanExecute 的看法是正确的,这就是为什么我说 Closing 可能不是一个很好的例子。
猜你喜欢
  • 1970-01-01
  • 2011-06-28
  • 2016-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-19
  • 2010-10-16
  • 1970-01-01
相关资源
最近更新 更多