Archive

Archive for the ‘Validation’ Category

Checking Property Types Automatically

May 6, 2012 2 comments

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:

  1. a way to provide globalized validation messages independent of the system’s exception messages.
  2. 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:

Protected Overrides Sub AttachRules(ByVal base As Object)
    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 DateMessage As String = "Must be a date value."

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:

Protected Overrides Function ValidateCore(ByVal value As Object, ByVal cultureInfo As System.Globalization.CultureInfo) As Boolean
    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:

Protected Overrides Function FormatMessage(ByVal value As Object) As String
    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.

Advertisements

Validating Objects with ValidationRules

December 31, 2010 4 comments

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.

Namespace Validation
    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:

Namespace Validation
    <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:

Protected Overridable Sub AttachRules(ByVal base As Object)

    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 ReadOnly RuleSetterProperty As DependencyProperty = DependencyProperty.RegisterAttached("RuleSetter", GetType(ValidationRuleSetter), GetType(Validation), New PropertyMetadata(AddressOf WPFGlue.Framework.StickyComponentManager.OnStickyComponentChanged))
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 v:Validation.RuleSetter="{StaticResource vrules}" >
    <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

December 16, 2009 8 comments

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 ReadOnly AdornedElementToolTipProperty As DependencyProperty = DependencyProperty.RegisterAttached("AdornedElementToolTip", GetType(Object), GetType(Validation), New PropertyMetadata(Nothing, AddressOf OnAdornedElementToolTipChanged))
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:

<ControlTemplate x:Key="errorTemplate">
    <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:

<StackPanel>
    <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>

Categories: Validation Tags: , ,

Forwarding the Result of WPF Validation in MVVM

December 3, 2009 2 comments

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:

Public Shared ReadOnly MVVMHasErrorProperty As DependencyProperty = _
    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:

Private Shared Function CoerceMVVMHasError(ByVal d As DependencyObject, ByVal baseValue As Object) As Object
    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.

Categories: Validation Tags: ,

Submit Sticky Command Example

November 21, 2009 Leave a comment

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

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.

WPFGlue Categories

November 7, 2009 Leave a comment

I’d like to classify WPFGlue components according to what they do in the application. So far, the following categories came out:

Navigation

The WPF navigation framework is just great. Especially the journal: you get the complete forwards and backwards navigation for free, the journal remembers the user input on the page, and this works even when pages are freed in order to save memory.

However, WPF navigation stops just short of being really sticky. Selecting an object on one page and passing it forward to another page is supposed to be done through code behind, and the journal serializes objects instead of hold references to them, which is in itself a wise thing to do in order to not keep these objects from being freed, but makes it difficult to return to the same object instance if one goes back to a page. Also, I don’t like the suggestion in the WPF documentation that the navigation topology should be defined through pages referencing each other through hyperlinks. In my opinion, the pages should work stand-alone as much as possible, and there should be a central definition for the navigation topology, defining the structure of the application and mapping pages to the appropriate places in the object model.

WPFGlue supports navigation through a sticky GoToPage CommandBinding which takes an object parameter and passes it to the DataContext of the new page, a sticky Session property which can hold WeakReferences to business objects while the journal stores pointers to these references, and a ViewModelKit for defining a navigation menu.

Localization

The need for localization is immediately obvious for every programmer whose mother tongue is not English. Surprisingly, in WPF localization seems to have been added more or less as an afterthought. While Visual Studio supports localization very nicely for Windows Forms applications, getting at resources contained in satellite assemblies is not straight forward in WPF, and even something as basic as detecting the system language and making it the default culture for display and formatting in the application requires extra work. I could imagine that in the beginning the creators of WPF thought that people would just write different XAML pages for different languages, as is done in HTML, and that the need for localizing XAML only arose after the power of XAML as a programming language became more apparent, and programming elements became more predominant on XAML pages than clear text.

In the Localization category, WPFGlue comes with some sticky MarkupExtensions which make it easier to localize an application. These MarkupExtensions get their values from standard .Net localized resources, so no need for LocBAML or third party tools there. Other MarkupExtensions can be used to set WPF elements’ Language property to the CurrentCulture or CurrentUICulture.

Commanding

The commanding support in WPF is ingenious. It allows to implement some functionality once and to attach it to all the places where it is applicable, through RoutedCommands and CommandBindings. At the same time, commands decouple the traditional menu and toolbar from the application’s object model, making for great reusable code. Fabulously sticky stuff!

Commands are also the way ViewModels in the MVVM pattern expose their functionality to the user interface. The only unsticky thing about them is that CommandBindings support adding handler routines only through code, either code behind or explicitly. Also, there is no native general way of wrapping a method call into a command.

In the Commanding category, WPFGlue should support mapping commands to method calls, and also direct commands (ICommand implementations that are exposed by a ViewModel) to routed commands which can be handled centrally. There should be a way to set up a CommandSet and to connect it to a page, thus being able to define the available commands and their handlers in a central location. Also, the Commanding category contains a CommandGroup, which executes a series of commands in one go.

Validation

MVVM developers seem to avoid the validation features of WPF in favour of validation through the ViewModel or business model, exposed through the IDataErrorInfo interface. I don’t really understand why.

First of all, providing error messages to a user is not the job of a business object. The business object shouldn’t know whether it is used by an interactive user, a background service, or a batch job. In addition to that, in order to be able to provide meaningful error messages, the business object model would have to localize its error messages, and I wouldn’t want to burden my business objects with a job like that.

Second, while the business model knows what itself can do, it has no idea about what the developer of an application that reuses it may want to do. It would be perfectly justified to forbid certain legal property values because they are outside the scope of the current application. In this case, the ViewModel would have to constrain the values it allows for a certain property before it passes them on to the business object model.

So why not let the ViewModel do the job? I agree, the ViewModel is responsible. However, I wouldn’t use IDataErrorInfo, because that forces me to re-implement a lot of functionality that is already present in the WPF validation model. For example, resetting the values of an object to their pre-edit state before they are committed while still being able to provide instant validation feedback is really easy with ValidationRules and a BindingGroup, but requires a lot of effort, including ongoing maintenance effort, when done in a ViewModel specialized for a certain business model.

So, what I’d do instead is create a ViewModelKit that allows one to define a group of ValidationRules, which may or may not be dependent on validation functions in the business object model, and to allow setting these ValidationRules to a BindingGroup using a sticky property.

In addition to that, one would find workarounds for some minor problems with Validation.ErrorTemplate and ways to inform the ViewModel of validation results in the Validation category of WPFGlue.

Application Services

Application services would be things like forwarding application lifetime events to the ViewModel or providing services that are available in the operating system environment without creating a dependency from the ViewModel to the View.

Examples for this are a MessageBox that is invoked through sticky properties, a sticky StartupCommand property, a markup extension that allows to include application settings in the user interface, or context sensitive help through sticky properties.

Common Tasks

Common tasks would be jobs that recur in user interfaces, so that it makes sense to implement them in sticky ViewModels.

Examples would be a FileManager that provides support for the usual File menu commands for any type of document, a ListEditor that has commands for moving, adding and deleting items in collections implementing the standard collection interfaces, a ListPager that divides a collection into pages of a given length, and the like.

Conclusion

This is only a tentative list. The only category that will definitely survive is Common Tasks, since the others hopefully will be replaced by native support through WPF as the framework develops and the gaps between business object model and user interface vanish, so that there is no more need to fill them with glue.