如何在 MVVM WPF 应用程序中取消 window 关闭

How to cancel window closing in MVVM WPF application

单击取消按钮(或右上角的 X,或 Esc)后如何取消退出特定表单?

WPF:

<Window
  ...
  x:Class="MyApp.MyView"
  ...
/>
  <Button Content="Cancel" Command="{Binding CancelCommand}" IsCancel="True"/>
</Window>

视图模型:

public class MyViewModel : Screen {
  private CancelCommand cancelCommand;
  public CancelCommand CancelCommand {
    get { return cancelCommand; }
  }
  public MyViewModel() {
    cancelCommand = new CancelCommand(this);
  }
}

public class CancelCommand : ICommand {

  public CancelCommand(MyViewModel viewModel) {
    this.viewModel = viewModel;
  }

  public override void Execute(object parameter) {
    if (true) { // here is a real condition
      MessageBoxResult messageBoxResult = System.Windows.MessageBox.Show(
        "Really close?",  "Warning", 
        System.Windows.MessageBoxButton.YesNo);
      if (messageBoxResult == MessageBoxResult.No) { return; }
    }
    viewModel.TryClose(false);
  }

  public override bool CanExecute(object parameter) {
    return true;
  }
}

当前代码无效。如果用户在弹出对话框中选择 'No',我希望用户留在当前表单上。 此外,覆盖 CanExecute 也无济于事。它只是禁用按钮。我想让用户点击按钮,但随后通知 him/her,数据将丢失。 也许我应该在按钮上分配一个事件侦听器?

编辑:

我成功地在“取消”按钮上显示了弹出窗口。但我仍然无法管理 Esc 或 X 按钮(右上角)。看来我和Cancel按钮搞混了,因为Execute方法是在我点击X按钮或者Esc的时候执行的。

编辑 2:

我改了问题。它是 'how cancel Cancel button'。但是,这不是我想要的。我需要取消 Esc 或 X 按钮。 在 'MyViewModel' 我添加:

        protected override void OnViewAttached(object view, object context) {
            base.OnViewAttached(view, context);
            (view as MyView).Closing += MyViewModel_Closing;
        }

        void MyViewModel_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
            if (true) {
                MessageBoxResult messageBoxResult = System.Windows.MessageBox.Show(
                  "Really close?",  "Warning", 
                  System.Windows.MessageBoxButton.YesNo);
                if (messageBoxResult == MessageBoxResult.No) {
                    e.Cancel = true;
                }
            }
        }

这解决了我的问题。但是,我需要 ICommand 了解单击了哪个按钮,保存或取消。有什么方法可以消除事件的使用吗?

您正在尝试在 ViewModel class 中执行 View 的工作。让您的视图 class 处理关闭请求以及是否应取消它。

要取消关闭 window,您可以订阅视图的 Closing 事件,并在显示 MessageBox 后将 CancelEventArgs.Cancel 设置为 true。

这是一个例子:

<Window
    ...
    x:Class="MyApp.MyView"
    Closing="OnClosing"
    ...
/>
</Window>

后面的代码:

private void OnClosing(object sender, CancelEventArgs e)
{
    var result = MessageBox.Show("Really close?",  "Warning", MessageBoxButton.YesNo);
    if (result != MessageBoxResult.Yes)
    {
        e.Cancel = true;
    }

    // OR, if triggering dialog via view-model:

    bool shouldClose = ((MyViewModel) DataContext).TryClose();
    if(!shouldClose)
    {
        e.Cancel = true;
    }
}

可以找到以视图模型方式执行此操作的非常好的示例 in the article of Nish Nishant,他在其中使用附加属性将 window 事件与命令挂钩。

附加行为示例代码(代码作者:Nish Nishant

public class WindowClosingBehavior {

    public static ICommand GetClosed(DependencyObject obj) {
        return (ICommand)obj.GetValue(ClosedProperty);
    }

    public static void SetClosed(DependencyObject obj, ICommand value) {
        obj.SetValue(ClosedProperty, value);
    }

    public static readonly DependencyProperty ClosedProperty 
        = DependencyProperty.RegisterAttached(
        "Closed", typeof(ICommand), typeof(WindowClosingBehavior),
        new UIPropertyMetadata(new PropertyChangedCallback(ClosedChanged)));

    private static void ClosedChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) {

        Window window = target as Window;

        if (window != null) {

            if (e.NewValue != null) {
                window.Closed += Window_Closed;
            }
            else {
                window.Closed -= Window_Closed;
            }
        }
    }

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

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

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

    private static void ClosingChanged(DependencyObject target, DependencyPropertyChangedEventArgs e) {

        Window window = target as Window;

        if (window != null) {

            if (e.NewValue != null) {
                window.Closing += Window_Closing;
            }
            else {
                window.Closing -= Window_Closing;
            }
        }
    }

    public static ICommand GetCancelClosing(DependencyObject obj) {
        return (ICommand)obj.GetValue(CancelClosingProperty);
    }

    public static void SetCancelClosing(DependencyObject obj, ICommand value) {
        obj.SetValue(CancelClosingProperty, value);
    }

    public static readonly DependencyProperty CancelClosingProperty 
        = DependencyProperty.RegisterAttached(
        "CancelClosing", typeof(ICommand), typeof(WindowClosingBehavior));

    static void Window_Closed(object sender, EventArgs e) {

        ICommand closed = GetClosed(sender as Window);

        if (closed != null) {
            closed.Execute(null);
        }
    }

    static void Window_Closing(object sender, CancelEventArgs e) {

        ICommand closing = GetClosing(sender as Window);

        if (closing != null) {

            if (closing.CanExecute(null)) {
                closing.Execute(null);
            }
            else {

                ICommand cancelClosing = GetCancelClosing(sender as Window);

                if (cancelClosing != null) {
                    cancelClosing.Execute(null);
                }

                e.Cancel = true;
            }
        }
    }
}   

如何绑定命令的示例:

<Window 
    x:Class="WindowClosingDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:nsmvvm="clr-namespace:NS.MVVM"
    nsmvvm:WindowClosingBehavior.Closed="{Binding ClosedCommand}"
    nsmvvm:WindowClosingBehavior.Closing="{Binding ClosingCommand}"
    nsmvvm:WindowClosingBehavior.CancelClosing="{Binding CancelClosingCommand}">

命令 "ClosedCommand"、"ClosingCommand" 和 "CancelClosingCommand" 应在单独的视图模型中定义。

internal class MainViewModel : ViewModelBase {

    private ObservableCollection<string> log = new ObservableCollection<string>();

    public ObservableCollection<string> Log {
        get { return log; }
    }

    private DelegateCommand exitCommand;

    public ICommand ExitCommand {

        get {

            if (exitCommand == null) {
                exitCommand = new DelegateCommand(Exit);
            }

            return exitCommand;
        }
    }

    private void Exit() {
        Application.Current.Shutdown();
    }

    private DelegateCommand closedCommand;

    public ICommand ClosedCommand {

        get {

            if (closedCommand == null) {
                closedCommand = new DelegateCommand(Closed);
            }

            return closedCommand;
        }
    }

    private void Closed() {
        log.Add("You won't see this of course! Closed command executed");
        MessageBox.Show("Closed");
    }

    private DelegateCommand closingCommand;

    public ICommand ClosingCommand {

        get {

            if (closingCommand == null) {
                closingCommand = new DelegateCommand(ExecuteClosing, CanExecuteClosing);
            }

            return closingCommand;
        }
    }

    private void ExecuteClosing() {
        log.Add("Closing command executed");
        MessageBox.Show("Closing");
    }

    private bool CanExecuteClosing() {

        log.Add("Closing command execution check");

        return MessageBox.Show("OK to close?", "Confirm", MessageBoxButton.YesNo) == MessageBoxResult.Yes;
    }

    private DelegateCommand cancelClosingCommand;

    public ICommand CancelClosingCommand {

        get {

            if (cancelClosingCommand == null) {
                cancelClosingCommand = new DelegateCommand(CancelClosing);
            }

            return cancelClosingCommand;
        }
    }

    private void CancelClosing() {
        log.Add("CancelClosing command executed");
        MessageBox.Show("CancelClosing");
    }
}

我不是 MVVM 专家,但在我看来 Yusufs 的回答并不完全是 MVVM。另一方面,Torpederos 的答案对于仅关闭关闭的取消有点复杂。这是我的方法。 在这个例子中我订阅了关闭事件,但它总是被取消

private void OnClosing(object sender, CancelEventArgs e)
{
    e.Cancel = true;
    return;
}

在 XAML 我添加了这个

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <i:InvokeCommandAction Command="{Binding Close}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

最后在视图模型中

public ICommand Close { get; set; }
Close = new RelayCommand(CommandClose);
private void CommandClose(object sender)
{
    if (Dirty)
    {
        // Save your data here
    }
    Environment.Exit(0);
}

在这种方法中,关闭事件首先被触发。这取消了关闭。之后调用交互触发器并通过 RelayCommand 触发视图模型中的代码。 在视图模型中,我可以使用在视图中无法访问的 Dirty 标志。

这是另一个直接从 ViewModel 取消关闭的例子window。

查看:

<Window x:Class="WpfApplicationMvvmLight.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Platform"
    Title="MainWindow" Height="350" Width="525">
<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <cmd:EventToCommand Command="{Binding Path=ClosingCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
    <TextBlock>content...</TextBlock>
</Grid>

ViewModel:

using GalaSoft.MvvmLight.CommandWpf;
using System.ComponentModel;
using System.Windows;

namespace WpfApplicationMvvmLight
{
  class SampleViewModel
  {
    public SampleViewModel() {
      _closingCommand = new RelayCommand<CancelEventArgs>(OnClosingCommand);
    }

    private RelayCommand<CancelEventArgs> _closingCommand;
    public RelayCommand<CancelEventArgs> ClosingCommand {
      get {
        return _closingCommand;
      }
    }

    private void OnClosingCommand(CancelEventArgs e) {
      //display your custom message box here..
      var result = MessageBox.Show("Do you want to close?", "", MessageBoxButton.YesNoCancel);
      //set e.Cancel to true to prevent the window from closing
      e.Cancel = result != MessageBoxResult.Yes;
    }
  }
}

后面的代码:

using System.Windows;

namespace WpfApplicationMvvmLight
{
  public partial class MainWindow : Window
  {
    public MainWindow() {
      InitializeComponent();
      this.DataContext = new SampleViewModel();
    }
  }
}

这是参考。 MVVM close window event