Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cancel window closing in MVVM WPF application

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?

like image 830
Andrii Muzychuk Avatar asked Mar 21 '16 11:03

Andrii Muzychuk


2 Answers

I'm not an MVVM expert, but in my opinion Yusufs' answer isn't quite MVVM. On the other hand Torpederos answer is a bit complicated for only close cancellation. Here is my approach. In this example I subscribed to the closing event, but it is always cancelled

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

In the XAML I added this

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>

And finally in the view model

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

In this approach the the closing event is triggered first. That cancels the closing. After that the interaction trigger is invoked and triggers the code in the view model via the RelayCommand. In the view model I can use the Dirty flag that is not accessible in the view.

like image 38
DaanH Avatar answered Sep 19 '22 13:09

DaanH


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;
    }
}
like image 129
Yusuf Tarık Günaydın Avatar answered Sep 20 '22 13:09

Yusuf Tarık Günaydın