# Friday, 20 February 2009

On Wednesday I posted about coming up with a way of decorating any UIElement with a red border in response to a validation error.  The only drawback (that I could see just then) was that I had to add borders to all the elements that I might want to exhibit this behavior.  Today Shaun suggested that I try it as an attached property instead, which might save that hassle.  Indeed it does.  Because of the way attached properties work in Silverlight, it’s possible to use them to attach behavior as well as data be hooking the value changed event.  Here’s the same (basically) logic as an attached property

public class ValidationService
{
    public static readonly DependencyProperty ValidationBehaviorProperty =
        DependencyProperty.RegisterAttached("ValidationBehavior", typeof(bool), typeof(ValidationService),
                                    new PropertyMetadata(OnValidationBehaviorChanged));

    public static bool GetValidationBehavior(DependencyObject d)
    {
        return (bool)d.GetValue(ValidationBehaviorProperty);
    }

    public static void SetValidationBehavior(DependencyObject d, bool value)
    {
        d.SetValue(ValidationBehaviorProperty, value);
    }

    private static void OnValidationBehaviorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is FrameworkElement)
        {
            FrameworkElement fe = d as FrameworkElement;

            if ((bool)e.OldValue)
            {

            }
            if ((bool)e.NewValue)
            {
                fe.BindingValidationError += new EventHandler<ValidationErrorEventArgs>(fe_BindingValidationError);
            }
        }
    }

    static void fe_BindingValidationError(object sender, ValidationErrorEventArgs e)
    {
        FrameworkElement fe = e.OriginalSource as FrameworkElement;
        DependencyObject parent = fe.Parent;
        Border b = null;

        if (parent is Grid)
        {
            b = new Border() { BorderBrush = new SolidColorBrush(Colors.Red), BorderThickness = new Thickness(0) };

            Grid g = parent as Grid;
            g.Children.Add(b);
            int column = (int)fe.GetValue(Grid.ColumnProperty);
            b.SetValue(Grid.ColumnProperty, column);
            int row = (int)fe.GetValue(Grid.RowProperty);
            b.SetValue(Grid.RowProperty, row);
            g.Children.Remove((UIElement)fe);
            b.Margin = fe.Margin;
            fe.Margin = new Thickness(0);
            b.Child = (UIElement)fe;
        }
        else if (parent is Border)
        {
            b = parent as Border;
        }

        if (e.Action == ValidationErrorEventAction.Added)
        {
            if (b != null)
            {
                b.BorderThickness = new Thickness(1);
            }
            ToolTipService.SetToolTip(fe, new TextBlock() { Text = e.Error.Exception.Message });
        }
        if (e.Action == ValidationErrorEventAction.Removed)
        {
            if (b != null)
            {
                b.BorderThickness = new Thickness(0);
            }
            ToolTipService.SetToolTip(fe, null);
        }
    }
}

Note that when registering the attached property, we register a value changed handler (in this case OnValidationBehaviorChanged).  This will get called the first time the property is set, which gives us an opportunity to mess with the element that the property is attached to.  If the value of the property is being set to true, we hook up the BindingValidationError handler.

In the validation error handler, we have to find out if the element is currently surrounded by a Border, and add one if it isn’t.  Note: in this example it will only work if the element is originally the child of a Grid.  This could easily be modified to support the other Panel types. 

Once the parent is a border, then the old logic from the previous example can be reused to set the border and tooltip on or off as appropriate. 

Notice that we don’t mark the BindingValidationError event as Handled so that it will bubble.  That allows us to write additional forms specific error handling higher up in the visual hierarchy.  At the form level we might want to invalidate the submit button if validation failed, or something similar, while letting the logic in the attached property handle the UI effects.

In the XAML, this new attached property can be used to attached the desired visual effect to any FrameworkElement like so

<TextBox Width="Auto" x:Name="txtNewHours"  Text="{Binding Hours, Mode=TwoWay, Converter={StaticResource hoursConverter}, 
    NotifyOnValidationError=true, ValidatesOnExceptions=true}" 
    my:ValidationService.ValidationBehavior="true"
    Grid.Row="1" Grid.Column="1" Margin="5,5,5,5"/>