如何在WPF MVVM中正确处理窗口关闭事件

4
我知道这个问题已经被问了很多次,但我会尽可能详细地说明。
我是WPF/MVVM的初学者,正在我的项目中使用Galasoft的MVVM Light Toolkit。
我有一个视图包含一个表单,用户输入一些患者信息。当他们点击关闭(X)按钮时,我想检查他们是否输入了内容,如果输入了内容,则询问他们在关闭前是否要保存(有、无和取消选项)。我做了一些研究,发现许多人建议使用EventToCommand功能,如下所示,
XAML
<Window
   xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
   xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF45"
   DataContext="{Binding Main, Source={StaticResource Locator}}">
   <i:Interaction.Triggers>
      <i:EventTrigger EventName="Closing">
         <cmd:EventToCommand Command="{Binding OnClosingCommand}" 
            PassEventArgsToCommand="True"/>
      </i:EventTrigger>
   </i:Interaction.Triggers>
...
</Window>

视图模型

public class MainViewModel : ViewModelBase
{
   public RelayCommand<CancelEventArgs> OnClosingCommand { get; set; }

   public MainViewModel()
   {
      this.OnClosingCommand = 
         new RelayCommand<CancelEventArgs>(this.OnClosingCommandExecuted);
   }

   private void OnClosingCommandExecuted(CancelEventArgs cancelEventArgs)
   {
      // logic to check if view model has updated since it is loaded
      if (mustCancelClosing)
      {
         cancelEventArgs.Cancel = true;
      } 
   }
}

上面的例子取自使用MVVM light关闭窗口时的确认
然而,MVVM Light Toolkit的创建者本人表示,这会破坏MVVM模式试图实现的关注点分离,因为它将属于视图(在这种情况下是CancelEventArgs)的事件参数传递给视图模型。他在这篇文章中提到了这一点http://blog.galasoft.ch/posts/2014/01/using-the-eventargsconverter-in-mvvm-light-and-why-is-there-no-eventtocommand-in-the-windows-8-1-version/
所以我的问题是,有什么正确的方法来处理这样的问题,而不会破坏MVVM模式。任何指向正确方向的观点都将不胜感激!
1个回答

5

我并不打算宣称自己掌握了绝对真理,但我喜欢以下方法。
基本视图模型包含一个像这样的RelayCommand/DelegateCommand:

public ICommand ClosingCommand { get; }

ICommand.Execute 实现如下:

/// <summary>
/// Executes an action, when user closes a window, displaying this instance, using system menu.
/// </summary>
protected virtual void Closing()
{
}

ICommand.CanExecute 则是:

/// <summary>
/// Detects whether user can close a window, displaying this instance, using system menu.
/// </summary>
/// <returns>
/// <see langword="true"/>, if window can be closed;
/// otherwise <see langword="false"/>.
/// </returns>
protected virtual bool CanClose()
{
    return true;
}

相应地,UI 使用附加行为来处理 Window.Closing 事件:

public static class WindowClosingBehavior
{
    public static readonly DependencyProperty ClosingProperty = DependencyProperty.RegisterAttached(
            "Closing", 
            typeof(ICommand), 
            typeof(WindowClosingBehavior),
            new UIPropertyMetadata(new PropertyChangedCallback(ClosingChanged)));

    public static ICommand GetClosing(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(ClosingProperty);
    }

    public static void SetClosing(DependencyObject obj, ICommand value)
    {
        obj.SetValue(ClosingProperty, value);
    }

    private static void ClosingChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        var window = target as Window;
        if (window != null)
        {
            if (e.NewValue != null)
                window.Closing += Window_Closing;
            else
                window.Closing -= Window_Closing;
        }
    }

    private static void Window_Closing(object sender, CancelEventArgs e)
    {
        var window = sender as Window;
        if (window != null)
        {
            var closing = GetClosing(window);
            if (closing != null)
            {
                if (closing.CanExecute(null))
                    closing.Execute(null);
                else
                    e.Cancel = true;
            }
        }
    }
}

XAML(假设视图模型是窗口的DataContext):

behaviors:WindowClosingBehavior.Closing="{Binding ClosingCommand}"

@JvdBerg:哦,抱歉。这里的GetClosing/SetClosing只是常规的附加属性值包装器。已添加缺失的方法。 - Dennis
哇喔,这是第一个真正适用于MVVM且没有其他框架的答案,而且它以一种非常清晰的方式实现,非常感谢! 另外,如果能添加将方法分配给命令的代码,使其成为一个100%完整的答案,那就太好了: ClosingCommand = new RelayCommand(Closing, CanClose); - mooshmore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接