4

I know this question has been asked many times but I will try to be specific as possible.

I am a beginner in WPF / MVVM and using MVVM Light Toolkit by Galasoft in my project.

I have a view containing a form where the user inputs some patient details. When they click the close (the X) button, I want to check if they have entered something and if so ask them if they want to save before closing with (Yes, No and Cancel) options. I did some research and found that many are suggesting the EventToCommand feature like so,

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>

View Model

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

The above example is taken from Confirmation when closing window with 'X' button with MVVM light

However, the creator of the MVVM Light Toolkit himself is saying this breaks the separation of concern that the MVVM pattern is trying to achieve since it is passing the event arguments belonging to the view (in this case the CancelEventArgs) to the view model. He said so in this article 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/

So my question is, What is the proper way to handle such problem which doesn't break the MVVM pattern. Any point in the right direction would be greatly appreciated!

0

1 Answer 1

5

I'm not pretending to absolute truth, but I like following approach.
Base view model has a RelayCommand/DelegateCommand like this:

public ICommand ClosingCommand { get; }

where ICommand.Execute is implemented as:

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

and ICommand.CanExecute as:

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

In turn, UI uses attached behavior to handle 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 (assumes, that view model is a DataContext for window):

behaviors:WindowClosingBehavior.Closing="{Binding ClosingCommand}"
Sign up to request clarification or add additional context in comments.

2 Comments

@JvdBerg: oh, sorry. GetClosing/SetClosing here are just regular attached property value wrappers. Added missed methods.
Woowie, this is the first answer that actually works for MVVM without any other frameworks, and it also does it in a really clean way, thanks a lot! Also it would be nice to add the code for assigning the methods to the command to make this a 100% complete answer: ClosingCommand = new RelayCommand(Closing, CanClose);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.