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:
<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:
Command="{Binding DeleteCommand}"
You can code:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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
.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.