Colin Eberhardt's Technology Adventures

BindingGroups for Total View Validation

January 26th, 2009

Over the weekend Sacha published a new article on codeproject, Total View Validation (does Sacha ever sleep?). This article addresses some of the perceived problems with the WPF binding framework, firstly, that the standard solution of using the ValidatesOnDataErrors property forces you to place validation logic into your bound business objects, and secondly, that there is no way to perform validation across multiple objects. His solution to both these problems was to construct a view model which contains the validation rules and applies them to all the objects within the form. A collection of validation failures are associated with the view model. These can either be rendered in their entirety or each control within the view can render errors relating to their context.

This blog post describes how BindingGroups can be used to address the problem of applying validation rules to multiple objects.

BindingGroups were introduced as part of .NET 3.5 SP1, Vincent Sibal provides a good introduction to this new features on his blog. I have previously blogged on the subject of BindingGroups, where I demonstrated how they can be used to provide improved error messages when type conversion fails during the binding process.

In this post I will take the application from Sacha’s article and modify it to use BindingGroups. The following is a screenshot of the application:

form

The form displays the details of two People (two instances of the Person class). The user inputs data using the above controls then clicks the Validate button to run all the Form’s validation rules. Most of the validation rules relate to a single property of the bound object, e.g. FullName cannot be empty. However there is one trick validation rule:

“If Person 1′s age is less than 18, Person 2′s age cannot be zero”

This rule cannot be evaluated by assocaiting it with the Age property of either person. It requires access to both Person instances, hence it really belongs at the scope of the form itself. This is where BindingGroups come in …

The XAML below shows our form which consists of a pair of StackPanels that contain fields bound to our Person instances by setting their DataContext properties in the code-behind. The ‘root’ element of the Window contains our BindingGroup.

<StackPanel Name="RootElement" Orientation="Vertical">
    <!-- the binding group for this form -->
    <StackPanel.BindingGroup>
        <BindingGroup Name="FormBindingGroup">
            <BindingGroup.ValidationRules>
                <local:PersonValidationRule
                            ValidationStep="CommittedValue"/>
            </BindingGroup.ValidationRules>
        </BindingGroup>
    </StackPanel.BindingGroup>
 
    <Border>
 
        <StackPanel Orientation="Vertical">
 
            <!-- Person One fields -->
            <StackPanel Name="personOnePanel" Orientation="Vertical">
                <Label Content="Person1"/>
 
                <!-- FirstName Property-->
                <StackPanel Orientation="Horizontal" >
                    <Label Content="FirstName" />
                    <TextBox Text="{Binding Path=FirstName, 
                                           BindingGroupName=FormBindingGroup,
                                           ValidatesOnDataErrors=true}" />                            
                </StackPanel>
 
                <!-- LastName Property-->
                ...
            </StackPanel>
 
            <!-- Person Twofields -->
            <StackPanel Name="personTwoPanel"  Orientation="Vertical">
                <Label Content="Person2"/>
 
                <!-- FirstName Property-->
                <StackPanel Orientation="Horizontal" >
                    <Label Content="FirstName" />
                    <TextBox Text="{Binding Path=FirstName, 
                                           BindingGroupName=FormBindingGroup,
                                           ValidatesOnDataErrors=true}" />                            
                </StackPanel>
 
                <!-- LastName Property-->
                ...
            </StackPanel>
 
        </StackPanel>
 
    </Border>
 
    <StackPanel Orientation="Vertical">
 
        <Button x:Name="btnValidate" Content="Validate" Margin="5"  />
 
    </StackPanel>
</StackPanel>

Rules associated with a BindingGroup have access to both their associated binding expressions, and the source objects used by these bindings. A BindingGroup becomes associated with a collection of binding expressions if it shares the same DataContext. However, in the above example we have a pair of objects, therefore we cannot share a DataContext across all the elements in our XAML (this is not strictly true, we could construct a view model for this purpose, however I really want to illustrate one of the other features of BindingGroups). BindingGroups can also be explicitly associated with a Binding via its BindingGroupName property.

So just what exactly can we do with our BindingGroup? It is possible to execute this rule at various different steps of the binding process, for example, it is possible to evaluate the rule before type conversion has occurred. In this instance, we are only interested in the converted value, hence the ValidationStep property is set to CommittedValue. The rule itself is very simple:

public class PersonValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        BindingGroup bindingGroup = (BindingGroup)value;
 
        // extract the two bound Person instances.
        Person personOne = bindingGroup.Items[0] as Person;
        Person personTwo = bindingGroup.Items[1] as Person;
 
        if (personTwo.Age==0 && personOne.Age<18)
        {
            return new ValidationResult(false,
                     "Person 2 Age can't be zero if Person1 Age < 18");
        }           
 
        return ValidationResult.ValidResult;
    }
}

We simply retrieve both Person instances, apply our rule raising an error if our condition is not met. Any validation errors returned by the BindingGroup become associated with the Validation.Errors attached property of the element which the BindingGroup is associated with. Therefore they can be accessed and styled in the ‘standard’ way, for example:

<Style TargetType="{x:Type TextBox}">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip"
                Value="{Binding RelativeSource={RelativeSource Self}, 
                       Path=(Validation.Errors)[0].ErrorContent}"/>
        </Trigger>
    </Style.Triggers>
</Style>

See the excellent article Validation in WPF for details.

However, BindingGroups offer one further advantage, validation errors from the associated binding expressions are also placed in the Validation.Errors collection, therefore we can output all the validation errors for our form by binding to this property as follows:

<ItemsControl ItemsSource="{Binding Path=(Validation.Errors), ElementName=RootElement}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Label Foreground="Red" Content="{Binding Path=ErrorContent}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

The following screenshot shows the form where multiple validation errors are present:

errors

Conclusions

So … which approach is better? BindingGroups or Sacha’s ViewModel technique?

Personally, I don’t think there is one best approach – and this blog post is certainly not an attack on Sacha’s article, which presents an elegant solution. My intention is to simply show that it is possible to perform validation on multiple objects at a ‘wider’ scope with the standard WPF framework.

To quote Mike Hillberg, the Poo is Optional.

A sample project with the code from this post is available for download: bindinggroupsglobalvalidation.zip.

Regards, Colin E.

Using BindingGroups for greater control over input validation

November 28th, 2008

In a recent post on his blog Josh Smith described a technique for providing more meaningful error messages when the type conversion process fails within the binding framework. Consider the following problem; you bind an integer property of your object (Age for example) to a TextBox within your user interface. If the user enters a non-numeric value into the TextBox an exception is thrown within the binding framework when it attempts to parse this value. The framework provide s a way of catching validation exceptions, by setting ValidatesOnExceptions to true within a binding, however the error message provided is “Input string was not in a correct format.” – this is the sort of error message that a software engineer would understand, but not the majority of the population!

standardvalidation

Josh details a technique that can be used to provide more meaningful error messages. His technique uses a View-Model (the classes which massage your data into a form which is more amenable to your presentation technology), which binds a text ‘Age’ property to ensure that there are no type conversions issues in the binding, allowing the issue of parsing errors to be managed directly. This blog post illustrates an alternative technique to his approach, using the recently introduced feature of BindingGroups.

Take for example a very simple example, a Person class which has properties of Name and Age. This class implements IDataErrorInfo, allowing us to manage validation logic within the business objects themselves. This class has a pair of simple rules which are applied to the Age property.

public class Person : IDataErrorInfo, INotifyPropertyChanged
{
    private int age;
    public int Age
    {
        get { return age; }
        set {
            age = value;
            RaisePropertyChanged("Age");
        }
    }
 
    private string name;
    public string Name
    {
        get { return name; }
        set {
            name = value;
            RaisePropertyChanged("Name");
        }
    }
 
    #region IDataErrorInfo Members
    public string Error
    {
        get { return null; }
    }
 
    public string this[string columnName]
    {
        get {
            if (columnName == "Age")
            {
                if (Age < 0)
                    return "Age cannot be less than 0.";
                if (Age > 120)
                    return "Age cannot be greater than 120.";
            }
            return null;
        }
    }
    #endregion
 
    #region INotifyPropertyChanged Members
    ...
    #endregion
}

An instance of the above class is displayed in a simple form using the following XAML:

<Grid x:Name="RootElement">
  <Grid.BindingGroup>
    <BindingGroup>
      <BindingGroup.ValidationRules>
        <local:PersonValidationRule
          ValidationStep="ConvertedProposedValue"/>
      </BindingGroup.ValidationRules>
    </BindingGroup>
  </Grid.BindingGroup>
 
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="*"/>
    <ColumnDefinition Width="1.8*"/>
  </Grid.ColumnDefinitions>
 
  <Grid.RowDefinitions>
    <RowDefinition/>
    <RowDefinition/>
    <RowDefinition/>
  </Grid.RowDefinitions>
 
  <Label Content="Name:"/>
  <TextBox Grid.Column="1" LostFocus="TextBox_LostFocus"
       Text="{Binding Name, ValidatesOnDataErrors=true}"/>
 
  <Label Grid.Row="1" Content="Age:"/>
  <TextBox Grid.Row="1" Grid.Column="1" LostFocus="TextBox_LostFocus"
       Text="{Binding Age, ValidatesOnDataErrors=true}"/>
 
  <Label Grid.Row="2" Grid.ColumnSpan="2" Foreground="Red"
       Content="{Binding Path=(Validation.Errors)[0].ErrorContent,
               ElementName=RootElement}"/>
 
</Grid>

The TextBox bindings use ValidatesOnDataErrors, this ensures that the IDataErrorInfo interface methods on the Person class will be invoked, enforcing our Age rules. However, they do not handle exceptions thrown in the binding process, i.e. ValidatesOnExceptions is absent.

The interesting part of the above code is the BindingGroup which is present in the Grid element, i.e. at the ‘Form’ level. When you define a BindingGroup it is related to the DataContext of the element that it is defined against. The BindingGroup will then have access to all the other Bindings which relate to this DataContext. In the above example, the bindings inherit the Grid’s DataContext and are members of the same BindingGroup.

BindingGroups allow group level validation, this is useful for example if you have validation rules which relate to more than one property, for example “StartDate < EndDate". You can read all about BindingGroups on Vincent Sibal’s Blog.

In the above example, we do not have complex validation logic, so why use a BindingGroup?

When a ValidationRule is associated with a BindingGroup it has access to both the BindingExpressions, i.e. the “Name” and “Age” bindings in our example, and the BindingGroup instance itself. This class has some very useful methods like TryGetValue, which will attempt to get the bound value from the TextBox (or other bound UI control). However, the interesting part is that you can determine at what point in the binding pipeline your validation rule is applied, which will in turn determine whether you get back the raw value from the control, for example the string “34″, or the value after it has been parsed, i.e., an integer ’34′.

When BindingGroup was added to the API in .NET 3.5 SP1, the ValidationRule class was extended to add a ValidationStep property. This enumeration indicates at what stage within the binding pipeline a particular rule is invoked. The four possible enumeration values are detailed on MSDN. The one which we are interested in here is ConvertedProposedValue, which will mean that our rule is invoked after the binding framework has parsed the input string to an integer.

Here is our validation rule:

public class PersonValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value,
                                             CultureInfo cultureInfo)
    {
        BindingGroup bindingGroup = (BindingGroup)value;
        Person person = (Person)bindingGroup.Items[0];
 
        // validate the age
        object objValue = null;
        if (!bindingGroup.TryGetValue(person, "Age", out objValue))
        {
            return new ValidationResult(false, "Age is not a whole number");
        }
 
        // if we can retrieve the value - can we parse it to an int?
        int parseResult;
        if (!Int32.TryParse(objValue as string, out parseResult))
        {
            return new ValidationResult(false,
                            string.Format("Age is not a whole number"));
        }
 
        return ValidationResult.ValidResult;
    }
}

In the above code we ask the BindingGroup to provide its proposed value for the Age property. If the user has input a non-numeric value, TryParse will fail. We can then catch this failure and provide a suitable error message.

The final piece of the puzzle is dictating when this BindingGroup runs its associated rules. One of the primary purposes of the bindingGroup is to allow transactional editing of objects, for example within the WPF DataGrid it is used to commit a Row as a ‘atom’. We must manually commit this BindingGroup in order to run our rule, a ‘Submit’ button could be added to our form, but for simplicity in this example I simply commit as each TextBox loses focus:

private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
    RootElement.BindingGroup.CommitEdit();
}

When using BindingGroups we can still of course validate each Binding independently, our ValidatesOnDataErrors still works.

The finished result is shown below, the screenshot on the right shows how our rule within the BindingGroup catches the case where the string cannot be parsed, and on the left where our Age rule enforced via IDataErrorInfo is violated.

bindinggroupvalidation

You can download a demo project with all of this code – bindinggroupvalidation, simple change the file extension from .doc to .zip.

*phew* – I thought I started writing a blog so that I could avoid writing lengthy articles!

Regards, Colin E.