DataErrorValidationRule - New Way To Invalidate Data In WPF

|

The WPF 3.5 has introduced a new data validation API aka DataErrorValidationRule, which you can specify on the Binding object, if the data source implements the IDataErrorInfo interface. This new feature enables some of the scenario the previous Custom ValidationRule cannot enable.

One of the scenario the custom ValidationRule cannot support is to enable data binding on the properties of the Custom ValidationRule class. Because ValidationRule is not a DependencyOject, you cannot define dependency properties on it to enable data binding. And because ValidationRule is not a part of the element tree, any Binding expression which relies on RelativeSource or ElementName or similar things that needs to walk up the element tree to find the binding source cannot be evaluated successfully. Some community members such as Josh Smith has come up with a hackery to workaround this limitation using the trick he calls "Virutal Branches". Or our beloved Dr. WPF's ObjectReference custom Markup Extension as he "bloated" in this MSDN WPF thread.

DataErrorValidationRule drives those "dirty tricks" to obsolete. Because You don't need to bind some values to custom ValidationRule object as validation input parameters. Because when implementing IDataErrorInfo, you do the validation logic at the source object. The following is an example of how to leverage the DataErrorValidationRule API to suppport the type of scenario "Virtual Branches" is trying to enable.

First off, the data source class should implement IDataErrorInfo or INotifyPropertyChanged if you want to enable two way data binding as follows:

public class Person : IDataErrorInfo, INotifyPropertyChanged
{
    private int age;
    private int min = 0;
    private int max = 150;

    public int Age
    {
        get { return age; }
        set
        {
            age = value;
            RaisePropertyChanged("Age");
        }
    }

    public string Error
    {
        get
        {
            return null;
        }
    }

    public int Min
    {
        get
        {
            return min;
        }
        set
        {
            min = value;
            RaisePropertyChanged("Min");
        }
    }

    public int Max
    {
        get
        {
            return max;
        }
        set
        {
            max = value;
            RaisePropertyChanged("Max");
        }
    }

    public string this[string name]
    {
        get
        {
            string result = null;

            if (name == "Age")
            {
                if (this.age < this.Min || this.age > this.Max)
                {
                    result = String.Format("Age must not be less than {0} or greater than {1}.", this.Min, this.Max);
                }
            }
            return result;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

You can see that Min and Max properties needs to be specified by the user, so we need to bind those two properties to corresponding UI elements as following XAML snippet shows:

<Window x:Class="BusinessLayerValidation.Window1"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="WPF IDataErrorInfo Sample"
       Width="450" Height="170"
       xmlns:src="clr-namespace:BusinessLayerValidation">

  <
Window.Resources>
    <
src:Person x:Key="data"/>
    <
Style x:Key="textBoxInError" TargetType="TextBox">
      <
Style.Triggers>
        <
Trigger Property="Validation.HasError" Value="true">
          <
Setter Property="ToolTip"
                 Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                        Path=(Validation.Errors)[0].ErrorContent}
"/>
        </
Trigger>
      </
Style.Triggers>
    </
Style>
  </
Window.Resources>

  <
StackPanel Margin="20" DataContext="{Binding Source={StaticResource data}}">
    <
StackPanel Orientation="Horizontal">
      <
TextBlock Width="60">
        Min:(<TextBlock Text="{Binding Path=Value, ElementName=minSlider}"/>)
      </TextBlock>
      <
Slider Margin="10, 0, 0, 0"
              Name="minSlider"
              Width="300"
              Orientation="Horizontal"
              IsSnapToTickEnabled="True"
              HorizontalAlignment="Right"
              TickPlacement="BottomRight"
              AutoToolTipPlacement="BottomRight"
              Value="{Binding Path=Min, Mode=TwoWay}"
              Minimum="0"
              Maximum="150"
              TickFrequency="10"/>
    </
StackPanel>
    <
StackPanel Orientation="Horizontal">
      <
TextBlock Width="60">
        Max:(<TextBlock Text="{Binding Path=Value, ElementName=maxSlider}"/>)
      </TextBlock>
      <
Slider Margin="10, 0, 0, 0"
              Name="maxSlider"
              Width="300"
              Orientation="Horizontal"
              IsSnapToTickEnabled="True"
              HorizontalAlignment="Right"
              TickPlacement="BottomRight"
              AutoToolTipPlacement="BottomRight"
              Value="{Binding Path=Max, Mode=TwoWay}"
              Minimum="0"
              Maximum="150"
              TickFrequency="10"/>
    </
StackPanel>
    <
TextBlock>Enter your age:</TextBlock>
    <
TextBox Style="{StaticResource textBoxInError}" Name="textBox">
      <
TextBox.Text>
        <
Binding Path="Age"
                ValidatesOnDataErrors="True"
                UpdateSourceTrigger="PropertyChanged">
          <
Binding.ValidationRules>
            <
ExceptionValidationRule/>
          </
Binding.ValidationRules>
        </
Binding>
      </
TextBox.Text>
    </
TextBox>
  </
StackPanel>
</
Window>

You can see from the XAML shown above that DataErrorValidationRule actually provide a greater flexibility when validating data. For a detailed introduction to DataErrorValidationRule, and its role in the WPF data validation model, you can refer to this WPF SDK blog article.

For completeness, I've attached full sample project here for further reference.

Attachment: WPFDataValidation.zip

1 comments:

Sandeep Jhankar Nellutla said...

Hi Marco, I liked your profile and recently I saw your contribution on XCeed DataGrid control questions in msdn forums. I have a question on it. How can you custom sort a column programmatically? I mean like:

Datagrid1.Itemsource = dataset
Datagrid1.columns(1).sort = Ascending ?? (Something like this?)

How can you do so? Anyidea?

Sandeep.nellutla@gmail.com