Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Catel - Part 4 of n: Unit testing with Catel

, 28 Jan 2011 CPOL
This article explains how to write unit tests for MVVM using Catel.
Catel-04_01-unittesting.zip
src
Catel.Articles.04 - Unit testing
Models
Properties
Settings.settings
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
ViewModels
Windows
Catel.Articles.04 - Unit testing.Test
Models
Properties
UI
ViewModels
Catel.Articles.Base
Data
Attributes
Properties
Settings.settings
Resources
Images
CatenaLogic.png
Preview.png
Run.png
ShowCode.png
UI
Controls
Helpers
ViewModels
Windows
Catel.Core
Attributes
ClassDiagrams
DataObjectBase.cd
SavableDataObjectBase.cd
Collections
Helpers
ComponentModel
Data
Attributes
Exceptions
Interfaces
Diagnostics
Extensions
Helpers
Exceptions
Helpers
IO
Exceptions
IoC
LLBLGen
Log4net
Appender
Extensions
Helpers
MVVM
Commands
Interfaces
Exceptions
Services
EventArgs
Exceptions
Interfaces
ViewModels
Attributes
Interfaces
Properties
Reflection
Exceptions
Extensions
Helpers
Runtime
Serialization
Attributes
Helpers
Security
Cryptography
Helpers
Catel.Examples.Models
Properties
Catel.Examples.PersonApplication
Properties
Settings.settings
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
ViewModels
Windows
Catel.Examples.Silverlight
Properties
Resources
Images
add.png
delete.png
edit.png
group.png
UI
Data
Converters
Pages
ViewModels
Windows
Catel.Examples.Silverlight.Web
Catel.Examples.Silverlight.Web.csproj.user
ClientBin
Properties
Catel.FxCop
Catel.Silverlight
Diagnostics
log4net
Core
MVVM
Commands
Services
ViewModels
Properties
Catel.Core
Catel.Windows
Reflection
Themes
Generic
Assets
Old
Windows
Controls
Data
Converters
Helpers
Helpers
Catel.Silverlight.Test
Properties
Catel.Silverlight.Test.Web
Catel.Silverlight.Test.Web.csproj.user
ClientBin
Properties
Catel.snk
Catel.Templates.WpfApplication
Properties
Settings.settings
UI
Controls
ViewModels
Windows
Catel.Templates.WpfItemTemplates
Properties
UI
Controls
ViewModels
Windows
Catel.Test
Collections
Convert
Data
Helpers
IO
MVVM
UI
ViewModels
Properties
Reflection
Runtime
Serialization
Security
Cryptography
Test References
Catel.Windows.accessor
Windows
Data
Converters
Catel.vsmdi
Catel.Windows
ClassDiagrams
ViewModelBase.cd
Collections
Extensions
Helpers
MVVM
Commands
Services
Test
UI
ViewModels
Properties
Settings.settings
Resources
Images
Add.png
ClearOutput.png
Edit.png
Error.png
Loading.gif
Preview.png
Remove.png
Save.png
TipOfTheDay.png
Warning.png
Themes
Aero
ExpressionDark
Assets
Generic
Assets
Controls
Jetpack
Assets
background.png
Old
SunnyOrange
Assets
Windows
Controls
Extensions
LinkLabel
StackGrid
Data
Converters
Helpers
Documents
Extensions
Extensions
Helpers
Input
Markup
Media
Effects
EmptyEffect
EmptyEffect.fx
EmptyEffect.ps
EmptyEffect.fx
GrayscaleEffect
GrayscaleEffect.fx
GrayscaleEffect.ps
Extensions
Imaging
Extensions
Windows
DataWindow
TipOfTheDay
Local.testsettings
Settings.StyleCop
TraceAndTestImpact.testsettings
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="WarningAndErrorValidator.cs" company="Catel development team">
//   Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
//   Business validation type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

// Use routed events if you are NOT using MVVM and want to show errors
#define USE_ROUTED_EVENTS

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using Catel.ComponentModel;

namespace Catel.Windows.Controls
{
    #region Enum
    /// <summary>
    /// Business validation type.
    /// </summary>
    public enum ValidationType
    {
        /// <summary>
        /// Warning.
        /// </summary>
        Warning,

        /// <summary>
        /// Error.
        /// </summary>
        Error
    }

    /// <summary>
    /// Validation event action.
    /// </summary>
    public enum ValidationEventAction
    {
        /// <summary>
        /// Added.
        /// </summary>
        Added,

        /// <summary>
        /// Removed.
        /// </summary>
        Removed,

        /// <summary>
        /// All validation info of the specified object should be cleared.
        /// </summary>
        ClearAll
    }
    #endregion

    /// <summary>
    /// Control for adding business rule validation to the form. Assign a value or binding to source for the business object or 
    /// collection of bussiness objects to validate.
    /// </summary>
    public class WarningAndErrorValidator : Control
    {
        #region Variables
        /// <summary>
        /// List of objects that are currently being validated. 
        /// </summary>
        private readonly Dictionary<object, ValidationData> _objectValidation = new Dictionary<object, ValidationData>();
        private readonly object _objectValidationLock = new object();
        #endregion

        #region Constructor & destructor
        /// <summary>
        /// Initializes static members of the <see cref="WarningAndErrorValidator"/> class.
        /// </summary>
        static WarningAndErrorValidator()
        {
#if !SILVERLIGHT && USE_ROUTED_EVENTS
            ValidationEvent = EventManager.RegisterRoutedEvent("ValidationEvent", RoutingStrategy.Bubble, typeof(EventHandler<ValidationEventArgs>), typeof(WarningAndErrorValidator));
#endif
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WarningAndErrorValidator"/> class.
        /// </summary>
        public WarningAndErrorValidator()
        {
#if !SILVERLIGHT
            Focusable = false;
#endif

            Loaded += delegate { Initialize(); };
            Unloaded += delegate { CleanUp(); };
        }
        #endregion

        #region Properties
        /// <summary>
        /// Source for validation. This can be an business object which implements <see cref="IDataErrorInfo"/> 
        /// and <see cref="INotifyPropertyChanged"/> or an <see cref="IEnumerable"/> containing bussiness objects.
        /// In case of a <see cref="IEnumerable"/> then the content should be static or the interface <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/>.
        /// </summary>
        /// <remarks>
        /// Wrapper for the Source dependency property.
        /// </remarks>
        public object Source
        {
            get { return GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }

        /// <summary>
        /// DependencyProperty definition as the backing store for Source.
        /// </summary>
        public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(object), typeof(WarningAndErrorValidator), 
            new PropertyMetadata(null, (sender, e) => ((WarningAndErrorValidator)sender).UpdateSource(e.OldValue, e.NewValue)));
        #endregion

        #region Events
#if SILVERLIGHT && USE_ROUTED_EVENTS
        /// <summary>
        /// Occurs when validation is triggered.
        /// </summary>
        public event EventHandler<ValidationEventArgs> Validation;
#else
        /// <summary>
        /// Routed event for handling warnings.
        /// </summary>
        public static RoutedEvent ValidationEvent { get; private set; }
#endif
        #endregion

        #region Methods
        /// <summary>
        /// Initializes this instance. Loads all the errors and warnings that were added when the control was not yet loaded.s
        /// </summary>
        private void Initialize()
        {
            if (Source != null)
            {
                UpdateSource(null, Source);
            }
        }

        /// <summary>
        /// Cleans up.
        /// </summary>
        private void CleanUp()
        {
            List<object> objects = new List<object>();

            lock (_objectValidationLock)
            {
                objects.AddRange(_objectValidation.Keys);
            }

            foreach (object obj in objects)
            {
                if (obj is IEnumerable)
                {
                    RemoveObjectsFromWatchList(obj as IEnumerable);
                }
                else if (obj is INotifyPropertyChanged)
                {
                    RemoveObjectFromWatchList(obj);
                }
            }

            _objectValidation.Clear();
        }

        /// <summary>
        /// Updates the source.
        /// </summary>
        /// <param name="oldValue">The old value.</param>
        /// <param name="newValue">The new value.</param>
        private void UpdateSource(object oldValue, object newValue)
        {
            if (oldValue is IEnumerable)
            {
                RemoveObjectsFromWatchList(oldValue as IEnumerable);
            }
            else if (oldValue is INotifyPropertyChanged)
            {
                RemoveObjectFromWatchList(oldValue);
            }

#if !SILVERLIGHT
            if (!IsLoaded)
            {
                return;
            }
#endif

            if (newValue is IEnumerable)
            {
                AddObjectsToWatchList(newValue as IEnumerable, newValue as IEnumerable);
            }
            else if (newValue is INotifyPropertyChanged)
            {
                AddObjectToWatchList(newValue);
            }
        }

        /// <summary>
        /// Adds an <see cref="IEnumerable"/> of objects to the watch list.
        /// </summary>
        /// <param name="values">The values to add to the watch list.</param>
        /// <param name="parentEnumerable">The parent enumerable. <c>Null</c> if the object does not belong to an enumerable.</param>
        private void AddObjectsToWatchList(IEnumerable values, IEnumerable parentEnumerable)
        {
            foreach (object value in values)
            {
                AddObjectToWatchList(value, parentEnumerable);
            }

            // Supports IObservableCollection through INotifyCollectionChanged and support IEntityCollectionCore 
            INotifyCollectionChanged iNotifyCollectionChanged = values as INotifyCollectionChanged;
            if (iNotifyCollectionChanged != null)
            {
                iNotifyCollectionChanged.CollectionChanged += iNotifyCollectionChanged_CollectionChanged;

                AddObjectToWatchList(parentEnumerable);
            }
        }

        /// <summary>
        /// Adds the object to the watch list.
        /// </summary>
        /// <param name="value">The object to add to the watch list.</param>
        private void AddObjectToWatchList(object value)
        {
            AddObjectToWatchList(value, null);
        }

        /// <summary>
        /// Adds the object to the watch list.
        /// </summary>
        /// <param name="value">The object to add to the watch list.</param>
        /// <param name="parentEnumerable">The parent enumerable. <c>Null</c> if the object does not belong to an enumerable.</param>
        private void AddObjectToWatchList(object value, IEnumerable parentEnumerable)
        {
            if (value == null)
            {
                return;
            }

            lock (_objectValidationLock)
            {
                if (!_objectValidation.ContainsKey(value))
                {
                    INotifyPropertyChanged iNotifyPropertyChanged = value as INotifyPropertyChanged;
                    if (iNotifyPropertyChanged != null)
                    {
                        iNotifyPropertyChanged.PropertyChanged += iNotifyPropertyChanged_PropertyChanged;
                    }
                }
            }

            CheckObjectValidation(value, null, parentEnumerable);
        }

        /// <summary>
        /// Removes an <see cref="IEnumerable"/> of objects from the watch list.
        /// </summary>
        /// <param name="values">The values to remove from the watch list.</param>
        private void RemoveObjectsFromWatchList(IEnumerable values)
        {
            foreach (object value in values)
            {
                RemoveObjectFromWatchList(value);
            }

            // Supports IObservableCollection through INotifyCollectionChanged and support IEntityCollectionCore 
            INotifyCollectionChanged iNotifyCollectionChanged = values as INotifyCollectionChanged;
            if (iNotifyCollectionChanged != null)
            {
                iNotifyCollectionChanged.CollectionChanged -= iNotifyCollectionChanged_CollectionChanged;

                RemoveObjectFromWatchList(values);
            }
        }

        /// <summary>
        /// Removes the object from watch list.
        /// </summary>
        /// <param name="value">The object to remove from the watch list.</param>
        private void RemoveObjectFromWatchList(object value)
        {
            if (value == null)
            {
                return;
            }

            INotifyPropertyChanged iNotifyPropertyChanged = value as INotifyPropertyChanged;
            if (iNotifyPropertyChanged != null)
            {
                iNotifyPropertyChanged.PropertyChanged -= iNotifyPropertyChanged_PropertyChanged;
            }

            RaiseBusinessValidationWarningOrError(value, string.Empty, ValidationEventAction.ClearAll, ValidationType.Warning);
            RaiseBusinessValidationWarningOrError(value, string.Empty, ValidationEventAction.ClearAll, ValidationType.Error);

            lock (_objectValidationLock)
            {
                _objectValidation.Remove(value);
            }
        }

        /// <summary>
        /// Checks a entity that either implements the <see cref="IDataWarningInfo"/> or <see cref="IDataErrorInfo"/> on warnings and errors.
        /// </summary>
        /// <param name="value">The object to check.</param>
        /// <param name="propertyChanged">The propery that has been changed. <c>null</c> if no specific property has changed.</param>
        /// <param name="parentEnumerable">The parent enumerable. <c>Null</c> if the object does not belong to an enumerable.</param>
        /// <remarks>
        /// Internally calls the generic method with the same name.
        /// </remarks>
        private void CheckObjectValidation(object value, string propertyChanged, IEnumerable parentEnumerable)
        {
            ValidationData currentValidationData;
            ValidationData oldValidationData;

            if (value == null)
            {
                return;
            }

            lock (_objectValidationLock)
            {
                if (!_objectValidation.ContainsKey(value))
                {
                    _objectValidation.Add(value, new ValidationData(parentEnumerable));
                }

                currentValidationData = _objectValidation[value];

                oldValidationData = (ValidationData)currentValidationData.Clone();
            }

            #region Warnings - fields
            CheckObjectValidationForFields(value, propertyChanged, currentValidationData.FieldWarnings, ValidationType.Warning);
            #endregion

            #region Warnings - business
            currentValidationData.BusinessWarnings.Clear();

            string businessWarning = GetWarningOrError(value, ValidationType.Warning);
            if (!string.IsNullOrEmpty(businessWarning))
            {
                currentValidationData.BusinessWarnings.Add(new BusinessWarningOrErrorInfo(businessWarning));
            }
            #endregion

            #region Errors - fields
            CheckObjectValidationForFields(value, propertyChanged, currentValidationData.FieldErrors, ValidationType.Error);
            #endregion

            #region Errors - business
            currentValidationData.BusinessErrors.Clear();

            string businessError = GetWarningOrError(value, ValidationType.Error);
            if (!string.IsNullOrEmpty(businessError))
            {
                currentValidationData.BusinessErrors.Add(new BusinessWarningOrErrorInfo(businessError));
            }
            #endregion

            RaiseEventsForDifferences(value, oldValidationData, currentValidationData);
        }

        /// <summary>
        /// Checks the object validation for fields warnings or errors.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="propertyChanged">The property changed.</param>
        /// <param name="infoList">The info list containing the warning or error info.</param>
        /// <param name="validationType">Type of the validation.</param>
        private static void CheckObjectValidationForFields(object value, string propertyChanged, ObservableCollection<FieldWarningOrErrorInfo> infoList,
            ValidationType validationType)
        {
            if (string.IsNullOrEmpty(propertyChanged))
            {
                infoList.Clear();
            }
            else
            {
                for (int i = 0; i < infoList.Count; i++)
                {
                    if (string.Compare(infoList[i].Field, propertyChanged) == 0)
                    {
                        infoList.RemoveAt(i);
                    }
                }
            }

            Dictionary<string, string> fieldWarningsOrErrors = CheckFieldWarningsOrErrors(value, propertyChanged, validationType);
            foreach (KeyValuePair<string, string> fieldWarningOrError in fieldWarningsOrErrors)
            {
                FieldWarningOrErrorInfo fieldWarningOrErrorInfo = new FieldWarningOrErrorInfo(fieldWarningOrError.Key, fieldWarningOrError.Value);
                if (!infoList.Contains(fieldWarningOrErrorInfo))
                {
                    infoList.Add(new FieldWarningOrErrorInfo(fieldWarningOrError.Key, fieldWarningOrError.Value));
                }
            }
        }

        /// <summary>
        /// Checks the field warnings or errors.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="propertyChanged">The property changed.</param>
        /// <param name="validationType">Type of the validation.</param>
        /// <returns>
        /// List of warnings or errors returned by the object.
        /// </returns>
        private static Dictionary<string, string> CheckFieldWarningsOrErrors(object value, string propertyChanged, ValidationType validationType)
        {
            Dictionary<string, string> warningsOrErrors = new Dictionary<string, string>();

            IDataWarningInfo iDataWarningInfo = value as IDataWarningInfo;
            if ((validationType == ValidationType.Warning) && (iDataWarningInfo == null))
            {
                return warningsOrErrors;
            }

            IDataErrorInfo iDataErrorInfo = value as IDataErrorInfo;
            if ((validationType == ValidationType.Error) && (iDataErrorInfo == null))
            {
                return warningsOrErrors;
            }

            List<string> propertiesToCheck = new List<string>();
            if (!string.IsNullOrEmpty(propertyChanged))
            {
                propertiesToCheck.Add(propertyChanged);
            }
            else
            {
                Type type = value.GetType();
                PropertyInfo[] properties = type.GetProperties();
                foreach (PropertyInfo property in properties)
                {
                    propertiesToCheck.Add(property.Name);
                }
            }

            foreach (string property in propertiesToCheck)
            {
                string warningOrError = string.Empty;
                switch (validationType)
                {
                    case ValidationType.Warning:
                        warningOrError = iDataWarningInfo[property];
                        break;

                    case ValidationType.Error:
                        warningOrError = iDataErrorInfo[property];
                        break;
                }

                if (!string.IsNullOrEmpty(warningOrError))
                {
                    warningsOrErrors[property] = warningOrError;
                }
            }

            return warningsOrErrors;
        }

        /// <summary>
        /// Gets the warning or error message for the object.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="type">The type.</param>
        /// <returns>
        /// Warning or error message formatted for the object.
        /// </returns>
        private static string GetWarningOrError(object value, ValidationType type)
        {
            string message = null;

            switch (type)
            {
                case ValidationType.Warning:
                    if (value is IDataWarningInfo)
                    {
                        message = ((IDataWarningInfo)value).Warning;
                    }
                    break;

                case ValidationType.Error:
                    if (value is IDataErrorInfo)
                    {
                        message = ((IDataErrorInfo)value).Error;
                    }
                    break;
            }

            return !string.IsNullOrEmpty(message) ? message : null;
        }

        /// <summary>
        /// Raises the events for differences.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="oldValidationData">The old validation data.</param>
        /// <param name="newValidationData">The new validation data.</param>
        private void RaiseEventsForDifferences(object value, ValidationData oldValidationData, ValidationData newValidationData)
        {
            #region Warnings - fields
            RaiseEventsForDifferencesInFields(value, oldValidationData.FieldWarnings, newValidationData.FieldWarnings, ValidationType.Warning);
            #endregion

            #region Warnings - business
            RaiseEventsForDifferencesInBusiness(value, oldValidationData.BusinessWarnings, newValidationData.BusinessWarnings, ValidationType.Warning);
            #endregion

            #region Errors - fields
            RaiseEventsForDifferencesInFields(value, oldValidationData.FieldErrors, newValidationData.FieldErrors, ValidationType.Error);
            #endregion

            #region Errors - business
            RaiseEventsForDifferencesInBusiness(value, oldValidationData.BusinessErrors, newValidationData.BusinessErrors, ValidationType.Error);
            #endregion
        }

        /// <summary>
        /// Raises the events for differences in fields.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="oldFieldData">The old field data.</param>
        /// <param name="newFieldData">The new field data.</param>
        /// <param name="validationType">Type of the validation.</param>
        private void RaiseEventsForDifferencesInFields(object value, ICollection<FieldWarningOrErrorInfo> oldFieldData,
            ICollection<FieldWarningOrErrorInfo> newFieldData, ValidationType validationType)
        {
            foreach (FieldWarningOrErrorInfo info in oldFieldData)
            {
                if (!newFieldData.Contains(info))
                {
                    RaiseBusinessValidationWarningOrError(value, info.Message, ValidationEventAction.Removed, validationType);
                }
            }

            foreach (FieldWarningOrErrorInfo info in newFieldData)
            {
                if (!oldFieldData.Contains(info))
                {
                    RaiseBusinessValidationWarningOrError(value, info.Message, ValidationEventAction.Added, validationType);
                }
            }
        }

        /// <summary>
        /// Raises the events for differences in business.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="oldBusinessData">The old business data.</param>
        /// <param name="newBusinessData">The new business data.</param>
        /// <param name="validationType">Type of the validation.</param>
        private void RaiseEventsForDifferencesInBusiness(object value, ICollection<BusinessWarningOrErrorInfo> oldBusinessData,
            ICollection<BusinessWarningOrErrorInfo> newBusinessData, ValidationType validationType)
        {
            foreach (BusinessWarningOrErrorInfo info in oldBusinessData)
            {
                if (!newBusinessData.Contains(info))
                {
                    RaiseBusinessValidationWarningOrError(value, info.Message, ValidationEventAction.Removed, validationType);
                }
            }

            foreach (BusinessWarningOrErrorInfo info in newBusinessData)
            {
                if (!oldBusinessData.Contains(info))
                {
                    RaiseBusinessValidationWarningOrError(value, info.Message, ValidationEventAction.Added, validationType);
                }
            }
        }

        /// <summary>
        /// Raises an validation warning or error event.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="message">A message.</param>
        /// <param name="action">A action for handling the error event.</param>
        /// <param name="type">The type.</param>
        private void RaiseBusinessValidationWarningOrError(object value, string message, ValidationEventAction action, ValidationType type)
        {
#if !SILVERLIGHT && USE_ROUTED_EVENTS
            ValidationRule validationRule = null;
            switch (type)
            {
                case ValidationType.Warning:
                    validationRule = new BusinessWarningValidationRule();
                    break;

                case ValidationType.Error:
                    validationRule = new BusinessErrorValidationRule();
                    break;
            }

            ValidationError validationError = new ValidationError(validationRule, value);
            validationError.ErrorContent = message;

            ValidationEventArgs newEventArgs = new ValidationEventArgs(validationError, action, type);
            RaiseEvent(newEventArgs);
#else
            ValidationEventArgs newEventArgs = new ValidationEventArgs(value, message, action, type);
            if (Validation != null)
            {
                Validation(this, newEventArgs);
            }
#endif
        }

        /// <summary>
        /// Handling changes of properties within entity.
        /// </summary>
        /// <param name="sender">A sender.</param>
        /// <param name="e">The event args.</param>
        private void iNotifyPropertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            CheckObjectValidation(sender, e.PropertyName, null);
        }

        /// <summary>
        /// Handling change of collection updating connections and error messages.
        /// </summary>
        /// <param name="sender">A sender.</param>
        /// <param name="e">Event args.</param>
        private void iNotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            // If the action is "reset", no OldItems will be available, so clear all items manually
            if (e.Action == NotifyCollectionChangedAction.Reset)
            {
                Collection<object> itemsToRemove = new Collection<object>();
                lock (_objectValidationLock)
                {
                    foreach (KeyValuePair<object, ValidationData> validationData in _objectValidation)
                    {
                        if (validationData.Value.ParentEnumerable == sender)
                        {
                            itemsToRemove.Add(validationData.Key);
                        }
                    }
                }

                // Remove the items that should be removed (outside the lock)
                foreach (object itemToRemove in itemsToRemove)
                {
                    RemoveObjectFromWatchList(itemToRemove);
                }

                return;
            }

            IEnumerable newItems = e.NewItems;
            IEnumerable oldItems = e.OldItems;

            if (oldItems != null)
            {
                RemoveObjectsFromWatchList(oldItems);
            }

            if (newItems != null)
            {
                AddObjectsToWatchList(newItems, sender as IEnumerable);
            }
        }

#if !SILVERLIGHT && USE_ROUTED_EVENTS
        /// <summary>
        /// Add an handler to routed event for validation events.
        /// </summary>
        /// <param name="element">A dependency object element.</param>
        /// <param name="handler">A handler.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="element"/> is <c>null</c>.</exception>
        public static void AddValidationHandler(DependencyObject element, EventHandler<ValidationEventArgs> handler)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            UIElement element2 = element as UIElement;
            if (element2 != null)
            {
                element2.AddHandler(ValidationEvent, handler);
            }
            else
            {
                ContentElement contentElement = element as ContentElement;
                if (contentElement != null)
                {
                    contentElement.AddHandler(ValidationEvent, handler);
                }
                else
                {
                    UIElement3D elementd = element as UIElement3D;
                    if (elementd == null)
                    {
                        throw new ArgumentException(string.Format("Invalid_IInputElement {0}", element.GetType()));
                    }

                    elementd.AddHandler(ValidationEvent, handler);
                }
            }
        }

        /// <summary>
        /// Removes the validation handler.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="handler">The handler.</param>
        /// <exception cref="ArgumentNullException">when <paramref name="element"/> is <c>null</c>.</exception>
        public static void RemoveValidationHandler(DependencyObject element, EventHandler<ValidationEventArgs> handler)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            UIElement element2 = element as UIElement;
            if (element2 != null)
            {
                element2.RemoveHandler(ValidationEvent, handler);
            }
            else
            {
                ContentElement contentElement = element as ContentElement;
                if (contentElement != null)
                {
                    contentElement.RemoveHandler(ValidationEvent, handler);
                }
                else
                {
                    UIElement3D elementd = element as UIElement3D;
                    if (elementd == null)
                    {
                        throw new ArgumentException(string.Format("Invalid_IInputElement {0}", element.GetType()));
                    }

                    elementd.RemoveHandler(ValidationEvent, handler);
                }
            }
        }
#endif
        #endregion
    }

    #region Data classes
    /// <summary>
    /// Class containing all validation info (thus warnings and errors) about a specific object.
    /// </summary>
    internal class ValidationData
    {
        #region Constructor & destructor
        /// <summary>
        /// Initializes a new instance of the <see cref="ValidationData"/> class.
        /// </summary>
        /// <param name="parentEnumerable">The parent ParentEnumerable. <c>Null</c> if the object does not belong to an enumerable.</param>
        public ValidationData(IEnumerable parentEnumerable)
        {
            FieldWarnings = new ObservableCollection<FieldWarningOrErrorInfo>();
            BusinessWarnings = new ObservableCollection<BusinessWarningOrErrorInfo>();
            FieldErrors = new ObservableCollection<FieldWarningOrErrorInfo>();
            BusinessErrors = new ObservableCollection<BusinessWarningOrErrorInfo>();

            ParentEnumerable = parentEnumerable;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the parent enumerable.
        /// </summary>
        /// <value>The parent enumerable.</value>
        public IEnumerable ParentEnumerable { get; private set; }

        /// <summary>
        /// Gets the field warnings.
        /// </summary>
        /// <value>The field warnings.</value>
        public ObservableCollection<FieldWarningOrErrorInfo> FieldWarnings { get; private set; }

        /// <summary>
        /// Gets the business warnings.
        /// </summary>
        /// <value>The business warnings.</value>
        public ObservableCollection<BusinessWarningOrErrorInfo> BusinessWarnings { get; private set; }

        /// <summary>
        /// Gets the field errors.
        /// </summary>
        /// <value>The field errors.</value>
        public ObservableCollection<FieldWarningOrErrorInfo> FieldErrors { get; private set; }

        /// <summary>
        /// Gets the business errors.
        /// </summary>
        /// <value>The business errors.</value>
        public ObservableCollection<BusinessWarningOrErrorInfo> BusinessErrors { get; private set; }
        #endregion

        #region Methods
        /// <summary>
        /// Clears the warnings and errors.
        /// </summary>
        public void ClearWarningsAndErrors()
        {
            BusinessWarnings.Clear();
            FieldWarnings.Clear();
            BusinessErrors.Clear();
            FieldErrors.Clear();
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        public object Clone()
        {
            ValidationData validationData = new ValidationData(ParentEnumerable);

            validationData.FieldWarnings = new ObservableCollection<FieldWarningOrErrorInfo>(FieldWarnings);
            validationData.BusinessWarnings = new ObservableCollection<BusinessWarningOrErrorInfo>(BusinessWarnings);
            validationData.FieldErrors = new ObservableCollection<FieldWarningOrErrorInfo>(FieldErrors);
            validationData.BusinessErrors = new ObservableCollection<BusinessWarningOrErrorInfo>(BusinessErrors);

            return validationData;
        }
        #endregion
    }

    /// <summary>
    /// Information class about business warnings and errors.
    /// </summary>
    internal class BusinessWarningOrErrorInfo
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="BusinessWarningOrErrorInfo"/> class.
        /// </summary>
        /// <param name="message">The message.</param>
        public BusinessWarningOrErrorInfo(string message)
        {
            Message = message;
        }

        #region Properties
        /// <summary>
        /// Gets the message.
        /// </summary>
        /// <value>The message.</value>
        public string Message { get; private set; }
        #endregion

        #region Methods
        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        /// 	<c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">
        /// The <paramref name="obj"/> parameter is null.
        /// </exception>
        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            if (obj.GetType() != GetType())
            {
                return false;
            }

            if (obj.GetHashCode() != GetHashCode())
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return string.Format(CultureInfo.InvariantCulture, "{0}", Message).GetHashCode();
        }
        #endregion
    }

    /// <summary>
    /// Information class about field warnings and errors.
    /// </summary>
    internal class FieldWarningOrErrorInfo
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="FieldWarningOrErrorInfo"/> class.
        /// </summary>
        /// <param name="field">The field.</param>
        /// <param name="message">The message.</param>
        public FieldWarningOrErrorInfo(string field, string message)
        {
            Field = field;
            Message = message;
        }

        #region Properties
        /// <summary>
        /// Gets the field.
        /// </summary>
        /// <value>The field.</value>
        public string Field { get; private set; }

        /// <summary>
        /// Gets the message.
        /// </summary>
        /// <value>The message.</value>
        public string Message { get; private set; }
        #endregion

        #region Methods
        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        /// 	<c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">
        /// The <paramref name="obj"/> parameter is null.
        /// </exception>
        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            if (obj.GetType() != GetType())
            {
                return false;
            }

            if (obj.GetHashCode() != GetHashCode())
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            return string.Format(CultureInfo.InvariantCulture, "{0}|{1}", Field, Message).GetHashCode();
        }
        #endregion
    }
    #endregion

    #region Validation rules
#if !SILVERLIGHT
    /// <summary>
    /// Validation rule class for handling bussiness warning validation.
    /// </summary>
    public class BusinessWarningValidationRule : ValidationRule
    {
        /// <summary>
        /// When overridden in a derived class, performs validation checks on a value.
        /// </summary>
        /// <param name="value">The value from the binding target to check.</param>
        /// <param name="cultureInfo">The culture to use in this rule.</param>
        /// <returns>
        /// A <see cref="T:System.Windows.Controls.ValidationResult"/> object.
        /// </returns>
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            IDataWarningInfo info = (IDataWarningInfo)value;
            string message = info.Warning;

            return (!string.IsNullOrEmpty(message)) ? new ValidationResult(false, message) : ValidationResult.ValidResult;
        }
    }

    /// <summary>
    /// Validation rule class for handling bussiness error validation.
    /// </summary>
    public class BusinessErrorValidationRule : ValidationRule
    {
        /// <summary>
        /// When overridden in a derived class, performs validation checks on a value.
        /// </summary>
        /// <param name="value">The value from the binding target to check.</param>
        /// <param name="cultureInfo">The culture to use in this rule.</param>
        /// <returns>
        /// A <see cref="T:System.Windows.Controls.ValidationResult"/> object.
        /// </returns>
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            IDataErrorInfo info = (IDataErrorInfo)value;
            string message = info.Error;

            return (!string.IsNullOrEmpty(message)) ? new ValidationResult(false, message) : ValidationResult.ValidResult;
        }
    }
#endif
    #endregion

    #region Event args
    /// <summary>
    /// Event arguments for event <see cref="WarningAndErrorValidator"/> Validation.
    /// </summary>
    public class ValidationEventArgs : 
#if !SILVERLIGHT && USE_ROUTED_EVENTS
        RoutedEventArgs
#else
        EventArgs
#endif
    {
        #region Variables
        #endregion

        #region Constructor & destructor
#if !SILVERLIGHT && USE_ROUTED_EVENTS
        /// <summary>
        /// Initializes a new instance of the <see cref="ValidationEventArgs"/> class.
        /// </summary>
        /// <param name="validationError">A validation error.</param>
        /// <param name="action">An action causes the event.</param>
        /// <param name="type">The type.</param>
        internal ValidationEventArgs(ValidationError validationError, ValidationEventAction action, ValidationType type)
        {
            RoutedEvent = WarningAndErrorValidator.ValidationEvent;

            Error = validationError;
            Action = action;
            Type = type;
        }
#else
        /// <summary>
        /// Initializes a new instance of the <see cref="ValidationEventArgs"/> class.
        /// </summary>
        /// <param name="value">The value that contains the warning or error.</param>
        /// <param name="message">The actual warning or error message.</param>
        /// <param name="action">The action of the validation event.</param>
        /// <param name="type">The type of validation.</param>
        internal ValidationEventArgs(object value, string message, ValidationEventAction action, ValidationType type)
        {
            Value = value;
            Message = message;
            Action = action;
            Type = type;
        }
#endif
        #endregion

        #region Properties
#if !SILVERLIGHT && USE_ROUTED_EVENTS
        /// <summary>
        /// An error.
        /// </summary>
        public ValidationError Error { get; private set; }
#else
        /// <summary>
        /// Gets the value that contains the warning or error.
        /// </summary>
        /// <value>The value that contains the warning or error.</value>
        public object Value { get; private set; }

        /// <summary>
        /// Gets the actual warning or error message.
        /// </summary>
        /// <value>The message.</value>
        public string Message { get; private set; }
#endif

        /// <summary>
        /// A action for handling event.
        /// </summary>
        public ValidationEventAction Action { get; private set; }

        /// <summary>
        /// Gets the type of the validation.
        /// </summary>
        /// <value>The type of the validation.</value>
        public ValidationType Type { get; private set; }
        #endregion

        #region Methods
#if !SILVERLIGHT && USE_ROUTED_EVENTS
        /// <summary>
        /// Invokes the event.
        /// </summary>
        /// <param name="genericHandler">A handler.</param>
        /// <param name="genericTarget">A target.</param>
        protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget)
        {
            EventHandler<ValidationEventArgs> handler = (EventHandler<ValidationEventArgs>)genericHandler;
            handler(genericTarget, this);
        }
#endif
        #endregion
    }
    #endregion
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Geert van Horrik
Software Developer CatenaLogic
Netherlands Netherlands
I am Geert van Horrik, and I have studied Computer Science in the Netherlands.
 
I love to write software using .NET (especially the combination of WPF and C#). I am also the lead developer of Catel, an open-source application development framework for WPF, Silverlight, WP7 and WinRT with the focus on MVVM.
 
I have my own company since January 1st 2007, called CatenaLogic. This company develops commercial and non-commercial software.
 
To download (or buy) applications I have written, visit my website: http://www.catenalogic.com
Follow on   Twitter

| Advertise | Privacy | Mobile
Web01 | 2.8.141022.2 | Last Updated 28 Jan 2011
Article Copyright 2011 by Geert van Horrik
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid