WPF Input Validation Part 2: Attached property approach

3

 In the previous article I’ve shown how to apply Validation Rules to controls in order to prevent/access user input. In the process, user was warned through a visual change in the target control. It’s the simplistic and direct way of doing validation and works perfectly from the UX point of view. However, from the developer point of view, a better implementation is required to ease the quantity of code needed and to isolate concerns.

The idea is to apply validation rules by means of an attached property, in which the owner would have the reference to the control and its target Dependency Property, and the required logic to add the rule object implicitly.

We’ll assume the WPF window of the previous example and proceed from there. The biggest difference rests is the XAML. The TextBox no longer has a validation rule and, instead, an attached property was added. We’ll discuss it afterwards.

<Window x:Class="ValidationSample2.ValidationWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Rules="clr-namespace:ValidationSample2"
    Title="ValidationWindow" Height="200" Width="300">
    <Window.Resources>
        <Style x:Key="EditableTextBox" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
            <Setter Property="Background" Value="#DDFFDD" />
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <Border BorderBrush="Red" BorderThickness="2">
                            <AdornedElementPlaceholder />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="true">
                    <Setter Property="Background" Value="#FFDDDD"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
        <StackPanel Name="ValidationPanel" >
            <Label>Validated Text Box:</Label>
            <!-- UpdateSourceTrigger is needed in order to raise property change event on input
            and not on focus, which is the default behavior -->
            <TextBox x:Name="ValidatedTextBox" Width="200"
                     Rules:MandatoryRule.TargetProperty="{x:Static TextBox.TextProperty}"
                     Style="{DynamicResource EditableTextBox}" Text="{Binding Path=MyProperty, UpdateSourceTrigger=PropertyChanged}" />
        </StackPanel>
    </Grid>
</Window>

As you can see, things got much simpler. A single property set is all there is in order to set my mandatory input rule. So how do we do this? It’s pretty simple, actually. I’ve created a static class called MandatoryInput and, as you can see in XAML, I’ve defined the TargetProperty attached property and I’m registering on the static constructor. The essence of this implementation happens when this property’s value changes. Check out the full blown implementation that follows:

#region #using Directives

using System;
using System.Windows;
using System.Windows.Controls;

#endregion

namespace ValidationSample2
{
    public static class MandatoryRule
    {
        #region Dependency Properties

        public static readonly DependencyProperty TargetProperty;

        #region Get/Sets

        public static DependencyProperty GetTargetProperty(UIElement target)
        {
            return target.GetValue(TargetProperty) as DependencyProperty;
        }

        public static void SetTargetProperty(UIElement target, DependencyProperty value)
        {
            target.SetValue(TargetProperty, value);
        }

        #endregion

        #endregion

        #region Constructor

        static MandatoryRule()
        {
            TargetProperty = DependencyProperty.RegisterAttached("Target",
                                                                 typeof (DependencyProperty), typeof (MandatoryRule),
                                                                 new UIPropertyMetadata(null, OnTargetPropertyChanged));
        }

        #endregion

        #region Event handlers

        private static void OnTargetPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            if (!(obj is UIElement))
                throw new InvalidOperationException("Object must be a UI element.");

            SetValidationRule(obj, args.NewValue as DependencyProperty);
        }

        #endregion

        #region Methods

        private static void SetValidationRule(DependencyObject obj, DependencyProperty property)
        {
            if (property == null)
                throw new InvalidOperationException("No Validation DependencyProperty attached.");

            GetRule(obj, property);
        }

        private static void GetRule(DependencyObject obj, DependencyProperty property)
        {
            var control = obj as Control;

            if (!control.IsInitialized)
            {
                RoutedEventHandler callback = null;
                callback = delegate
                               {
                                   control.Loaded -= callback;
                                   SetValidationRule(obj, property);
                               };
                control.Loaded += callback;
                return;
            }

            var expression = control.GetBindingExpression(property);
            if (expression == null)
                throw new InvalidOperationException(
                    "The control's validation property must be bound for the Validator to validate it.");

            var binding = expression.ParentBinding;
            if (binding == null)
                throw new ApplicationException("Property binding expression has no parent binding.");

            ValidationRule mandatoryRule = null;
            foreach (var rule in binding.ValidationRules)
            {
                if (rule is MandatoryInputRule)
                {
                    if (mandatoryRule == null)
                        mandatoryRule = rule as MandatoryInputRule;
                    else
                        throw new InvalidOperationException(
                            "There's more than one MandatoryInputRule in the Binding's ValidationRules.");
                }
            }

            if (mandatoryRule == null)
            {
                mandatoryRule = new MandatoryInputRule();
                binding.ValidationRules.Add(mandatoryRule);
            }
        }

        #endregion
    }
}

As an attached property, I had to create the usual Get[PropertyName] and Set[PropertyName] methods so that these could actually set the values in the control. The idea is for this property to hold the target control’s dependency property we are going to interact with, and upon property change notification, execute our logic.

However, this notification actually  comes in two distinct phases. The first one happens when the XAML is processed by the XAML parser and the last is when it’s actually initialized. As you can see in the code, the GetRule() method is called on such occasions, however, binding data will only be available when the affected control is initialized, so it is crucial to do our work only when. So, on the first phase, since the control isn’t yet initialized, we just set an event handler to execute when control is Loaded. This event handler will call the same SetValidationRule() method who attached it in the first place, but in that point in time, it will proceed all the way to the binding manipulation.

So the trick here is to fetch the BindingExpression on the target DependencyProperty of the affected control. Keep in mind that a BindingExpression is an underlying object that knows the connection between the binding source and the binding target. On the other hand, a Binding is merely a higher level object in the binding mechanism that contains all the information about the binding source and the nature of the binding, and can be shared across several BindingExpression objects.

Moving on, with our BindingExpression, we then get the Binding object and add our validation rule to it, validating duplicates in the process. And that’s all there is to it.

I know this already leverages the implementation and code required, and most people would be comfortable with this, but we still must set UpdateSourceTrigger on the Binding object, otherwise input validation only occurs when the textbox loses focus. Since others, and myself would have a “mission incomplete” kind of feeling, we’ll move on.

At this moment, you may be thinking in changing the value of UpdateSourceTrigger in the GetRule() method, where the binding object is exposed:

(...)
var binding = expression.ParentBinding;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
(...)

 You could indeed to it, and the app would compile just fine. But at runtime you would get the following exception: “Binding cannot be changed after it has been used”. If you think about it, it makes sense, because when a binding is set, the event wiring is created in order for change notifications to go from source to target and vice-versa. So this means that a new binding object must be created, and then set on the target DependencyProperty. The following variant of the GetRule() method exposes this like of thinking:

static void GetRule(DependencyObject obj, DependencyProperty property)
{
    var control = obj as Control;

    if (!control.IsInitialized)
    {
        RoutedEventHandler callback = null;
        callback = delegate
                       {
                           control.Loaded -= callback;
                           SetValidationRule(obj, property);
                       };
        control.Loaded += callback;
        return;
    }

    var expression = control.GetBindingExpression(property);
    if (expression == null)
        throw new InvalidOperationException("The control's validation property must be bound for the Validator to validate it.");

    var binding = expression.ParentBinding;
    if (binding == null)
        throw new ApplicationException("Property binding expression has no parent binding.");

    /*
     * The TextBox.Text property has a default UpdateSourceTrigger value of LostFocus.
     * This means that the text you type into the TextBox does not update the source
     * until the TextBox loses focus. This causes a runtime exception 'Binding cannot
     * be changed after it has been used'. So we must reset the binding.
    */

    if (binding.UpdateSourceTrigger == UpdateSourceTrigger.Default)
    {
        var bind = new Binding() {
                                     Mode = binding.Mode,
                                     UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
                                     Path = binding.Path,
                                     BindsDirectlyToSource = binding.BindsDirectlyToSource,
                                     Converter = binding.Converter,
                                     ConverterCulture = binding.ConverterCulture,
                                     ConverterParameter = binding.ConverterParameter,
                                     FallbackValue = binding.FallbackValue,
                                     IsAsync = binding.IsAsync,
                                     NotifyOnSourceUpdated = binding.NotifyOnSourceUpdated,
                                     NotifyOnTargetUpdated = binding.NotifyOnTargetUpdated,
                                     NotifyOnValidationError = binding.NotifyOnValidationError,
                                     StringFormat = binding.StringFormat,
                                     TargetNullValue = binding.TargetNullValue,
                                     UpdateSourceExceptionFilter = binding.UpdateSourceExceptionFilter,
                                     ValidatesOnDataErrors = binding.ValidatesOnDataErrors,
                                     ValidatesOnExceptions = binding.ValidatesOnExceptions,
                                     XPath = binding.XPath
                                 };

        if (binding.ElementName == null && binding.RelativeSource == null)
            bind.Source = control.DataContext;
        else if (binding.Source == null && binding.RelativeSource == null)
            bind.ElementName = binding.ElementName;
        else
            bind.RelativeSource = binding.RelativeSource;

        binding = bind;
    }

    ValidationRule mandatoryRule = null;
    foreach (ValidationRule rule in binding.ValidationRules)
    {
        if (rule is MandatoryInputRule)
        {
            if (mandatoryRule == null)
                mandatoryRule = rule as MandatoryInputRule;
            else
                throw new InvalidOperationException("There's more than one MandatoryInputRule in the Binding's ValidationRules.");
        }
    }

    if (mandatoryRule == null)
    {
        mandatoryRule = new MandatoryInputRule();
        binding.ValidationRules.Add(mandatoryRule);
    }

    BindingOperations.ClearBinding(obj, property);
    BindingOperations.SetBinding(obj, TextBox.TextProperty, binding);
}

I’ve purposefully cloned a binding object and set its properties for better understanding and so that all the previous settings could be persisted, except for the origin of all this, the UpdateSourceTrigger property.

In the next article we’ll come out with an even better approach using a Markup Extension. It’s nothing new, but makes life a bit easier for the developer, and more clean and extensible from an architectural point of view.

In case you missed them, here is the previous article:

Wpf Input Validation Part I – Validation Rules

Technorati Tags: , , ,

Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Google Buzz (aka. Google Reader)

WPF Input Validation Part 1: Tutorial and examples

5

I’ve recently set up a sample of a validation mechanism for WPF text input controls on the UI that I thought it would be of general interest due to the “Gokuu approach”* I’ve done with it, otherwise it just wouldn’t make my day :) The requirement is to create a UI control behavior that visually signals validation errors over user input, so when the user submits empty or invalid data on a mandatory field, the input control changes it’s appearance or some kind of action takes place, like a validation message box. Then, it should prevent data submission when a button is clicked or the Enter key is pressed, for instance.

I’m talking about WPF Validation Rules. Validation Rules are objects that apply validation over a data Binding operation, and they are added to the Binding.ValidationRules Collection. They can be created by deriving from the ValidationRule class and overriding its Validate method that should hold the validation logic. Then, normally, you would add the rule to the collection in your xaml code, through property element syntax, or in code.

Although not a part of this article, there’s another validation mechanism built into WPF. You can also test one’s input through an ExceptionValidationRule, which invalidates a value if there are exceptions during source updates of the binding source property. You can even provide custom logic to specify how the binding engine handles these exceptions by using a UpdateSourceExceptionFilterCallback.

These series of articles will focus on creating and applying custom validation rules the most effective way. I’ll discuss this in an incremental way, building up a structured thinking till an acceptable solution is reached.

Moving on… We should then start with a simple WPF application, with it’s main window as our demo canvas. Lets a text control to fetch some user input to validate, say, a TextBox control, and make it data bound to a property that lives on the Window control itself. We’ll set it as it’s own DataContext for that.

<Window x:Class="ValidationSample1.ValidationWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Rules="clr-namespace:ValidationSample1"
    Title="ValidationWindow" Height="200" Width="300">
    <Window.Resources>

    </Window.Resources>
    <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
        <StackPanel Name="ValidationPanel" >
            <Label>Validated Text Box:</Label>
            <TextBox x:Name="ValidatedTextBox"
                     Width="200"/>
            </TextBox>
        </StackPanel>
    </Grid>
</Window>
#region #using Directives

using System.ComponentModel;
using System.Windows;

#endregion

namespace ValidationSample1
{
    /// <summary>
    /// Interaction logic for ValidationWindow.xaml
    /// </summary>
    public partial class ValidationWindow : Window, INotifyPropertyChanged
    {
        public string myProperty;

        public string MyProperty
        {
            get { return myProperty; }
            set
            {
                if (value == myProperty)
                    return;

                myProperty = value;
                OnNotifyPropertyChanged("myProperty");
            }
        }

        public ValidationWindow()
        {
            InitializeComponent();

            DataContext = this;
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

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

        #endregion
    }
}

 Now we need to make sure the user doesn’t submit empty data and visually indicate him that same requirement. For that, we’re going to create a custom ValidationRule to prevent the user from submitting empty text. Like I said, this is done by extending the System.Windows.Controls.ValidationRule class:

#region #using Directives

using System.Globalization;
using System.Windows.Controls;

#endregion

namespace ValidationSample1
{
    public class MandatoryInputRule : ValidationRule
    {
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            if (value != null)
            {
                string input = value as string;

                if (input.Length > 0)
                    return new ValidationResult(true, null);
            }

            return new ValidationResult(false, "Validation error. Field input required.");
        }
    }
}

The next step is to connect the Validation Rule to the binding target. To do so, you can go either the XAML way or in code. I’ll show you the first approach.

<TextBox x:Name="ValidatedTextBox"
         Width="200">
    <TextBox.Text>
        <!--  Textbox notifies changes when Text is changed, and not focus. -->
        <Binding Path="MyProperty" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <!--  Validation rule set to run when binding target is updated. -->
                <Rules:MandatoryInputRule ValidatesOnTargetUpdated="True" />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Notice that our binding object sets the UpdateSourceTrigger property to PropertyChanged. This is needed because TextBox updates it’s source when it loses Focus and as such, the default value of UpdateSourceTrigger for the TextBox is LostFocus. In this example, this isn’t enough. We need to analyze the data as user writes. So the ideal update behavior is to trigger when data, or property changes, hence the UpdateSourceTrigger.PropertyChanged value in the preceding example.

For now, our binding is empty value-aware due to the validation rule we applied. But wait! If we run the app, nothing happens. It still doesn’t manifest visually when user submits no text. We must specify a style behavior for when validations errors occur, the user gets warned through visual changes in the control. So we’ll add a style to the Window Resources collection and set it on the TextBox. The XAML would go like this:

<Window x:Class="ValidationSample1.ValidationWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Rules="clr-namespace:ValidationSample1"
    Title="ValidationWindow" Height="200" Width="300">
    <Window.Resources>
        <Style x:Key="EditableTextBox" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
            <Setter Property="Background" Value="#DDFFDD" />
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <Border BorderBrush="Red" BorderThickness="2">
                            <AdornedElementPlaceholder />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="true">
                    <Setter Property="Background" Value="#FFDDDD"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
        <StackPanel Name="ValidationPanel" >
            <Label>Validated Text Box:</Label>
            <TextBox x:Name="ValidatedTextBox"
                     Width="200"
                     Style="{DynamicResource EditableTextBox}">
                <TextBox.Text>
                    <!--  Textbox notifies changes when Text is changed, and not focus. -->
                    <Binding Path="MyProperty" UpdateSourceTrigger="PropertyChanged">
                        <Binding.ValidationRules>
                            <!--  Validation rule set to run when binding target is updated. -->
                            <Rules:MandatoryInputRule ValidatesOnTargetUpdated="True" />
                        </Binding.ValidationRules>
                    </Binding>
                </TextBox.Text>
            </TextBox>
        </StackPanel>
    </Grid>
</Window>

Now, the user sees a red border when no text is submitted. So the task is complete, but I can’t stop wondering the amount of code needed to add validation to a single control. Imagine an entire form…

The next article will present a new approach to this implementation and will require much less code.

* “Goku approach” is an analogy that means one’s accomplished the basic and most objective functional requirement, but then he can’t stop feeling an urge to improve the implementation whether performance, architectural or usability-wise. So that’s when code tends to get upgraded to the next level, shall we say a super level or “super-sayin”, like in the Dragon Ball anime. And after that, the same urge may arise again, and again, giving way to the typical Goku-powerup pattern that the main character faces along the series when needs to evolve to face his enemies :)

Technorati Tags: , ,

Digg This
Reddit This
Stumble Now!
Buzz This
Vote on DZone
Share on Facebook
Bookmark this on Delicious
Kick It on DotNetKicks.com
Shout it
Share on LinkedIn
Bookmark this on Technorati
Post on Twitter
Google Buzz (aka. Google Reader)

Note: Silverlight, C#, in fact any .NET web development projects is best used with windows hosting than Linux based hosting.