I took inspiration from this post, and have adapted it into a library I'm building for my own use (but will be public located here: https://github.com/RFBCodeWorks/MvvmControls
While my approach does somewhat expose the View to the ViewModel via the 'sender' and 'eventargs' being passed to the handler, I used this approach just in case its needed for some other sort of handling. For example, if the handler was not the ViewModel, but was instead some service that recorded when windows were opened/closed, then that service may want to know about the sender. If the VM doesn't want to know about the View, then it simply doesn't examine the sender or args.
Here is the relevant code I've come up with, which eliminates the Code-Behind, and allows binding within xaml:
Behaviors:WindowBehaviors.IWindowClosingHandler="{Binding ElementName=ThisWindow, Path=DataContext}"
/// <summary>
/// Interface that can be used to send a signal from the View to the ViewModel that the window is closing
/// </summary>
public interface IWindowClosingHandler
{
/// <summary>
/// Executes when window is closing
/// </summary>
void OnWindowClosing(object sender, System.ComponentModel.CancelEventArgs e);
/// <summary>
/// Occurs when the window has closed
/// </summary>
void OnWindowClosed(object sender, EventArgs e);
}
/// <summary>
/// Attached Properties for Windows that allow MVVM to react to a window Loading/Activating/Deactivating/Closing
/// </summary>
public static class WindowBehaviors
{
#region < IWindowClosing >
/// <summary>
/// Assigns an <see cref="IWindowClosingHandler"/> handler to a <see cref="Window"/>
/// </summary>
public static readonly DependencyProperty IWindowClosingHandlerProperty =
DependencyProperty.RegisterAttached(nameof(IWindowClosingHandler),
typeof(IWindowClosingHandler),
typeof(WindowBehaviors),
new PropertyMetadata(null, IWindowClosingHandlerPropertyChanged)
);
/// <summary>
/// Gets the assigned <see cref="IWindowLoadingHandler"/> from a <see cref="Window"/>
/// </summary>
public static IWindowClosingHandler GetIWindowClosingHandler(DependencyObject obj) => (IWindowClosingHandler)obj.GetValue(IWindowClosingHandlerProperty);
/// <summary>
/// Assigns an <see cref="IWindowClosingHandler"/> to a <see cref="Window"/>
/// </summary>
public static void SetIWindowClosingHandler(DependencyObject obj, IWindowClosingHandler value)
{
if (obj is not null and not Window) throw new ArgumentException($"{nameof(IWindowClosingHandler)} property can only be bound to a {nameof(Window)}");
obj.SetValue(IWindowClosingHandlerProperty, value);
}
private static void IWindowClosingHandlerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Window w = d as Window;
if (w is null) return;
if (e.NewValue != null)
{
w.Closing += W_Closing;
w.Closed += W_Closed;
}
else
{
w.Closing -= W_Closing;
w.Closed -= W_Closed;
}
}
private static void W_Closed(object sender, EventArgs e)
{
GetIWindowClosingHandler(sender as DependencyObject)?.OnWindowClosed(sender, e);
}
private static void W_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
GetIWindowClosingHandler(sender as DependencyObject)?.OnWindowClosing(sender, e);
}
#endregion
}