Handling the window closing event with WPF / MVVM Light Toolkit

C#WpfXamlMvvmMvvm Light

C# Problem Overview


I'd like to handle the Closing event (when a user clicks the upper right 'X' button) of my window in order to eventually display a confirm message or/and cancel the closing.

I know how to do this in the code-behind: subscribe to the Closing event of the window then use the CancelEventArgs.Cancel property.

But I'm using MVVM so I'm not sure it's the good approach.

I think the good approach would be to bind the Closing event to a Command in my ViewModel.

I tried that:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <cmd:EventToCommand Command="{Binding CloseCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>

With an associated RelayCommand in my ViewModel but it doesn't work (the command's code is not executed).

C# Solutions


Solution 1 - C#

I would simply associate the handler in the View constructor:

MyWindow() 
{
    // Set up ViewModel, assign to DataContext etc.
    Closing += viewModel.OnWindowClosing;
}

Then add the handler to the ViewModel:

using System.ComponentModel;

public void OnWindowClosing(object sender, CancelEventArgs e) 
{
   // Handle closing logic, set e.Cancel as needed
}

In this case, you gain exactly nothing except complexity by using a more elaborate pattern with more indirection (5 extra lines of XAML plus Command pattern).

The "zero code-behind" mantra is not the goal in itself, the point is to decouple ViewModel from the View. Even when the event is bound in code-behind of the View, the ViewModel does not depend on the View and the closing logic can be unit-tested.

Solution 2 - C#

This code works just fine:

ViewModel.cs:

public ICommand WindowClosing
{
    get
    {
        return new RelayCommand<CancelEventArgs>(
            (args) =>{
                     });
    }
}

and in XAML:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <command:EventToCommand Command="{Binding WindowClosing}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
</i:Interaction.Triggers>

assuming that:

  • ViewModel is assigned to a DataContext of the main container.
  • xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL5"
  • xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

Solution 3 - C#

This option is even easier, and maybe is suitable for you. In your View Model constructor, you can subscribe the Main Window closing event like this:

Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing);

void MainWindow_Closing(object sender, CancelEventArgs e)
{
            //Your code to handle the event
}

All the best.

Solution 4 - C#

Here is an answer according to the MVVM-pattern if you don't want to know about the Window (or any of its event) in the ViewModel.

public interface IClosing
{
    /// <summary>
    /// Executes when window is closing
    /// </summary>
    /// <returns>Whether the windows should be closed by the caller</returns>
    bool OnClosing();
}

In the ViewModel add the interface and implementation

public bool OnClosing()
{
    bool close = true;

    //Ask whether to save changes och cancel etc
    //close = false; //If you want to cancel close

    return close;
}

In the Window I add the Closing event. This code behind doesn't break the MVVM pattern. The View can know about the viewmodel!

void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    IClosing context = DataContext as IClosing;
    if (context != null)
    {
        e.Cancel = !context.OnClosing();
    }
}

Solution 5 - C#

Geez, seems like a lot of code going on here for this. Stas above had the right approach for minimal effort. Here is my adaptation (using MVVMLight but should be recognizable)... Oh and the PassEventArgsToCommand="True" is definitely needed as indicated above.

(credit to Laurent Bugnion http://blog.galasoft.ch/archive/2009/10/18/clean-shutdown-in-silverlight-and-wpf-applications.aspx)

   ... MainWindow Xaml
   ...
   WindowStyle="ThreeDBorderWindow" 
    WindowStartupLocation="Manual">

 

<i:Interaction.Triggers>
    <i:EventTrigger EventName="Closing">
        <cmd:EventToCommand Command="{Binding WindowClosingCommand}" PassEventArgsToCommand="True" />
    </i:EventTrigger>
</i:Interaction.Triggers> 

In the view model:

///<summary>
///  public RelayCommand<CancelEventArgs> WindowClosingCommand
///</summary>
public RelayCommand<CancelEventArgs> WindowClosingCommand { get; private set; }
 ...
 ...
 ...
        // Window Closing
        WindowClosingCommand = new RelayCommand<CancelEventArgs>((args) =>
                                                                      {
                                                                          ShutdownService.MainWindowClosing(args);
                                                                      },
                                                                      (args) => CanShutdown);

in the ShutdownService

    /// <summary>
    ///   ask the application to shutdown
    /// </summary>
    public static void MainWindowClosing(CancelEventArgs e)
    {
        e.Cancel = true;  /// CANCEL THE CLOSE - let the shutdown service decide what to do with the shutdown request
        RequestShutdown();
    }

RequestShutdown looks something like the following but basicallyRequestShutdown or whatever it is named decides whether to shutdown the application or not (which will merrily close the window anyway):

...
...
...
    /// <summary>
    ///   ask the application to shutdown
    /// </summary>
    public static void RequestShutdown()
    {
       
        // Unless one of the listeners aborted the shutdown, we proceed.  If they abort the shutdown, they are responsible for restarting it too.

        var shouldAbortShutdown = false;
        Logger.InfoFormat("Application starting shutdown at {0}...", DateTime.Now);
        var msg = new NotificationMessageAction<bool>(
            Notifications.ConfirmShutdown,
            shouldAbort => shouldAbortShutdown |= shouldAbort);

        // recipients should answer either true or false with msg.execute(true) etc.

        Messenger.Default.Send(msg, Notifications.ConfirmShutdown);

        if (!shouldAbortShutdown)
        {
            // This time it is for real
            Messenger.Default.Send(new NotificationMessage(Notifications.NotifyShutdown),
                                   Notifications.NotifyShutdown);
            Logger.InfoFormat("Application has shutdown at {0}", DateTime.Now);
            Application.Current.Shutdown();
        }
        else
            Logger.InfoFormat("Application shutdown aborted at {0}", DateTime.Now);
    }
    }

Solution 6 - C#

The asker should use STAS answer, but for readers who use prism and no galasoft/mvvmlight, they may want to try what I used:

In the definition at the top for window or usercontrol, etc define namespace:

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

And just below that definition:

<i:Interaction.Triggers>
        <i:EventTrigger EventName="Closing">
            <i:InvokeCommandAction Command="{Binding WindowClosing}" CommandParameter="{Binding}" />
        </i:EventTrigger>
</i:Interaction.Triggers>

Property in your viewmodel:

public ICommand WindowClosing { get; private set; }

Attach delegatecommand in your viewmodel constructor:

this.WindowClosing = new DelegateCommand<object>(this.OnWindowClosing);

Finally, your code you want to reach on close of the control/window/whatever:

private void OnWindowClosing(object obj)
        {
            //put code here
        }

Solution 7 - C#

I would be tempted to use an event handler within your App.xaml.cs file that will allow you to decide on whether to close the application or not.

For example you could then have something like the following code in your App.xaml.cs file:

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    // Create the ViewModel to attach the window to
    MainWindow window = new MainWindow();
    var viewModel = new MainWindowViewModel();

    // Create the handler that will allow the window to close when the viewModel asks.
    EventHandler handler = null;
    handler = delegate
    {
        //***Code here to decide on closing the application****
        //***returns resultClose which is true if we want to close***
        if(resultClose == true)
        {
            viewModel.RequestClose -= handler;
            window.Close();
        }
    }
    viewModel.RequestClose += handler;

    window.DataContaxt = viewModel;

    window.Show();

}

Then within your MainWindowViewModel code you could have the following:

#region Fields
RelayCommand closeCommand;
#endregion

#region CloseCommand
/// <summary>
/// Returns the command that, when invoked, attempts
/// to remove this workspace from the user interface.
/// </summary>
public ICommand CloseCommand
{
    get
    {
        if (closeCommand == null)
            closeCommand = new RelayCommand(param => this.OnRequestClose());

        return closeCommand;
    }
}
#endregion // CloseCommand

#region RequestClose [event]

/// <summary>
/// Raised when this workspace should be removed from the UI.
/// </summary>
public event EventHandler RequestClose;

/// <summary>
/// If requested to close and a RequestClose delegate has been set then call it.
/// </summary>
void OnRequestClose()
{
    EventHandler handler = this.RequestClose;
    if (handler != null)
    {
        handler(this, EventArgs.Empty);
    }
}

#endregion // RequestClose [event]

Solution 8 - C#

Basically, window event may not be assigned to MVVM. In general, the Close button show a Dialog box to ask the user "save : yes/no/cancel", and this may not be achieved by the MVVM.

You may keep the OnClosing event handler, where you call the Model.Close.CanExecute() and set the boolean result in the event property. So after the CanExecute() call if true, OR in the OnClosed event, call the Model.Close.Execute()

Solution 9 - C#

I haven't done much testing with this but it seems to work. Here's what I came up with:

namespace OrtzIRC.WPF
{
    using System;
    using System.Windows;
    using OrtzIRC.WPF.ViewModels;

    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private MainViewModel viewModel = new MainViewModel();
        private MainWindow window = new MainWindow();

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            viewModel.RequestClose += ViewModelRequestClose;

            window.DataContext = viewModel;
            window.Closing += Window_Closing;
            window.Show();
        }

        private void ViewModelRequestClose(object sender, EventArgs e)
        {
            viewModel.RequestClose -= ViewModelRequestClose;
            window.Close();
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            window.Closing -= Window_Closing;
            viewModel.RequestClose -= ViewModelRequestClose; //Otherwise Close gets called again
            viewModel.CloseCommand.Execute(null);
        }
    }
}

Solution 10 - C#

We use AttachedCommandBehavior for this. You can attach any event to a command on your view model avoiding any code behind.

We use it throughout our solution and have almost zero code behind

http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/

Solution 11 - C#

Using MVVM Light Toolkit:

Assuming that there is an Exit command in view model:

ICommand _exitCommand;
public ICommand ExitCommand
{
    get
    {
        if (_exitCommand == null)
            _exitCommand = new RelayCommand<object>(call => OnExit());
        return _exitCommand;
    }
}

void OnExit()
{
     var msg = new NotificationMessageAction<object>(this, "ExitApplication", (o) =>{});
     Messenger.Default.Send(msg);
}

This is received in the view:

Messenger.Default.Register<NotificationMessageAction<object>>(this, (m) => if (m.Notification == "ExitApplication")
{
     Application.Current.Shutdown();
});

On the other hand, I handle Closing event in MainWindow, using the instance of ViewModel:

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{ 
    if (((ViewModel.MainViewModel)DataContext).CancelBeforeClose())
        e.Cancel = true;
}

CancelBeforeClose checks the current state of view model and returns true if closing should be stopped.

Hope it helps someone.

Solution 12 - C#

You could easily do it with some code behind; In Main.xaml set: Closing="Window_Closing"

In Main.cs:

 public MainViewModel dataContext { get; set; }
 public ICommand CloseApp 
   {
      get { return (ICommand)GetValue(CloseAppProperty); }
      set { SetValue(CloseAppProperty, value); }
   }
public static readonly DependencyProperty CloseAppProperty =
DependencyProperty.Register("CloseApp", typeof(ICommand), typeof(MainWindow), new PropertyMetadata(null));

In Main.OnLoading:

dataContext = DataContext as MainViewModel;

In Main.Window_Closing:

   if (CloseApp != null)
      CloseApp .Execute(this);

In MainWindowModel:

    public ICommand CloseApp => new CloseApp (this);

And finally:

class CloseApp : ICommand { public event EventHandler CanExecuteChanged;

    private MainViewModel _viewModel;

    public CloseApp (MainViewModel viewModel)
    {
        _viewModel = viewModel;
    }


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

    public void Execute(object parameter)
    {
        Console.WriteLine();
    }
}

Solution 13 - C#

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        MessageBox.Show("closing");
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionOlivier PayenView Question on Stackoverflow
Solution 1 - C#dbkkView Answer on Stackoverflow
Solution 2 - C#StasView Answer on Stackoverflow
Solution 3 - C#PILuacesView Answer on Stackoverflow
Solution 4 - C#AxdorphCoderView Answer on Stackoverflow
Solution 5 - C#AllenMView Answer on Stackoverflow
Solution 6 - C#ChrisView Answer on Stackoverflow
Solution 7 - C#ChrisBDView Answer on Stackoverflow
Solution 8 - C#EchtelionView Answer on Stackoverflow
Solution 9 - C#Brian OrtizView Answer on Stackoverflow
Solution 10 - C#Chris AdamsView Answer on Stackoverflow
Solution 11 - C#RonView Answer on Stackoverflow
Solution 12 - C#Damnjan MarkovicView Answer on Stackoverflow
Solution 13 - C#Mattias SturebrandView Answer on Stackoverflow