Implementing the Sticky Command Design Pattern
The Sticky Command design pattern is one of the patterns in WPFGlue which allow to add custom functionality to out-of-the-box WPF controls. Adding a Sticky Command to a FrameworkElement gives it a certain new behaviour, and makes this behaviour available to the element and all its child elements through a routed command, while the implementation of the element remains separate and independent of the implementation of the behaviour.
Let’s take an example. A very common need in data oriented applications is a submit command which sends the values of a group of controls to the underlying data store. This command should be disabled when the data is invalid, and it should be possible to connect it to a KeyGesture which allows to call the command by pressing a given key when entering data.
<GroupBox x:Name="CustomerGroupBox" Header="Customer" v:Submit.Command="{StaticResource SubmitCommand}" v:Submit.KeyGesture="Enter"> <!—- Content left out for clarity --> </GroupBox>
The Sticky Command is stuck to the GroupBox by setting the Submit.Command Sticky Property. Optionally, one can add a KeyGesture which invokes the command, in this case pressing the Enter key. The SubmitCommand is just a standard RoutedCommand:
<Window.Resources> <RoutedCommand x:Key="SubmitCommand"/> </Window.Resources>
By itself it doesn’t provide any functionality; it’s just kind of an identifier for the behaviour we want to add to the GroupBox.
Creating a Sticky Property
A Sticky Property is an attached property that, in addition to storing a value on the target element, does some work in its PropertyChanged handler. In our case, this work consists of creating a CommandBinding and setting it on the target element, or removing the created CommandBinding, respectively.
Public Class Submit Inherits DependencyObject Public Shared ReadOnly CommandProperty As DependencyProperty = _ DependencyProperty.RegisterAttached("Command", _ GetType(RoutedCommand), GetType(Submit), _ New PropertyMetadata(AddressOf OnCommandChanged)) Public Shared Function GetCommand(ByVal d As DependencyObject) _ As RoutedCommand Return d.GetValue(CommandProperty) End Function Public Shared Sub SetCommand(ByVal d As DependencyObject, _ ByVal value As RoutedCommand) d.SetValue(CommandProperty, value) End Sub Private Shared Sub OnCommandChanged(ByVal d As DependencyObject, _ ByVal e As DependencyPropertyChangedEventArgs) Dim f As FrameworkElement = TryCast(d, FrameworkElement) If f IsNot Nothing Then If e.OldValue IsNot Nothing Then Detach(f, e.OldValue) End If If e.NewValue IsNot Nothing Then Attach(f, e.NewValue) End If End If End Sub '... End Class
The implementation of OnCommandChanged is quite simple: if the property already has a value, the command should be detached from the target, and if a new value is provided, this value should be attached to it. One might ask why it is necessary to provide a Detach procedure as well. Why not just leave the CommandBinding in place? If the command can be removed by changing setting the value of Submit.Command to Nothing, then the command can be attached and detached through triggers, which opens up a whole new line of uses…
Attaching and Detaching the CommandBinding
Private Shared Sub Attach(ByVal d As FrameworkElement, _ ByVal command As RoutedCommand) d.CommandBindings.Add(New SubmitCommandBinding(command)) Dim gesture As KeyGesture = GetKeyGesture(d) AttachKeyGesture(d, command, gesture) If d.BindingGroup Is Nothing Then d.BindingGroup = New BindingGroup End If End Sub Private Shared Sub Detach(ByVal d As FrameworkElement, _ ByVal command As RoutedCommand) For Each b As SubmitCommandBinding In _ d.CommandBindings.OfType(Of SubmitCommandBinding)() d.CommandBindings.Remove(b) Next DetachKeyGesture(d) End Sub
The Attach procedure creates a new CommandBinding that connects the provided RoutedCommand to its implementation, and adds it to the targets CommandBindings collection.
The Detach procedure removes the CommandBinding from the collection. In order to be able to find the correct CommandBinding, Attach uses a class derived from CommandBinding, in order to use its type as marker. This way, one doesn’t have to store a reference to the created CommandBinding in order to be able to remove it later. The dummy class is a private nested class inside the Submit class:
Private Class SubmitCommandBinding Inherits CommandBinding Public Sub New(ByVal command As RoutedCommand) MyBase.New(command, AddressOf OnExecutedSubmit, _ AddressOf OnCanExecuteSubmit) End Sub End Class
The Command’s Implementation
The procedures OnExecutedSubmit and OnCanExecuteSubmit handle the RoutedCommand.CanExecute and RoutedCommand.Executed events that are sent out by the CommandBinding. It is useful to separate the code specific to event handling from the code that does the actual work:
Private Shared Sub OnCanExecuteSubmit(ByVal sender As Object, _ ByVal e As CanExecuteRoutedEventArgs) Dim result As Boolean = False Dim target As FrameworkElement = TryCast(sender, FrameworkElement) If target IsNot Nothing Then ' Separate event specific code from domain specific code... result = CanExecuteSubmit(target,e.Parameter) End If e.CanExecute = result e.Handled = True End Sub Public Shared Function CanExecuteSubmit(ByVal target As FrameworkElement, _ ByVal parameter As Object) As Boolean Dim result As Boolean = False If target IsNot Nothing Then result = (target.BindingGroup IsNot Nothing AndAlso Not _ System.Windows.Controls.Validation.GetHasError(target)) End If Return result End Function Private Shared Sub OnExecutedSubmit(ByVal sender As Object, _ ByVal e As ExecutedRoutedEventArgs) Dim target As FrameworkElement = TryCast(sender, FrameworkElement) If target IsNot Nothing Then If CanExecuteSubmit(target, e.Parameter) Then ExecuteSubmit(target, e.Parameter) End If e.Handled = True End If End Sub Public Shared Sub ExecuteSubmit(ByVal target As FrameworkElement, _ ByVal parameter As Object) target.BindingGroup.UpdateSources() End Sub
Providing a KeyGesture
In order to be able to specify a KeyGesture for the command on the target, we need to create a second Sticky Property, which is able to add the KeyGesture to the target’s InputBindings collection. The method is similar to the one used with the CommandBinding itself. However, there is one point of note: We cannot be sure of the order in which the attached properties are set on the target. So, we have to make sure that CommandBinding and InputBinding are set up correctly no matter which is specified first. Since the InputBinding depends on the CommandBinding, one could say that Submit.Command is the main Sticky Property, while Submit.KeyGesture is a supporting Sticky property. The rule would then be that the Attach and Detach procedures for the main Sticky property would have to call the Attach procedures for all supporting properties in turn, while the Attach procedures of the supporting properties only attach something if the main Sticky Property has already been set.
The implementation of the KeyGesture functionality looks like this:
Public Shared ReadOnly KeyGestureProperty As DependencyProperty = _ DependencyProperty.RegisterAttached("KeyGesture", _ GetType(KeyGesture), GetType(Submit), _ New PropertyMetadata(AddressOf OnKeyGestureChanged)) Public Shared Function GetKeyGesture(ByVal d As DependencyObject) _ As KeyGesture Return d.GetValue(KeyGestureProperty) End Function Public Shared Sub SetKeyGesture(ByVal d As DependencyObject, _ ByVal value As KeyGesture) d.SetValue(KeyGestureProperty, value) End Sub Private Shared Sub OnKeyGestureChanged(ByVal d As DependencyObject, _ ByVal e As DependencyPropertyChangedEventArgs) Dim f As FrameworkElement = TryCast(d, FrameworkElement) If f IsNot Nothing Then If e.OldValue IsNot Nothing Then DetachKeyGesture(f) End If If e.NewValue IsNot Nothing Then AttachKeyGesture(f, GetCommand(f), e.NewValue) End If End If End Sub Private Shared Sub AttachKeyGesture(ByVal d As FrameworkElement, _ ByVal command As RoutedCommand, _ ByVal gesture As KeyGesture) If command IsNot Nothing And gesture IsNot Nothing Then d.InputBindings.Add(New SubmitKeyBinding(command, gesture)) End If End Sub Private Shared Sub DetachKeyGesture(ByVal d As FrameworkElement) For Each i As SubmitKeyBinding In _ d.InputBindings.OfType(Of SubmitKeyBinding)() d.InputBindings.Remove(i) Next End Sub Private Class SubmitKeyBinding Inherits InputBinding Public Sub New(ByVal command As RoutedCommand, _ ByVal gesture As KeyGesture) MyBase.New(command, gesture) End Sub End Class
Reusability
Once we have this submit command, we can reuse it as it is, in any project, with any kind of control or data. But what about reusability of the pattern implementation itself?
The whole Submit class consists of static members, only. This has the advantage of not creating dependent object references with the command bindings, which could interfere with the garbage collection of the page on which the command is used. Unfortunately, shared members cannot be overridden, which means that it is not straight forward to create a common base class for Sticky Commands and derive new implementations from that class. However, the domain specific code and the pattern specific code are quite easy to separate into different procedures, so that it might be an option to use Item Templates or Code Snippets in order to reuse the pattern specific code.
Conclusion
The Sticky Command pattern offers a powerful and flexible way of modifying the behaviour of standard controls without changing their implementation. Because it is used through declarative syntax, it can leverage the full power of WPF style, trigger and template mechanisms. In addition to that, a new implementation of the pattern can be created from an existing one with minimal changes.
Downloads
The example solution on the Downloads page contains a project called SubmitCommandExample which has the complete code for this example.
