Archive

Posts Tagged ‘example’

Implementing the Sticky Command Design Pattern

February 13, 2010 1 comment

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.

Advertisements

Implementing the Sticky Command Design Pattern

November 12, 2009 2 comments

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

Download a complete example project for the Submit StickyCommand here.

Download an Item Template for Sticky Commands here.