Saturday, February 25, 2012

WPF : Validation Summary Control

In this post I am going to build a validation summary control like ASP.net and Silverlight.
We will build that in 2 steps. In 1st step we will build a simple control which will have an Itemscontrol and it will list all errors sequentally, and In second step we will write an attached property which will attach any control that wants its error tobe shown in validation summary control.
So lets start with first steps, here we are going to build an usercontrol and will name it as ValidationSummary.
This  control in its XAML will have an ItemsControl which will have its Items property bounded to a list of error message. This control will maintain a Dictionary of controls which is going to be subscribe and wants their messages to be shown in ValidationSummaryControl.  For each of control subscribed ValidationSummayControl will listen for ValidationError and for every error in Validation.GetErrors method it will list errors.
Here is the code for ValidationSummary's XAML view.

<UserControl x:Class="Controls.ValidationSummary"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Name="validationSummary"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <UserControl.Resources>
        <DataTemplate x:Key="ErrorViewerItemTemplate" DataType="string" >
            <StackPanel Orientation="Horizontal">
                <Ellipse Fill="Red" Width="5" Height="5" VerticalAlignment="Center"
             HorizontalAlignment="Center" Margin="5,0,0,0" />
                <TextBlock Text="{Binding}" FontSize="11" FontStyle="Italic"
            Foreground="red" Padding="2" Margin="5,0,0,0"
             HorizontalAlignment="Left" VerticalAlignment="Center" />
            </StackPanel>
        </DataTemplate>
    </UserControl.Resources>
    <Grid>
        <ItemsControl x:Name="itemsControl" 
        ItemTemplate="{StaticResource ErrorViewerItemTemplate}"  />
    </Grid>
</UserControl>

Here is the code for ValidationSummary.cs file.

 
namespace Controls
{
    /// <summary>
    /// Interaction logic for ValidationSummary.xaml
    /// </summary>
    [ExcludeFromCodeCoverage]
    public partial class ValidationSummary : UserControlIErrorViewer
    {
        #region Private variables
 
        private readonly List<DependencyObject> _elements = new List<DependencyObject>();
 
        private readonly Dictionary<FrameworkElementstring> _errorMessages =
            new Dictionary<FrameworkElementstring>();
 
        #endregion
 
        #region Constructor
 
        public ValidationSummary()
        {
            InitializeComponent();
        }
 
        #endregion
 
        #region Methods
 
        public void SetElement(DependencyObject element)
        {
            if (!_elements.Contains(element))
            {
                _elements.Add(element);
                Validation.AddErrorHandler(element, Element_ValidationError);
            }
        }
 
        private void Element_ValidationError(object sender, ValidationErrorEventArgs e)
        {
            GetErrorsRecursive(sender as FrameworkElement, _errorMessages);
        }
 
        private void GetErrorsRecursive(FrameworkElement elem, Dictionary<FrameworkElementstring> errors)
        {
            errors.Remove(elem);
 
            if (Validation.GetHasError(elem))
                foreach (ValidationError error in Validation.GetErrors(elem))
                {
                    errors.Add(elem, error.ErrorContent.ToString());
                    break;
                }
 
            foreach (object child in LogicalTreeHelper.GetChildren(elem))
                if (child is FrameworkElement)
                    GetErrorsRecursive(child as FrameworkElement, errors);
 
            itemsControl.ItemsSource = null;
            itemsControl.ItemsSource = _errorMessages.Values;
        }
 
        #endregion
    }
}

Now let's start with step 2 where we will write an attached property that will attach an element which wants to show error message in above ValidationSummary control.
Here is the code for ValidationSummaryValidator
using System;
using System.Diagnostics.CodeAnalysis;
using System.Windows;
using Prudential.SCT.BPC.FrontEnd.Core.Interfaces.Infrastructure;
 
namespace Prudential.SCT.BPC.FrontEnd.Core.Common
{
    /// <summary>
    /// This class is used to attach ValidationSummary control with controls on View.
    /// </summary>
    [ExcludeFromCodeCoverage]
    public static class ValidationSummaryValidator
    {
        #region "AdornerSite"
 
        /// <summary>
        /// Attached Property : AdornerSite
        /// It will let framework elements who want to be a part of ValidationSummary control, subscribe themselves.
        /// </summary>
        public static DependencyProperty AdornerSiteProperty = DependencyProperty.RegisterAttached("AdornerSite",
                                                                                                   typeof (
                                                                                                       DependencyObject),
                                                                                                   typeof (
                                                                                                       ValidationSummaryValidator
                                                                                                       ),
                                                                                                   new PropertyMetadata(
                                                                                                       null,
                                                                                                       OnAdornerSiteChanged));
 
 
        /// <summary>
        /// Gets Validation Subscribing elements
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        [AttachedPropertyBrowsableForType(typeof (DependencyObject))]
        public static DependencyObject GetAdornerSite(DependencyObject element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }
            return (element.GetValue(AdornerSiteProperty) as DependencyObject);
        }
 
        /// <summary>
        /// Adds elements that want to be a part of Validation summary control
        /// </summary>
        /// <param name="element"></param>
        /// <param name="value"></param>
        public static void SetAdornerSite(DependencyObject element, DependencyObject value)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }
            element.SetValue(AdornerSiteProperty, value);
        }
 
        /// <summary>
        /// CallBack for Adorner site whenever it it set.
        /// </summary>
        /// <param name="d"></param>
        /// <param name="e"></param>
        private static void OnAdornerSiteChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var errorViewer = e.NewValue as IErrorViewer;
            if (errorViewer != null)
            {
                errorViewer.SetElement(d);
            }
        }
 
        #endregion
    }
}

Now lets see how you will bind a text box control with ValidationSummary control. 
First you will add an instance of ValidationSummaryControl in your XAM view as show below

<Controls:ValidationSummary Grid.Row="7" Visibility="Hidden" x:Name="validationSummary" />

Then you will pick up the control which you want to attach in your view, here we are going to pick up a textbox 

<TextBox common:ValidationSummaryValidator.AdornerSite="validationSummary"
  Text="{Binding CustomText,ValidatesOnDataErrors=true,NotifyOnValidationError=True
}"  />

1 comment: