What you are looking for is the Closing
event on Window
. Its CancelEventArgs
carry a Cancel
property of type bool
that can be set to true
, which will cancel closing the window. The closing event will both be fired for closing a window using the close button and pressing Alt+F4.
Code-Behind
In a code-behind scenario, you would add an event handler in XAML like this.
<Window Closing="OnClosing" ...>
Then, you would create this event handler in code-behind and set the Cancel
property accordingly.
private void OnClosing(object sender, CancelEventArgs e)
{
// Check if any field has been edited
if (IsDirty())
{
string message = "You have unsaved changes.
Are you sure you want to close this form?";
string title = "Close Window";
MessageBoxResult result = MessageBox.Show(message, title, MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (result == MessageBoxResult.Cancel)
e.Cancel = true;
}
}
Event Trigger (Non MVVM)
In MVVM you could install the Microsoft.Xaml.Behaviors.Wpf NuGet package, which is a replacement of the legacy Blend behaviors (System.Windows.Interactivity
). You can use an EventTrigger
to bind the event to a command in your view model. In this example, you pass the CancelEventArgs
directly to the command.
<Window ...>
<b:Interaction.Triggers>
<b:EventTrigger EventName="Closing">
<b:InvokeCommandAction Command="{Binding ClosingCommand}"
PassEventArgsToCommand="True"/>
</b:EventTrigger>
</b:Interaction.Triggers>
<!-- ...other markup. -->
</Window>
This solution allows you to define a command in your view model.
public class MyViewModel
{
public MyViewModel()
{
ClosingCommand = new RelayCommand<CancelEventArgs>(ExecuteClosing);
}
public ICommand ClosingCommand { get; }
private void ExecuteClosing(CancelEventArgs e)
{
string message = "You have unsaved changes.
Are you sure you want to close this form?";
string title = "Close Window";
MessageBoxResult result = MessageBox.Show(message, title, MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel);
if (result == MessageBoxResult.Cancel)
e.Cancel = true;
}
}
I do not provide an ICommand
implementation here. For more information on the basics refer to:
Although this solution uses a command on the view model, it is not MVVM compliant, since the message box is a view component that must not reside in a view model. The same applies to the cancel event args.
MVVM Behavior
An MVVM compliant way could be to create a behavior to move the confirmation code out. For that create an interface for your view model that contains the IsDirty
method and implement it in your view model.
public interface IStatefulViewModel
{
bool IsDirty();
}
public class MyViewModel : IStatefulViewModel
{
// ...your code.
public bool IsDirty()
{
// ...your checks.
}
}
Then, create a behavior using the Microsoft.Xaml.Behaviors.Wpf NuGet package. This behavior is reusable and encapsulates the closing logic decoupled from your view model. The Caption
and Message
dependency properties allow binding the message box contents.
public class WindowClosingBehavior : Behavior<Window>
{
public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register(
nameof(Caption), typeof(string), typeof(WindowClosingBehavior), new PropertyMetadata(string.Empty));
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
nameof(Message), typeof(string), typeof(WindowClosingBehavior), new PropertyMetadata(string.Empty));
public string Caption
{
get => (string)GetValue(CaptionProperty);
set => SetValue(CaptionProperty, value);
}
public string Message
{
get => (string)GetValue(MessageProperty);
set => SetValue(MessageProperty, value);
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Closing += OnClosing;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Closing -= OnClosing;
}
private void OnClosing(object sender, CancelEventArgs e)
{
if (!(AssociatedObject.DataContext is IStatefulViewModel statefulViewModel))
return;
if (!statefulViewModel.IsDirty())
return;
e.Cancel = ConfirmClosing();
}
private bool ConfirmClosing()
{
var result = MessageBox.Show(
Message,
Caption,
MessageBoxButton.OKCancel,
MessageBoxImage.Warning,
MessageBoxResult.Cancel);
return result == MessageBoxResult.Cancel;
}
}
Attach the behavior to your window. Note that you can do this on any window.
<Window ...>
<b:Interaction.Behaviors>
<local:WindowClosingBehavior Caption="Close Window"
Message="You have unsaved changes. Are you sure you want to close this form?"/>
</b:Interaction.Behaviors>
<!-- ...other markup. -->
</Window>
Do not be confused by the
characters, those are newlines (
) in XML.