Checking Property Types Automatically
It’s been a long time since I have written in this blog. The reason is: there have been many changes in my life during the past two years, and I simply haven’t found the time until now. Anyway, I’ve been checking out things and learning continuously, and I hope to be able to share these things more regularly in the future.
In the last post, Validating Objects with ValidationRules, I proposed a way to define ValidationRules for an object in one location and to set them on all Bindings that need them through a single attached property.
Today, I want to show how to set up basic type checks automatically. The example code is contained in the ValidationExample project in the source code distribution, which you can access through the downloads page.
How WPF handles Type
Normally, if the value entered in a TextBox cannot be converted into the type of the bound property, e.g. if you enter letters into a TextBox expecting an integer value, first of all there will be a runtime exception. By default, WPF will handle this exception, mark the TextBox as having invalid data, and assign a default “Conversion error” error message to it. Since the value cannot be converted, WPF will not update the bound property, which means that it will keep its value unchanged.
The first level of refinement here would be to allow the Binding to display exception messages by setting ValidatesOnExceptions to true. Now, if changing a property causes an exception, Validation.Errors will contain the exception’s message. Now the user will at least know whether the reason for the exception is a type mismatch or a numerical overflow.
Validation as a User Interface Responsibility
Why is this not enough?
Reusable software components like services or libraries need to check their input and throw exceptions if it is invalid. This is how one piece of software tells another piece of software that it has done something wrong.
User Interfaces, which basically translate between human users and the software they want to use, are responsible for making sure that those users only provide valid input values. The best way to do that is to offer only valid options to the user. Where that isn’t possible, validation steps in by providing explicit and meaningful validation messages which tell the users what they have to change in order to achieve the intended result.
As a rule of thumb, I’d say that one should protect one’s services and libraries by throwing exceptions, and that one should use validation in the UI to make sure that those exceptions never get thrown.
So, while exceptions are directed at technically aware developers debugging their software components, validation messages are directed at end users with possibly no technical background at all. For example, the error message you receive when you enter too many digits into the TextBox that validates with exceptions in the example project, reads: “Value was either too large or too small for an Int32”. This is obviously not what one would display to a user who has never heard of Int32.
Therefore, in the user interface, one would need two functionalities:
- a way to provide globalized validation messages independent of the system’s exception messages.
- a way to keep WPF controls from trying to update properties with values of non-matching type.
The TypeValidationRuleSetter Class
The TypeValidationRuleSetter class is a subclass of the ValidationRuleSetter class I mentioned in the last post. In addition to adding predefined ValidationRules to specified properties on a bound object, it will inspect the object’s type, find out the primitive CLR-type of a bound property, and will add default validation rules for each type which use XAML-configurable validation messages.
At the heart of the TypeValidationRuleSetter class is the following method:
Dim f As FrameworkElement = TryCast(base, FrameworkElement)
If f IsNot Nothing And ReferenceType IsNot Nothing Then
If f IsNot Nothing Then
Dim bg As BindingGroup = f.BindingGroup
If bg IsNot Nothing Then
For Each be As BindingExpression In bg.BindingExpressions.OfType(Of BindingExpression)()
Dim propertyName = be.ParentBinding.Path.Path
Dim propertyInfo As System.Reflection.PropertyInfo = ReferenceType.GetProperty(propertyName)
Select Case propertyInfo.PropertyType
Case GetType(DateTime)
be.ParentBinding.ValidationRules.Add(New DateValidationRule(Me.DateMessage))
Case GetType(Int16)
be.ParentBinding.ValidationRules.Add(New IntegerValidationRule(Int16.MinValue, Int16.MaxValue, Me.IntegerMessage))
Case GetType(Int32)
be.ParentBinding.ValidationRules.Add(New IntegerValidationRule(Int32.MinValue, Int32.MaxValue,Me.IntegerMessage))
Case GetType(Int64)
be.ParentBinding.ValidationRules.Add(New IntegerValidationRule(Int64.MinValue, Int64.MaxValue, Me.IntegerMessage))
Case GetType(Single)
be.ParentBinding.ValidationRules.Add(New NumericValidationRule(Single.MinValue, Single.MaxValue,Me.NumericMessage))
Case GetType(Double)
be.ParentBinding.ValidationRules.Add(New NumericValidationRule(Double.MinValue, Double.MaxValue, Me.NumericMessage))
Case GetType(Decimal)
be.ParentBinding.ValidationRules.Add(New NumericValidationRule(Decimal.MinValue, Decimal.MaxValue, Me.NumericMessage))
Case GetType(Integer)
be.ParentBinding.ValidationRules.Add(New IntegerValidationRule(Integer.MinValue, Integer.MaxValue, Me.IntegerMessage))
End Select
If Setters.Contains(propertyName) Then
For Each v As System.Windows.Controls.ValidationRule In Setters.Item(propertyName).ValidationRules
be.ParentBinding.ValidationRules.Add(v)
Next
End If
Next
End If
End If
End If
End Sub
This method will be called during the Initialized event, when Bindings have been established but still can be modified. It inspects the type of the property and adds one of a choice of default ValidationRules, which basically make a difference only between date, integer and other numerical values, providing different value ranges as feedback to the end user. So, the class translates from the technical, physical representation, which is determined by the CLR type of the property, to the logical limitations that type entails, which is what is relevant for the user.
So, although there are different checks for different allowed value ranges, there are only three configurable, i.e. localizable, validation messages:
Public Property IntegerMessage As String = "Must be an integer between {1} and {2}."
Public Property NumericMessage As String = "Must be a number between {1} and {2}."
The default ValidationRules use basic .Net functions in order to determine whether .Net can convert the value:
Dim result As Boolean = IsNumeric(value)
If result And MinValue.HasValue Then
result = CDbl(value) >= MinValue
End If
If result And MaxValue.HasValue Then
result = CDbl(value) <= MaxValue
End If
Return result
End Function
Finally, the allowed minimum and maximum values are formatted into the validation message, to give the users complete feedback on what they have to enter in order to make it a valid value:
Return String.Format(Me.Message, value, MinValue, MaxValue)
End Function
Conclusion
The TypeValidationRuleSetter class allows it to include meaningful and localizable validation messages for type checks with minimal effort. At the same time, it protects the application from exceptions which would otherwise be thrown because of type mismatches between what is entered into a TextBox and the type of the bound property.
Validating Objects with ValidationRules
In examples about writing WPF applications with the MVVM pattern, normally one doesn’t find any reference to ValidationRules. The normal way to do things there is to wrap the business object into the ViewModel and to implement IDataErrorInfo on the ViewModel. However, there are several points about this approach which I don’t like:
- I don’t like to wrap classes into other classes. The kind of code this produces is exactly the kind of useless boilerplate code I want to get rid of by using a flexible data binding system like WPF’s. If it can’t be helped, this kind of code should be generated, but that is not the topic of this blog.
- Validation on the ViewModel or Business level removes validation logic and error messages from the domain of the user interface definition to the domain of business logic. Now I like my business logic to be language independent, and it is absolutely valid for a user interface to implement different (more restrictive) checks on a property value than the business object it accesses. So, even if validation logic is implemented on the business level, I want to be able to choose how to use it and what error messages to display on the UI level, i.e. in XAML.
That said, ValidationRules have another significant advantage: they can check values before they are passed to the properties of the bound object. This means that I don’t have to implement rollback capabilities on the business object, but can make sure on the UI level that only valid values are set on the business object. This will be covered in a later post.
With all these advantages, there is a serious drawback to ValidationRules: defining them in XAML is rather unwieldy. Done the traditional way, they tend to bloat the XAML code, and sometimes it is difficult to get at all the reference data they need for their checks.
This is where WPFGlue comes into the picture: here, I want to introduce some ideas which allow to define and apply ValidationRules in a more concise manner, and to do some basic checks even without specifying ValidationRules explicitly.
The BindingGroup Class
The BindingGroup class has been introduced in WPF 3.5 SP1. A BindingGroup can be set on every FrameworkElement. If set, it can define ValidationRules which are able to access multiple properties of the DataContext in order to validate them against each other. In order to afford this possibility, the BindingGroup class provides methods which can access all Bindings in the subtree under the element which defines the BindingGroup, as long as these Bindings have the same DataContext as that element.
The idea here is to use this ability of the BindingGroup class in order to set up ValidationRules for all bindings which share a given DataContext.
So, in order to validate objects of a given class, first of all one would use a container like a StackPanel, Grid or GroupBox to combine a group of controls which share an instance of that class as their DataContext. Then, one would create a BindingGroup object and set it to the BindingGroup property of that container. Further, one would define a set of ValidationRules for a given class. Some of them would be at the property level, others would access multiple properties, as would be normal for ValidationRules on a BindingGroup object. Then, one would apply this set of ValidationRules to the container, instead of the separate controls which edit the object’s properties. One could reuse the set of ValidationRules wherever an instance of the DataContext class was used.
Using the BindingGroup’s ability for finding Bindings which have the same DataContext, one would add the ValidationRules to the appropriate Bindings inside the container.
How to Modify Bindings Which are Defined in XAML
The documentation says that Bindings can not be modified after they have been used. However, there is an event that comes right between the time when the Bindings have all been created, and when they are used for the first time: Initialized. If the DataContext is not null and a BindingGroup is present on a FrameworkElement, this BindingGroup can access and modify those Bindings during the FrameworkElement’s Initialized event.
So, one would need a couple of things:
- A way to define sets of ValidationRules.
- A Sticky Component which handles distributing the ValidationRules to the Bindings.
- A Sticky Property which attaches an instance of a set of ValidationRules to a container.
Defining Sets of ValidationRules
First of all, I said that I wanted to be able to define error messages in XAML. Using the techniques from the WPFGlue Localization namespace, I can handle multiple languages quite conveniently in XAML, and there might be other considerations like the intended audience of an application which might induce me to change the text of error messages. So, my first addition to a WPFGlue ValidationRule base class would be a Message property, which allows me to set the error message in XAML. In addition to that, I just added the functionality to format the property value into the error message.
Public MustInherit Class ValidationRule
Inherits System.Windows.Controls.ValidationRule
Private _Message As String = String.Empty
Public Property Message() As String
Get
Return _Message
End Get
Set(ByVal value As String)
_Message = value
End Set
End Property
Public Overrides Function Validate(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As System.Windows.Controls.ValidationResult
Dim result As ValidationResult = ValidationResult.ValidResult
If Not ValidateCore(value, cultureInfo) Then
Dim message As String = Me.FormatMessage(value)
result = New ValidationResult(False, message)
End If
Return result
End Function
Protected MustOverride Function ValidateCore(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As Boolean
Protected Overridable Function FormatMessage(ByVal value As Object) As String
Return String.Format(Me.Message, value)
End Function
End Class
End Namespace
The set of ValidationRules would be defined as a resource in XAML. In order to be able to do that, I create several classes which allow to define collections of ValidationRules grouped by property name:
<System.Windows.Markup.ContentProperty("Setters")>
Public Class ValidationRuleSetter
Inherits DependencyObject
Implements IStickyComponent
Private Shared _DummyDataContext As Object = New Object
Protected Overridable ReadOnly Property DummyDataContext As Object
Get
Return _DummyDataContext
End Get
End Property
Private _Setters As New PropertyValidationRuleSetterCollection
Public ReadOnly Property Setters As PropertyValidationRuleSetterCollection
Get
Return _Setters
End Get
End Property
The collection Setters contains collections of ValidationRules, grouped under different property names.
The ValidationRuleSetter class is a Sticky Component: in addition to defining the set of ValidationRules, it also has the ability to set the ValidationRules on the Bindings which belong to the container’s BindingGroup. The following method will be called during the container’s Initialized event:
Dim f As FrameworkElement = TryCast(base, FrameworkElement)
If f IsNot Nothing Then
Dim bg As BindingGroup = f.BindingGroup
If bg IsNot Nothing Then
For Each be As BindingExpression In bg.BindingExpressions.OfType(Of BindingExpression)()
If Setters.Contains(be.ParentBinding.Path.Path) Then
For Each v As System.Windows.Controls.ValidationRule In Setters.Item(be.ParentBinding.Path.Path).ValidationRules
be.ParentBinding.ValidationRules.Add(v)
Next
End If
If Setters.Contains(String.Empty) Then
For Each v As System.Windows.Controls.ValidationRule In Setters.Item(String.Empty).ValidationRules
bg.ValidationRules.Add(v)
Next
End If
Next
End If
End If
End Sub
For each Binding that is accessible to the BindingGroup, it looks up the name of the bound property and attaches all ValidationRules which are grouped under that name, if any. By convention, ValidationRules which have an empty String as property name are attached to the BindingGroup itself.
How to Use ValidationRuleSetter on a Container
In order to be able to attach the Sticky Component to a container, one defines a custom attached property like this:
Public Shared Function GetRuleSetter(ByVal d As DependencyObject) As ValidationRuleSetter
Return d.GetValue(RuleSetterProperty)
End Function
Public Shared Sub SetRuleSetter(ByVal d As DependencyObject, ByVal value As ValidationRuleSetter)
d.SetValue(RuleSetterProperty, value)
End Sub
This is actually the standard way of defining a sticky property in the WPFGlue framework.
One would then define the container in XAML like this:
<StackPanel.BindingGroup>
<BindingGroup/>
</StackPanel.BindingGroup>
<TextBox Text="{Binding StringProperty, UpdateSourceTrigger=PropertyChanged}"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"/>
<TextBox Text="{Binding IntegerProperty, UpdateSourceTrigger=PropertyChanged}"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"/>
<TextBox Text="{Binding DoubleProperty, UpdateSourceTrigger=PropertyChanged}"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"/>
</StackPanel>
Side Effects of Using BindingGroups
The presence of a BindingGroup on a container has various effects:
- Any failed ValidationRule on any Binding inside the BindingGroup will make Validation.HasError return true on the container.
- The BindingGroup’s own ValidationRules will cause the Validation.ErrorTemplate to be displayed on the container. ValidationRules on the Bindings will show the ErrorTemplate on the respective control and also the container’s ErrorTemplate.
- The UpdateSourceTrigger on all Bindings inside the BindingGroup will be set to Explicit by default, which means that one would have to call BindingGroup.CommitEdit in order to have the Bindings update their sources. However, if the UpdateSourceTrigger on a Binding is set to another value in XAML, this value will be honoured.
Conclusion
Using this technique, it is possible to separate the definition of validation rules both from the model and from the view. This overcomes the main disadvantage of ValidationRules and opens up the way for further applications.
In posts to come, I intend to cover how to extend this technique in order to do type checks on property values before they are committed to the bound properties, and how to connect this with general data entry handling.
On the Downloads page, you can find the full code of the examples, plus some additions which will be covered in a later post.
Using Validation.ErrorTemplate
This was originally published as a thread in the MSDN WPF forum before I started this blog. However, today I revisited the thread and decided to maintain it here, now..
There are a couple of known problems and unknown features with Validation.ErrorTemplate that came up frequently, and I’d like to collect the workarounds for these things in one place. This is how far I got:
Problem: The Content of the ErrorTemplate is not displayed correctly
e.g. the red border doesn’t fit around the control, or parts of the error template are not drawn. The reason is that there may not be enough space around the control to display the error template, so the solution is to make the margin of the control big enough that the error template can be drawn.
Unknown Feature: The DataContext of the ErrorTemplate is the Validation.Errors Collection
Haven’t found this in the documentation, but it means that the easiest way to get at an error message in an error template is to use {Binding Path=/ErrorContent}.
Problem: Have to Change the Style of a Control in Order to Set the Error Message as ToolTip
It seems to be impossible to set the ToolTip of a control using Styles or Triggers in the error template. Usually this is worked around by modifying the Style of the control, as in the example from the WPF SDK. However, this has the disadvantage that one has to change the style of the control in order to accommodate Validation, which might interfere with other uses of the Style. There are better workarounds: most elegant by using a ToolTip on some error indicator that is part of the error template, or, if it must be the control itself, using a custom attached property on the AdornedElementPlaceholder like this:
Public Shared Function GetAdornedElementToolTip(ByVal d As DependencyObject) As Object
Return d.GetValue(AdornedElementToolTipProperty)
End Function
Public Shared Sub SetAdornedElementToolTip(ByVal d As DependencyObject, ByVal value As Object)
d.SetValue(AdornedElementToolTipProperty, value)
End Sub
Private Shared Sub OnAdornedElementToolTipChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim placeholder As AdornedElementPlaceholder = TryCast(d, AdornedElementPlaceholder)
If placeholder IsNot Nothing Then
Dim control As Control = TryCast(placeholder.AdornedElement, Control)
If control IsNot Nothing Then
control.ToolTip = e.NewValue
End If
End If
End Sub
One can bind the error message to this property, and this way the error template works without interfering with the control’s style.
Problem: The Error Template is not Displayed when a Page is Loaded
This is by design, since one could assume that the user doesn’t want to see error messages before he/she made any mistakes, but sometimes one needs this functionality. So, the ValidatesOnTargetUpdated property was introduced on the ValidationRule class; by setting it to true, one sees the validation result immediately.
However, there is one caveat: you must make sure that you set the DataContext after the page is initialized; this would be either in the constructor after the generated comment line that says that initialization code should go there, or in the Loaded event. If you want to set the DataContext in XAML, you find a solution for this problem here (among other things): https://wpfglue.wordpress.com/2009/12/08/navigating-from-object-to-object/
Anyway, I hear this will be fixed in WPF 4.0.
Problem: When Using an Expander, the Error Template Remains Visible even if the Expander is Collapsed
This problem is a known bug, unfortunately as far as I know not yet scheduled for fixing. The workaround (found in the MSDN WPF forum) is binding the Visibility property of the outermost element of the error template to the AdornedElement.IsVisible property of the AdornedElementPlaceholder, using the BooleanToVisibilityConverter that comes with WPF.
Standard Error Template
So, this would be my standard error template:
<Border BorderBrush="Red" BorderThickness="1">
<Border.Visibility>
<Binding ElementName="placeholder" Path="AdornedElement.IsVisible">
<Binding.Converter>
<BooleanToVisibilityConverter/>
</Binding.Converter>
</Binding>
</Border.Visibility>
<StackPanel>
<AdornedElementPlaceholder x:Name="placeholder"
v:Validation.AdornedElementToolTip="{Binding Path=/ErrorContent}"/>
</StackPanel>
</Border>
</ControlTemplate>
And this would be how I use it:
<TextBox x:Name="Abox" Margin="3" Validation.ErrorTemplate="{StaticResource errorTemplate}"
Text="{Binding Path=A, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" />
<Expander Header="Test" IsExpanded="True" Validation.ErrorTemplate="{x:Null}">
<Expander.BindingGroup>
<BindingGroup/>
</Expander.BindingGroup>
<TextBox x:Name="Bbox" Margin="3"
Validation.ErrorTemplate="{StaticResource errorTemplate}"
Text="{Binding Path=B, UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}"/>
</Expander>
</StackPanel>
Forwarding the Result of WPF Validation in MVVM
A question that is frequently raised in connection with MVVM design is how one can access the result of WPF Validation (Validation.HasError) from the ViewModel. There is a way, but before I start explaining it, I’d like to ask an important question:
Why Would You Want to Do it?
Normally you would say: actually the ViewModel is responsible for validating the user input. So, if the input is invalid, the ViewModel should already know about it, isn’t it? Well, partially. There are some cases were the user input never reaches the ViewModel: when what the user entered cannot be converted to the correct data type, e.g. with date values or numbers. Catching these conversion errors in the ViewModel requires that you wrap each and every property of the data model that has one of these data types into a ViewModel property of type string, which bloats the ViewModel unnecessarily, and creates exactly the kind of glue code one tries to avoid by using the MVVM pattern in the first place.
One possibility to work around this is to create general, reusable ValidationRules for type checking and assign them to the Bindings. But then the ViewModel would have to know about the results of these checks in order to disable or enable commands or make other decisions based on the validity of its data. This would be the use case for the following technique:
How to Do it
The trick is to create a Sticky Property (which is a special case of an attached property) which, different from Validation.HasError, allows data bindings with direction to the source:
DependencyProperty.RegisterAttached("MVVMHasError", GetType(Boolean), GetType(Window1), _
New FrameworkPropertyMetadata(False, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, _
Nothing, AddressOf CoerceMVVMHasError))
Public Shared Function GetMVVMHasError(ByVal d As DependencyObject) As Boolean
Return d.GetValue(MVVMHasErrorProperty)
End Function
Public Shared Sub SetMVVMHasError(ByVal d As DependencyObject, ByVal value As Boolean)
d.SetValue(MVVMHasErrorProperty, value)
End Sub
In the metadata of this property, I set a flag that tells WPF that this property expects to forward its value to the source, so we don’t have to set the Binding Mode on the bindings in XAML.
In order to reflect the value of Validation.HasError, we need to set up an event that fires whenever the value of this property changes. In order to find out that we need this event, the CoerceValue callback of the property checks whether the property is data bound:
Dim result As Boolean = baseValue
If BindingOperations.IsDataBound(d, MVVMHasErrorProperty) Then
If GetHasErrorDescriptor(d) Is Nothing Then
Dim desc As System.ComponentModel.DependencyPropertyDescriptor = System.ComponentModel.DependencyPropertyDescriptor.FromProperty(System.Windows.Controls.Validation.HasErrorProperty, d.GetType)
desc.AddValueChanged(d, AddressOf OnHasErrorChanged)
SetHasErrorDescriptor(d, desc)
result = System.Windows.Controls.Validation.GetHasError(d)
End If
Else
If GetHasErrorDescriptor(d) IsNot Nothing Then
Dim desc As System.ComponentModel.DependencyPropertyDescriptor = GetHasErrorDescriptor(d)
desc.RemoveValueChanged(d, AddressOf OnHasErrorChanged)
SetHasErrorDescriptor(d, Nothing)
End If
End If
Return result
End Function
Private Shared Sub OnHasErrorChanged(ByVal sender As Object, ByVal e As EventArgs)
Dim d As DependencyObject = TryCast(sender, DependencyObject)
If d IsNot Nothing Then
d.SetValue(MVVMHasErrorProperty, d.GetValue(Validation.HasErrorProperty))
End If
End Sub
Private Shared ReadOnly HasErrorDescriptorProperty As DependencyProperty = DependencyProperty.RegisterAttached("HasErrorDescriptor", GetType(System.ComponentModel.DependencyPropertyDescriptor), GetType(Window1))
Private Shared Function GetHasErrorDescriptor(ByVal d As DependencyObject) As System.ComponentModel.DependencyPropertyDescriptor
Return d.GetValue(HasErrorDescriptorProperty)
End Function
Private Shared Sub SetHasErrorDescriptor(ByVal d As DependencyObject, ByVal value As System.ComponentModel.DependencyPropertyDescriptor)
d.SetValue(HasErrorDescriptorProperty, value)
End Sub
And that’s it… Notice that the CoerceValue callback uses a private DependencyProperty in order to store the DependencyPropertyDescriptor that is used to set up the change event. This has two purposes: it allows to remove the notification conveniently, and more important, it serves as a flag in order to see whether the notification is already in place or still needs to be set up.
You can download an example for this technique here. (You will have to change the extension to .zip in order to extract the contents).
Credits
I found the information about how to monitor changes in any object’s DependencyProperties in this article by Charles Petzold.
Submit Sticky Command Example
WPFGluePublished.zip on the Downloads page contains an example project called SubmitCommandExample which demonstrates how to use a Sticky Command in order to submit validated user input from a group of data bound controls to the object they bind to. The command will only be enabled if the input passes validation, and invalid data will not be forwarded to the data object.
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
Download a complete example project for the Submit StickyCommand here.
Download an Item Template for Sticky Commands here.