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:












