Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF

Handling a Window's Closed and Closing events in the View-Model

Rate me:
Please Sign up or sign in to vote.
4.94/5 (24 votes)
15 Apr 2010CPOL2 min read 182.8K   3.3K   41   23
This article discusses an attached behavior that lets you handle the View Window's Closed and Closing events via commands in the View-Model

Image 1

Introduction

This article was inspired by Reed Copsey, Jr.'s Blend behavior which is up on the Expression Code Gallery. Reed's behavior uses a neat technique that lets the View-Model handle the Closing/Closed events of the View in an MVVM friendly manner. Since his code was tied to the Expression DLLs I thought it would be a good idea to write a plain WPF version. While similar in concept to the Blend behavior, I've slightly deviated from how the concept is implemented and also in how it's used. So this is not a direct 1-to-1 replacement though you should be able to get things working pretty much the same without too much effort.

The code idea is to allow a Window's Closed and Closing events to be handled via commands in the View-Model, and to get this going I wrote an attached behavior for the Window object.

Usage

The behavior exposes three commands:

  • Closed - This is executed when the Window.Closed event is fired.
  • Closing
    • Execute - This is executed if the Window.Closing event was not cancelled.
    • CanExecute - This is called when the Window.Closing event is fired, and gives you a chance to decide if you want to cancel or not.
  • CancelClosing - This is executed when the Window.Closing event is cancelled.

Here's an example of how you'd implement these commands in the View-Model. Notice how the example has MessageBox.Show calls in them, these are merely used to demonstrate how this works. You may want to use some kind of dependency injection to avoid direct UI code and instead use some kind of MessageBox service here (assuming you do want to prompt the user with a Yes/No message-box).

C#
internal class MainViewModel : ViewModelBase
{
    private ObservableCollection<string> log = new ObservableCollection<string>();

    public ObservableCollection<string> Log
    {
        get { return log; }
    }

    private DelegateCommand exitCommand;

    public ICommand ExitCommand
    {
        get
        {
            if (exitCommand == null)
            {
                exitCommand = new DelegateCommand(Exit);
            }
            return exitCommand;
        }
    }

    private void Exit()
    {
        Application.Current.Shutdown();
    }

    private DelegateCommand closedCommand;

    public ICommand ClosedCommand
    {
        get
        {
            if (closedCommand == null)
            {
                closedCommand = new DelegateCommand(Closed);
            }
            return closedCommand;
        }
    }

    private void Closed()
    {
        log.Add("You won't see this of course! Closed command executed");
        MessageBox.Show("Closed");
    }

    private DelegateCommand closingCommand;

    public ICommand ClosingCommand
    {
        get
        {
            if (closingCommand == null)
            {
                closingCommand = new DelegateCommand(
                    ExecuteClosing, CanExecuteClosing);
            }
            return closingCommand;
        }
    }

    private void ExecuteClosing()
    {
        log.Add("Closing command executed");
        MessageBox.Show("Closing");
    }

    private bool CanExecuteClosing()
    {
        log.Add("Closing command execution check");

        return MessageBox.Show("OK to close?", "Confirm", 
            MessageBoxButton.YesNo) == MessageBoxResult.Yes;
    }

    private DelegateCommand cancelClosingCommand;

    public ICommand CancelClosingCommand
    {
        get
        {
            if (cancelClosingCommand == null)
            {
                cancelClosingCommand = new DelegateCommand(CancelClosing);
            }
            return cancelClosingCommand;
        }
    }

    private void CancelClosing()
    {
        log.Add("CancelClosing command executed");
        MessageBox.Show("CancelClosing");
    }
}

And here's how you attach the commands in the XAML.

XML
<Window x:Class="WindowClosingDemo.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:nsmvvm="clr-namespace:NS.MVVM"
  nsmvvm:WindowClosingBehavior.Closed="{Binding ClosedCommand}"
  nsmvvm:WindowClosingBehavior.Closing="{Binding ClosingCommand}"
  nsmvvm:WindowClosingBehavior.CancelClosing="{Binding CancelClosingCommand}"
  Title="MainWindow" Height="350" Width="525">

Implementation

Here's the code listing for the attached behavior. This listing has been reformatted to fit within 600 pixels.

C#
public class WindowClosingBehavior
{
    public static ICommand GetClosed(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(ClosedProperty);
    }

    public static void SetClosed(DependencyObject obj, ICommand value)
    {
        obj.SetValue(ClosedProperty, value);
    }

    public static readonly DependencyProperty ClosedProperty 
        = DependencyProperty.RegisterAttached(
        "Closed", typeof(ICommand), typeof(WindowClosingBehavior),
        new UIPropertyMetadata(new PropertyChangedCallback(ClosedChanged)));

    private static void ClosedChanged(
      DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        Window window = target as Window;
        
        if (window != null)
        {
            if (e.NewValue != null)
            {
                window.Closed += Window_Closed;
            }
            else
            {
                window.Closed -= Window_Closed;
            }
        }
    }

    public static ICommand GetClosing(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(ClosingProperty);
    }

    public static void SetClosing(DependencyObject obj, ICommand value)
    {
        obj.SetValue(ClosingProperty, value);
    }

    public static readonly DependencyProperty ClosingProperty 
        = DependencyProperty.RegisterAttached(
        "Closing", typeof(ICommand), typeof(WindowClosingBehavior),
        new UIPropertyMetadata(new PropertyChangedCallback(ClosingChanged)));

    private static void ClosingChanged(
      DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        Window window = target as Window;

        if (window != null)
        {
            if (e.NewValue != null)
            {
                window.Closing += Window_Closing;
            }
            else
            {
                window.Closing -= Window_Closing;
            }
        }
    }

    public static ICommand GetCancelClosing(DependencyObject obj)
    {
        return (ICommand)obj.GetValue(CancelClosingProperty);
    }

    public static void SetCancelClosing(DependencyObject obj, ICommand value)
    {
        obj.SetValue(CancelClosingProperty, value);
    }

    public static readonly DependencyProperty CancelClosingProperty 
        = DependencyProperty.RegisterAttached(
        "CancelClosing", typeof(ICommand), typeof(WindowClosingBehavior));

    static void Window_Closed(object sender, EventArgs e)
    {
        ICommand closed = GetClosed(sender as Window);
        if (closed != null)
        {
            closed.Execute(null);
        }
    }

    static void Window_Closing(object sender, CancelEventArgs e)
    {
        ICommand closing = GetClosing(sender as Window);
        if (closing != null)
        {
            if (closing.CanExecute(null))
            {
                closing.Execute(null);
            }
            else
            {
                ICommand cancelClosing = GetCancelClosing(sender as Window);
                if (cancelClosing != null)
                {
                    cancelClosing.Execute(null);
                }

                e.Cancel = true;
            }
        }
    }
}

I have highlighted the relevant portions in the code above. For the Closed event, we just execute any command if available since it's too late to worry about cancelling. For the Closing event, the CanExecute of the Closing command is used to determine if we are cancelling or not. If we are not cancelling, we execute the Closing command, else we execute a CancelClosing command if one is available. 

That's all. Thank you.

Reference

History

  • April 15, 2010 - Article first published

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Nish Nishant is a technology enthusiast from Columbus, Ohio. He has over 20 years of software industry experience in various roles including Chief Technology Officer, Senior Solution Architect, Lead Software Architect, Principal Software Engineer, and Engineering/Architecture Team Leader. Nish is a 14-time recipient of the Microsoft Visual C++ MVP Award.

Nish authored C++/CLI in Action for Manning Publications in 2005, and co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is experienced in technology leadership, solution architecture, software architecture, cloud development (AWS and Azure), REST services, software engineering best practices, CI/CD, mentoring, and directing all stages of software development.

Nish's Technology Blog : voidnish.wordpress.com

Comments and Discussions

 
GeneralMy vote of 5 Pin
sVasilich19-Sep-19 9:59
sVasilich19-Sep-19 9:59 
GeneralMy vote of 2 Pin
The.SR18-Jun-19 23:40
The.SR18-Jun-19 23:40 
QuestionCan this be applied to UserControl or need alternative? Pin
.NetDev_101221-Jan-18 9:56
.NetDev_101221-Jan-18 9:56 
QuestionWhat is the purpose of the SetXXXXCommand functions? Pin
Joe O'Leary5-May-17 5:12
Joe O'Leary5-May-17 5:12 
PraiseThis is beautiful! Pin
L4U80Y11-Nov-15 7:06
L4U80Y11-Nov-15 7:06 
AnswerRe: This is beautiful! Pin
Nish Nishant11-Nov-15 7:29
sitebuilderNish Nishant11-Nov-15 7:29 
SuggestionThere is something wrong with the example formatting Pin
Member 1027184628-Oct-15 5:56
Member 1027184628-Oct-15 5:56 
AnswerRe: There is something wrong with the example formatting Pin
Nish Nishant28-Oct-15 6:01
sitebuilderNish Nishant28-Oct-15 6:01 
GeneralMy vote of 5 Pin
Wayne Gaylard22-Jan-13 3:46
professionalWayne Gaylard22-Jan-13 3:46 
GeneralMy vote of 5 Pin
Sergiy Tkachuk6-Aug-12 13:16
Sergiy Tkachuk6-Aug-12 13:16 
GeneralNice, but the menu does not work. Pin
Magnus Gudmundsson25-Jan-11 1:26
professionalMagnus Gudmundsson25-Jan-11 1:26 
GeneralRe: Nice, but the menu does not work. Pin
Nish Nishant25-Jan-11 4:26
sitebuilderNish Nishant25-Jan-11 4:26 
GeneralRe: Nice, but the menu does not work. Pin
Magnus Gudmundsson25-Jan-11 22:02
professionalMagnus Gudmundsson25-Jan-11 22:02 
GeneralRe: Nice, but the menu does not work. Pin
Nish Nishant26-Jan-11 2:02
sitebuilderNish Nishant26-Jan-11 2:02 
GeneralGood stuff, this what I do in CInch too Pin
Sacha Barber15-Apr-10 20:39
Sacha Barber15-Apr-10 20:39 
GeneralRe: Good stuff, this what I do in CInch too Pin
Nish Nishant16-Apr-10 1:24
sitebuilderNish Nishant16-Apr-10 1:24 
Thanks Sacha.

GeneralRe: Good stuff, this what I do in CInch too Pin
Alan Beasley18-Apr-10 0:43
Alan Beasley18-Apr-10 0:43 
GeneralRe: Good stuff, this what I do in CInch too Pin
Alan Beasley18-Apr-10 0:40
Alan Beasley18-Apr-10 0:40 
GeneralRe: Good stuff, this what I do in CInch too Pin
Sacha Barber18-Apr-10 1:03
Sacha Barber18-Apr-10 1:03 
GeneralRe: Good stuff, this what I do in CInch too Pin
Alan Beasley19-Apr-10 0:24
Alan Beasley19-Apr-10 0:24 
GeneralRe: Good stuff, this what I do in CInch too Pin
Sacha Barber19-Apr-10 4:10
Sacha Barber19-Apr-10 4:10 
GeneralRe: Good stuff, this what I do in CInch too Pin
Pete O'Hanlon19-Apr-10 11:26
subeditorPete O'Hanlon19-Apr-10 11:26 
GeneralRe: Good stuff, this what I do in CInch too Pin
Sacha Barber19-Apr-10 20:58
Sacha Barber19-Apr-10 20:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.