
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).
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.
<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.
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