WPF Command Confirmation

October 25, 2012

tags: , , ,
no comments

In the last post, I introduced you to my DynamicGridFormationBehavior, which gives you the ability to dynamically change a grid’s formation, using only Xaml. In this post, I want to introduce you to another behavior, called ConfirmationBehavior.

First let’s set the context. Assume that we have an application that handles customers’ information, and allows CRUD operations on it. The client asked that before any critical operation on the data (deleting a customer for example), a confirmation message will appear.

If you’re following the MVVM pattern in your WPF applications (and you should), you will probably end up with a ViewModel with an ICommand to handle the delete request, and a View with a button which will be bound to the command.

   1: public class CustomerViewModel : NotificationObject

   2: {

   3:     ...

   4:     public DelegateCommand DeleteCommand { get; private set; }

   5:     ...

   6: }

   1: <Button Content="Delete" Command="{Binding DeleteCommand}"/>

But what about the confirmation? Should it be part of the ViewModel, or part of the View’s code-behind?

Putting the confirmation code inside the ViewModel will sacrifice its testability, since the ViewModel will explicitly call MessageBox.Show (or some other alerts-related UI), preventing us from easily writing automatic unit tests for it. Furthermore, placing a UI specific code inside the ViewModel is a violation of the SRP principle.

Putting the confirmation code inside the View’s code-behind will not only sacrifice the testability, but also the maintainability of the application, since we will have to duplicate the entire confirmation code over and over again for each button that invokes a command that needs to be confirmed.

Enter ConfirmationBehavior

By encapsulating the confirmation code into a behavior, our code will be testable, since the ViewModel will not contain any UI-related code, and maintainable, since the application’s layers will be clearly separated, and the behavior will be reusable.

Let’s take a look at the behavior’s implementation:

   1: public class ConfirmationBehavior<T> : Behavior<T> where T : ButtonBase

   2: {

   3:     public static readonly DependencyProperty MessageProperty =

   4:         DependencyProperty.Register("Message", typeof (string), typeof (ConfirmationBehavior<T>));

   5:  

   6:     public string Message

   7:     {

   8:         get { return (string) GetValue(MessageProperty); }

   9:         set { SetValue(MessageProperty, value); }

  10:     }

  11:  

  12:     public static readonly DependencyProperty CaptionProperty =

  13:         DependencyProperty.Register("Caption", typeof (string), typeof (ConfirmationBehavior<T>));

  14:  

  15:     public string Caption

  16:     {

  17:         get { return (string) GetValue(CaptionProperty); }

  18:         set { SetValue(CaptionProperty, value); }

  19:     }

  20:  

  21:     public static readonly DependencyProperty CommandProperty =

  22:         DependencyProperty.Register("Command", typeof (ICommand), typeof (ConfirmationBehavior<T>));

  23:  

  24:     public ICommand Command

  25:     {

  26:         get { return (ICommand) GetValue(CommandProperty); }

  27:         set { SetValue(CommandProperty, value); }

  28:     }

  29:  

  30:     public static readonly DependencyProperty CommandParameterProperty =

  31:         DependencyProperty.Register("CommandParameter", typeof (object), typeof (ConfirmationBehavior<T>));

  32:  

  33:     public object CommandParameter

  34:     {

  35:         get { return GetValue(CommandParameterProperty); }

  36:         set { SetValue(CommandParameterProperty, value); }

  37:     }

  38:  

  39:     protected override void OnAttached()

  40:     {

  41:         base.OnAttached();

  42:         AssociatedObject.Click += OnButtonClick;

  43:     }

  44:  

  45:     protected override void OnDetaching()

  46:     {

  47:         base.OnDetaching();

  48:         AssociatedObject.Click -= OnButtonClick;

  49:     }

  50:  

  51:     private void OnButtonClick(object sender, RoutedEventArgs e)

  52:     {

  53:         if (Command == null || !Command.CanExecute(CommandParameter))

  54:             return;

  55:  

  56:         if (ShouldConfirm())

  57:         {

  58:             MessageBoxResult result = MessageBox.Show(Message, Caption, MessageBoxButton.YesNo,

  59:                                                       MessageBoxImage.Question);

  60:             if (result == MessageBoxResult.Yes)

  61:                 OnConfirmed();

  62:             else

  63:                 OnNotConfirmed();

  64:         }

  65:         else

  66:         {

  67:             OnConfirmed();

  68:         }

  69:     }

  70:  

  71:     protected virtual bool ShouldConfirm()

  72:     {

  73:         return true;

  74:     }

  75:  

  76:     protected virtual void OnConfirmed()

  77:     {

  78:         Command.Execute(CommandParameter);

  79:     }

  80:  

  81:     protected virtual void OnNotConfirmed() { }

  82: }

  83:  

  84: public class ConfirmationBehavior : ConfirmationBehavior<ButtonBase>

  85: {    

  86: }

  87:  

  88: public class ToggleConfirmationBehavior : ConfirmationBehavior<ToggleButton>

  89: {

  90:     protected override bool ShouldConfirm()

  91:     {

  92:         return AssociatedObject.IsChecked == true;

  93:     }

  94:  

  95:     protected override void OnNotConfirmed()

  96:     {

  97:         AssociatedObject.IsChecked = false;

  98:     }

  99: }

Please note the following:

  • The generic type parameter enables us to inherit and customize the behavior for more specific types (like ToggleButton, as you can see above).
  • The behavior allows us to customize the message and caption of the confirmation.
  • Currently, the behavior is using System.Windows.MessageBox, which is impossible to style. You can change it to use a different control if you wish (like the one in the Extended WPF Toolkit).
  • The non-generic ConfirmationBehavior is there just because not all Xaml schemas have a support for generics.

And the usage:

   1: <Button Content="Delete">

   2:     <i:Interaction.Behaviors>

   3:         <behaviors:ConfirmationBehavior

   4:             Message="Are You Sure?"

   5:             Caption="Please Confirm..."

   6:             Command="{Binding DeleteCommand}">

   7:         </behaviors:ConfirmationBehavior>

   8:     </i:Interaction.Behaviors>

   9: </Button>

As you can see, by using the behavior, we kept our ViewModel clean and made our intentions clear for anyone who is reading the Xaml.

The code is available on GitHub, as part of my WPFUtils project.

Cross-posted from http://www.programmingtidbits.com/post/2012/10/25/WPF-Command-Confirmation.aspx

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*