How to cancel window closing in MVVM WPF application

Andrii Muzychuk picture Andrii Muzychuk · Mar 21, 2016 · Viewed 22.7k times · Source

How can I cancel exiting from particular form after Cancel button (or X at the top right corner, or Esc) was clicked?

WPF:

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

ViewModel:

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

Current code doesn't work. I want user to stay on current form if it chooses 'No' in popup dialog. Also, overriding CanExecute doesn't help. It just disables the button. I want to allow user to hit the button, but then notify him/her, that data will be lost. Maybe I should assign an event listener on button?

EDIT:

I managed showing popup on Cancel button. But I still can't manage Esc or X button (top right). It seems I was confused with Cancel button, because Execute method is executed when I click X button or Esc.

EDIT2:

I changed the question. It was 'how cancel Cancel button'. However, it wasn't what I was looking for. I need to cancel Esc or X button. In 'MyViewModel' I add:

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

This solved my problem. However, I need ICommand to understand, which button was clicked, Save or Cancel. Is there any way to eliminate usage of event?

Answer

Yusuf Tarık G&#252;naydın picture Yusuf Tarık Günaydın · Mar 21, 2016

You are trying to do View's work in ViewModel class. Let your View class to handle the closing request and whether it should be canceled or not.

To cancel closing of a window you can subscribe to the Closing event of view and set CancelEventArgs.Cancel to true after showing a MessageBox.

Here is an example:

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

Code behind:

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