WPF – Bring Closing Event to MVVM

The Issue

I’m jumping around a bit, but I don’t get to work on a single project at a time, so I am going to switch gears to talk about some WPF work that I put together. In this project, I want to make sure that I can control when the user closes the window, regardless of whether they click on the “X” in the top-right or if they click the “Exit” button in the UI.

The problem that we have is that the logic that controls whether the user can close the window or not is in the view model. The exit button is controlled by the view model. But the “X” in the top-left corner can only be controlled by the Closing event in the code behind. You can use the event parameters to cancel the close. How can we leverage the logic in the view model to determine if the Window can close or not?

Hack Method

One thing we can do, is inside of the Closing event, we could query the view model to see if it is ok to exit or not. It’s not the greatest, it will be writing a lot of custom code in every window code behind.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Closing += MainWindow_Closing;
    }

    private void MainWindowClosing(object sender, CancelEventArgs a)
    {
        // make sure we have the view model class
        if (DataContext is MainWindowViewModel vm)
        {
            // check with the view model if should be canceled
            e.Cancel = vm.ShouldCancel();
        }
    }
}

Not pretty but it works.

Using Attached Properties

The way I chose to do it is to use DependencyProperty’s that participate in the XAML binding system and can attach to the view model directly and not have to wire up each and every window explicitly. To do this, we need to setup a few new properties. One for enabling or disabling this functionality; a second for a parameter to send back and forth from the view model; and finally, a third that will hold an object that contains an action that gets called when the window is attempted to be closed.

Let’s get started with the two dependency properties that are used for holding the can close implementation and the can close parameter that is passed back and forth. These properties have the following shapes.

public interface ICanCloseResult
{
    bool Cancel { get; set; }
}

public interface ICanCloseCheck
{
    void CheckCanClose(ICanCloseResult canCloseResult);
}

And here are the properties that hold these values.

public class CanCloseBinding
{
        public static readonly DependencyProperty CanCloseCheckProperty =
            DependencyProperty.RegisterAttached(
                "CanCloseCheck",
                typeof(ICanCloseCheck),
                typeof(CanCloseBinding),
                new PropertyMetadata(null));

        public static ICanCloseCheck GetCanCloseCheck(DependencyObject sender) => 
            (ICanCloseCheck) sender.GetValue(CanCloseCheckProperty);
        public static void SetCanCloseCheck(DependencyObject sender, ICanCloseCheck value) => 
            sender.SetValue(CanCloseCheckProperty, value);


        public static readonly DependencyProperty CanCloseCheckParameterProperty =
            DependencyProperty.RegisterAttached(
                "CanCloseCheckParameter",
                typeof(ICanCloseResult),
                typeof(CanCloseBinding),
                new PropertyMetadata(null));

        public static ICanCloseResult GetCanCloseCheckParameter(DependencyObject sender) => 
            (ICanCloseResult) sender.GetValue(CanCloseCheckParameterProperty);
        public static void SetCanCloseCheckParameter(DependencyObject sender, ICanCloseResult value) => 
            sender.SetValue(CanCloseCheckParameterProperty, value);
}

Next we have to build up the dependency property that enables or disables the functionality. The important thing this property does is, when it is set to true, it attaches an event handler to the Closing event of the window. Then, when that event is executed, it tries to get the instances of the other two parameters and execute them. So it looks like the following.

namespace CanClose.Views
{
public class CanCloseBinding
{
    // ... other properties here

    public static readonly DependencyProperty CheckCanCloseEnabledProperty =
        DependencyProperty.RegisterAttached(
            "CheckCanCloseEnabled",
            typeof(bool),
            typeof(CanCloseBinding),
            new PropertyMetadata(false, new PropertyChangedCallback(OnCheckCanClosePropertyChanged)));

    private static void OnCheckCanClosePropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (sender is Window w)
        {
            bool newEnabled = (bool) e.NewValue;
            bool oldEnabled = (bool) e.OldValue;

            if (oldEnabled && !newEnabled)
                w.Closing -= MyOwnWindowClosing; // remove the event handler for Closing from the Window
            else if (!oldEnabled && newEnabled)
                w.Closing += MyOwnWindowClosing; // add the event handler for Closing to the window
        }
    }

    public static bool GetCheckCanCloseEnabled(DependencyObject sender) => 
        (bool) sender.GetValue(CheckCanCloseEnabledProperty);
    public static void SetCheckCanCloseEnabled(DependencyObject sender, bool value) => 
        sender.SetValue(CheckCanCloseEnabledProperty, value);

    public static void MyOwnWindowClosing(object sender, CancelEventArgs e)
    {
        /// get the values from the dependency properties
        ICanCloseCheck q = GetCanCloseCheck((Window) sender);
        ICanCloseResult p = GetCanCloseCheckParameter((Window) sender);
        if (q == null || p == null)
            return;

        /// execute and save the value back to the CancelEventArgs object
        q.CheckCanClose(p);
        e.Cancel = p.Cancel;
    }
}
}

Finally, all we need to do is attach these properties to the window that we want to control from our view model. And that is done like this:

<Window x:Class="CanClose.Views.MainWindow"
    xmlns:local="clr-namespace:CanClose.Views"
    local:CanCloseBinding.CheckCanCloseEnabled="True"
    local:CanCloseBinding.CanCloseCheck="{Binding CCQuery}"
    local:CanCloseBinding.CanCloseCheckParameter="{Binding CCResult}"
    >
    <!-- stuff here -->
</Window>

And that is about it. If you want to see a sample, you can find it here.