Click here to Skip to main content
15,881,173 members
Articles / Desktop Programming / WPF
Article

WPF Conditional command

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
7 May 2012CPOL2 min read 11.8K   5  
A view side ICommand that conditionally executes a view-model side ICommand

Introduction

There are times when you what to do something view relate before or after executing a command. The classic example is the delete command: you what to confirm the deletion with users and give then the opportunity to change their mind. So I created an abstract class, ConditionalCommand, which implements the ICommand interface but redirects the command to another command defined as a dependency property. But before executing, it first asks the overridable question pShouldCommandExecute. For an example, I created a class ConfirmDeleteCommand that inherits from ConditionalCommand, adds another dependency property for the delete name, and overrides pShouldCommandExecute with code that redirects the question to a MsgBox. You can then define this command as a resource:

XML
<UserControl.Resources>
    <local:ConfirmDeleteCommand x:key="ConfirmDeleteCommand" 
        Command="{Binding DeleteCommand}"
        DeleteName="{Binding SelectedPlanName}"/>
</UserControl.Resources>

Since this, too, is an ICommand, you can use it anywhere a command can be specified. Instead of coding:

XML
Command="{Binding DeleteCommand}"

You can code:

XML
Command="{StaticResource ConfirmDeleteCommand}"

Note that this command is defined on the view side. The view-model side or DataContext side knows nothing about this command and this command knows only what is data-bound.

There is one catch: resources do not inherit DataContext. To do that, we must add some code-behind in the root control's constructor:

VB
Sub New()
     InitializeComponent()
     ConditionalCommand.SetAsOwnerOfResources(Me)
 End Sub

Now, every ConditionalCommand resource will inherit the root control's DataContext.

This assumes that the root control has the desired DataContext. If it's defined on a control lower in the hierarchy, then the resource must be moved to that control and its Initialize event handled by calling this method.

Detail look at the ConditionalCommand abstract class

The first thing to notice about this class is that it inherits from FrameworkElement instead of DependencyObject. It does this to get access the DataContext.

Here is the static ("Shared", in VB speak) method that the control called:

VB
Friend Shared Sub SetAsOwnerOfResources(aOwner As FrameworkElement)
  For Each eResource In aOwner.Resources.Cast(Of DictionaryEntry)()
     If TypeOf eResource.Value Is ConditionalCommand Then
        DirectCast(eResource.Value, ConditionalCommand).Owner = aOwner
     End If
  Next
End Sub

And the owner's property setter:

VB
Set(value As FrameworkElement)
   pOwner = value
   AddHandler pOwner.DataContextChanged,
              Sub(sender, e) Me.DataContext = e.NewValue
End Set

This waits for the owner's DataContext to change and then saves it.

Since this class implements ICommand, it must implement the interface's two methods and one event.

This first one is easy:

VB
Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
  Return If(Command Is Nothing, False, Command.CanExecute(CommandParameter))
End Function

This defers the question to the command if it is assigned or answers "no" if it isn't. Note that CommandParameter is a dependency property which defaults to nothing. If it were required, it could have been bound in the XAML.

The next one is the conditional part:

VB
Sub Execute(parameter As Object) Implements ICommand.Execute
   If pShouldCommandExecute() Then
        Command.Execute(CommandParameter)
        pOnCommandExecuted()
   End If
End Sub
Protected MustOverride Function pShouldCommandExecute() As Boolean
Protected MustOverride Sub pOnCommandExecuted()

The event is a little more involved:

VB
Custom Event CanExecuteChanged As EventHandler _
                Implements ICommand.CanExecuteChanged
   AddHandler(aHandler As EventHandler)
      If Command Is Nothing Then
          pHandlers.Add(aHandler)
      Else
          AddHandler Command.CanExecuteChanged, aHandler
      End If
   End AddHandler
   RemoveHandler(aHandler As EventHandler)
      If Command Is Nothing Then
           pHandlers.Remove(aHandler)
      Else
           RemoveHandler Command.CanExecuteChanged, aHandler
      End If
   End RemoveHandler
   RaiseEvent(sender As Object, e As EventArgs)
   End RaiseEvent
End Event
Private pHandlers As New List(Of EventHandler)

This redirects event handling to the command if it is assigned. If not, it must temporarily save the handlers.

When the command is assigned, the following routine is called to re-assign then to the command:

VB
Private Shared Sub pHandleCommandValueChanged(
         d As DependencyObject,
         e As DependencyPropertyChangedEventArgs)
  Dim mCommand = DirectCast(e.NewValue, ICommand)
  If mCommand Is Nothing Then Exit Sub
  Dim mMe = DirectCast(d, ConditionalCommand)
  For Each eHandler In mMe.pHandlers
     AddHandler mCommand.CanExecuteChanged, eHandler
  Next
  mMe.pHandlers.Clear()
End Sub

This is called because of the way the command dependency property is defined:

VB
Public Shared ReadOnly CommandProperty As DependencyProperty =
      DependencyProperty.Register(
          name:="Command",
          propertyType:=GetType(ICommand),
          ownerType:=GetType(ConditionalCommand),
          typeMetadata:=New FrameworkPropertyMetadata(
              defaultValue:=Nothing,
              PropertyChangedCallback:=AddressOf pHandleCommandValueChanged))

It's the last parameter that does the hook-up.

Detail look at the ComfirmDeleteCommand class.

This class inherits ConditionalCommand, adds a dependency property for DeleteName, and overrides the method pShouldCommandExecute as follows:

VB
Protected Overrides Function pShouldCommandExecute() As Boolean
  Dim mResult = MessageBox.Show(
          "Confirm deletion of " & DeleteName,
          "Confirm deletion", 
           MessageBoxButton.YesNo, 
           MessageBoxImage.Question, 
           MessageBoxResult.No)
  Return mResult = MessageBoxResult.Yes
End Function

Note that this command will work for deleting anything so long as the name of what's being deleted can be derived from DataContext.

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
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --