PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Catel.Windows35/Windows/Controls/WarningAndErrorValidator.cs

#
C# | 1003 lines | 580 code | 122 blank | 301 comment | 77 complexity | 0b298fcc91bf6b4fd39fabad88049553 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. // --------------------------------------------------------------------------------------------------------------------
  2. // <copyright file="WarningAndErrorValidator.cs" company="Catel development team">
  3. // Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
  4. // </copyright>
  5. // <summary>
  6. // Business validation type.
  7. // </summary>
  8. // --------------------------------------------------------------------------------------------------------------------
  9. namespace Catel.Windows.Controls
  10. {
  11. using System;
  12. using System.Collections;
  13. using System.Collections.Generic;
  14. using System.Collections.ObjectModel;
  15. using System.Collections.Specialized;
  16. using System.ComponentModel;
  17. using System.Globalization;
  18. using System.Reflection;
  19. using System.Windows;
  20. using System.Windows.Controls;
  21. using Collections.ObjectModel;
  22. #region Enum
  23. /// <summary>
  24. /// Business validation type.
  25. /// </summary>
  26. public enum ValidationType
  27. {
  28. /// <summary>
  29. /// Warning.
  30. /// </summary>
  31. Warning,
  32. /// <summary>
  33. /// Error.
  34. /// </summary>
  35. Error
  36. }
  37. /// <summary>
  38. /// Validation event action.
  39. /// </summary>
  40. public enum ValidationEventAction
  41. {
  42. /// <summary>
  43. /// Added.
  44. /// </summary>
  45. Added,
  46. /// <summary>
  47. /// Removed.
  48. /// </summary>
  49. Removed,
  50. /// <summary>
  51. /// Refresh the validation, don't add or remove.
  52. /// </summary>
  53. Refresh,
  54. /// <summary>
  55. /// All validation info of the specified object should be cleared.
  56. /// </summary>
  57. ClearAll
  58. }
  59. #endregion
  60. /// <summary>
  61. /// Control for adding business rule validation to the form. Assign a value or binding to source for the business object or
  62. /// collection of bussiness objects to validate.
  63. /// </summary>
  64. public class WarningAndErrorValidator : Control
  65. {
  66. #region Variables
  67. /// <summary>
  68. /// List of objects that are currently being validated.
  69. /// </summary>
  70. private readonly Dictionary<object, ValidationData> _objectValidation = new Dictionary<object, ValidationData>();
  71. private readonly object _objectValidationLock = new object();
  72. #endregion
  73. #region Constructor & destructor
  74. /// <summary>
  75. /// Initializes a new instance of the <see cref="WarningAndErrorValidator"/> class.
  76. /// </summary>
  77. public WarningAndErrorValidator()
  78. {
  79. #if !SILVERLIGHT
  80. Focusable = false;
  81. #endif
  82. Loaded += OnLoaded;
  83. Unloaded += OnUnloaded;
  84. }
  85. #endregion
  86. #region Properties
  87. /// <summary>
  88. /// Source for validation. This can be an business object which implements <see cref="IDataErrorInfo"/>
  89. /// and <see cref="INotifyPropertyChanged"/> or an <see cref="IEnumerable"/> containing bussiness objects.
  90. /// In case of a <see cref="IEnumerable"/> then the content should be static or the interface <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/>.
  91. /// </summary>
  92. /// <remarks>
  93. /// Wrapper for the Source dependency property.
  94. /// </remarks>
  95. public object Source
  96. {
  97. get { return GetValue(SourceProperty); }
  98. set { SetValue(SourceProperty, value); }
  99. }
  100. /// <summary>
  101. /// DependencyProperty definition as the backing store for Source.
  102. /// </summary>
  103. public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(object), typeof(WarningAndErrorValidator),
  104. new PropertyMetadata(null, (sender, e) => ((WarningAndErrorValidator)sender).UpdateSource(e.OldValue, e.NewValue)));
  105. #endregion
  106. #region Events
  107. /// <summary>
  108. /// Occurs when validation is triggered.
  109. /// </summary>
  110. public event EventHandler<ValidationEventArgs> Validation;
  111. #endregion
  112. #region Methods
  113. /// <summary>
  114. /// Called when the control is loaded.
  115. /// </summary>
  116. /// <param name="sender">The sender.</param>
  117. /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
  118. private void OnLoaded(object sender, EventArgs e)
  119. {
  120. Initialize();
  121. #if SILVERLIGHT
  122. RaiseEventsForAllErrorsAndWarnings();
  123. #endif
  124. }
  125. /// <summary>
  126. /// Called when the control is unloaded.
  127. /// </summary>
  128. /// <param name="sender">The sender.</param>
  129. /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
  130. private void OnUnloaded(object sender, EventArgs e)
  131. {
  132. CleanUp();
  133. }
  134. /// <summary>
  135. /// Initializes this instance. Loads all the errors and warnings that were added when the control was not yet loaded.s
  136. /// </summary>
  137. private void Initialize()
  138. {
  139. if (Source != null)
  140. {
  141. UpdateSource(null, Source);
  142. }
  143. }
  144. /// <summary>
  145. /// Cleans up.
  146. /// </summary>
  147. private void CleanUp()
  148. {
  149. List<object> objects = new List<object>();
  150. lock (_objectValidationLock)
  151. {
  152. objects.AddRange(_objectValidation.Keys);
  153. }
  154. foreach (object obj in objects)
  155. {
  156. if (obj is IEnumerable)
  157. {
  158. RemoveObjectsFromWatchList(obj as IEnumerable);
  159. }
  160. else if (obj is INotifyPropertyChanged)
  161. {
  162. RemoveObjectFromWatchList(obj);
  163. }
  164. }
  165. _objectValidation.Clear();
  166. }
  167. /// <summary>
  168. /// Updates the source.
  169. /// </summary>
  170. /// <param name="oldValue">The old value.</param>
  171. /// <param name="newValue">The new value.</param>
  172. private void UpdateSource(object oldValue, object newValue)
  173. {
  174. var oldValueAsIEnumerable = oldValue as IEnumerable;
  175. if (oldValueAsIEnumerable != null)
  176. {
  177. RemoveObjectsFromWatchList(oldValueAsIEnumerable);
  178. }
  179. else if (oldValue is INotifyPropertyChanged)
  180. {
  181. RemoveObjectFromWatchList(oldValue);
  182. }
  183. #if !SILVERLIGHT
  184. if (!IsLoaded)
  185. {
  186. return;
  187. }
  188. #endif
  189. var newValueAsIEnumerable = newValue as IEnumerable;
  190. if (newValueAsIEnumerable != null)
  191. {
  192. AddObjectsToWatchList(newValueAsIEnumerable, newValueAsIEnumerable);
  193. }
  194. else if (newValue is INotifyPropertyChanged)
  195. {
  196. AddObjectToWatchList(newValue);
  197. }
  198. }
  199. /// <summary>
  200. /// Adds an <see cref="IEnumerable"/> of objects to the watch list.
  201. /// </summary>
  202. /// <param name="values">The values to add to the watch list.</param>
  203. /// <param name="parentEnumerable">The parent enumerable. <c>Null</c> if the object does not belong to an enumerable.</param>
  204. /// <exception cref="ArgumentNullException">The <paramref name="values"/> is <c>null</c>.</exception>
  205. private void AddObjectsToWatchList(IEnumerable values, IEnumerable parentEnumerable)
  206. {
  207. Argument.IsNotNull("values", values);
  208. foreach (object value in values)
  209. {
  210. AddObjectToWatchList(value, parentEnumerable);
  211. }
  212. // Supports IObservableCollection through INotifyCollectionChanged and support IEntityCollectionCore
  213. INotifyCollectionChanged iNotifyCollectionChanged = values as INotifyCollectionChanged;
  214. if (iNotifyCollectionChanged != null)
  215. {
  216. iNotifyCollectionChanged.CollectionChanged += iNotifyCollectionChanged_CollectionChanged;
  217. AddObjectToWatchList(parentEnumerable);
  218. }
  219. }
  220. /// <summary>
  221. /// Adds the object to the watch list.
  222. /// </summary>
  223. /// <param name="value">The object to add to the watch list.</param>
  224. private void AddObjectToWatchList(object value)
  225. {
  226. AddObjectToWatchList(value, null);
  227. }
  228. /// <summary>
  229. /// Adds the object to the watch list.
  230. /// </summary>
  231. /// <param name="value">The object to add to the watch list.</param>
  232. /// <param name="parentEnumerable">The parent enumerable. <c>Null</c> if the object does not belong to an enumerable.</param>
  233. private void AddObjectToWatchList(object value, IEnumerable parentEnumerable)
  234. {
  235. if (value == null)
  236. {
  237. return;
  238. }
  239. lock (_objectValidationLock)
  240. {
  241. if (!_objectValidation.ContainsKey(value))
  242. {
  243. INotifyPropertyChanged iNotifyPropertyChanged = value as INotifyPropertyChanged;
  244. if (iNotifyPropertyChanged != null)
  245. {
  246. iNotifyPropertyChanged.PropertyChanged += iNotifyPropertyChanged_PropertyChanged;
  247. }
  248. }
  249. }
  250. CheckObjectValidation(value, null, parentEnumerable);
  251. }
  252. /// <summary>
  253. /// Removes an <see cref="IEnumerable"/> of objects from the watch list.
  254. /// </summary>
  255. /// <param name="values">The values to remove from the watch list.</param>
  256. private void RemoveObjectsFromWatchList(IEnumerable values)
  257. {
  258. foreach (object value in values)
  259. {
  260. RemoveObjectFromWatchList(value);
  261. }
  262. // Supports IObservableCollection through INotifyCollectionChanged and support IEntityCollectionCore
  263. INotifyCollectionChanged iNotifyCollectionChanged = values as INotifyCollectionChanged;
  264. if (iNotifyCollectionChanged != null)
  265. {
  266. iNotifyCollectionChanged.CollectionChanged -= iNotifyCollectionChanged_CollectionChanged;
  267. RemoveObjectFromWatchList(values);
  268. }
  269. }
  270. /// <summary>
  271. /// Removes the object from watch list.
  272. /// </summary>
  273. /// <param name="value">The object to remove from the watch list.</param>
  274. private void RemoveObjectFromWatchList(object value)
  275. {
  276. if (value == null)
  277. {
  278. return;
  279. }
  280. INotifyPropertyChanged iNotifyPropertyChanged = value as INotifyPropertyChanged;
  281. if (iNotifyPropertyChanged != null)
  282. {
  283. iNotifyPropertyChanged.PropertyChanged -= iNotifyPropertyChanged_PropertyChanged;
  284. }
  285. RaiseBusinessValidationWarningOrError(value, string.Empty, ValidationEventAction.ClearAll, ValidationType.Warning);
  286. RaiseBusinessValidationWarningOrError(value, string.Empty, ValidationEventAction.ClearAll, ValidationType.Error);
  287. lock (_objectValidationLock)
  288. {
  289. _objectValidation.Remove(value);
  290. }
  291. }
  292. /// <summary>
  293. /// Checks a entity that either implements the <see cref="IDataWarningInfo"/> or <see cref="IDataErrorInfo"/> on warnings and errors.
  294. /// </summary>
  295. /// <param name="value">The object to check.</param>
  296. /// <param name="propertyChanged">The propery that has been changed. <c>null</c> if no specific property has changed.</param>
  297. /// <param name="parentEnumerable">The parent enumerable. <c>Null</c> if the object does not belong to an enumerable.</param>
  298. /// <remarks>
  299. /// Internally calls the generic method with the same name.
  300. /// </remarks>
  301. private void CheckObjectValidation(object value, string propertyChanged, IEnumerable parentEnumerable)
  302. {
  303. ValidationData currentValidationData;
  304. ValidationData oldValidationData;
  305. if (value == null)
  306. {
  307. return;
  308. }
  309. lock (_objectValidationLock)
  310. {
  311. if (!_objectValidation.ContainsKey(value))
  312. {
  313. _objectValidation.Add(value, new ValidationData(parentEnumerable));
  314. }
  315. currentValidationData = _objectValidation[value];
  316. oldValidationData = (ValidationData)currentValidationData.Clone();
  317. }
  318. #region Warnings - fields
  319. CheckObjectValidationForFields(value, propertyChanged, currentValidationData.FieldWarnings, ValidationType.Warning);
  320. #endregion
  321. #region Warnings - business
  322. currentValidationData.BusinessWarnings.Clear();
  323. string businessWarning = GetWarningOrError(value, ValidationType.Warning);
  324. if (!string.IsNullOrEmpty(businessWarning))
  325. {
  326. currentValidationData.BusinessWarnings.Add(new BusinessWarningOrErrorInfo(businessWarning));
  327. }
  328. #endregion
  329. #region Errors - fields
  330. CheckObjectValidationForFields(value, propertyChanged, currentValidationData.FieldErrors, ValidationType.Error);
  331. #endregion
  332. #region Errors - business
  333. currentValidationData.BusinessErrors.Clear();
  334. string businessError = GetWarningOrError(value, ValidationType.Error);
  335. if (!string.IsNullOrEmpty(businessError))
  336. {
  337. currentValidationData.BusinessErrors.Add(new BusinessWarningOrErrorInfo(businessError));
  338. }
  339. #endregion
  340. RaiseEventsForDifferences(value, oldValidationData, currentValidationData);
  341. }
  342. /// <summary>
  343. /// Checks the object validation for fields warnings or errors.
  344. /// </summary>
  345. /// <param name="value">The value.</param>
  346. /// <param name="propertyChanged">The property changed.</param>
  347. /// <param name="infoList">The info list containing the warning or error info.</param>
  348. /// <param name="validationType">Type of the validation.</param>
  349. private static void CheckObjectValidationForFields(object value, string propertyChanged, ObservableCollection<FieldWarningOrErrorInfo> infoList,
  350. ValidationType validationType)
  351. {
  352. if (string.IsNullOrEmpty(propertyChanged))
  353. {
  354. infoList.Clear();
  355. }
  356. else
  357. {
  358. for (int i = 0; i < infoList.Count; i++)
  359. {
  360. if (string.Compare(infoList[i].Field, propertyChanged) == 0)
  361. {
  362. infoList.RemoveAt(i);
  363. }
  364. }
  365. }
  366. Dictionary<string, string> fieldWarningsOrErrors = CheckFieldWarningsOrErrors(value, propertyChanged, validationType);
  367. foreach (KeyValuePair<string, string> fieldWarningOrError in fieldWarningsOrErrors)
  368. {
  369. FieldWarningOrErrorInfo fieldWarningOrErrorInfo = new FieldWarningOrErrorInfo(fieldWarningOrError.Key, fieldWarningOrError.Value);
  370. if (!infoList.Contains(fieldWarningOrErrorInfo))
  371. {
  372. infoList.Add(new FieldWarningOrErrorInfo(fieldWarningOrError.Key, fieldWarningOrError.Value));
  373. }
  374. }
  375. }
  376. /// <summary>
  377. /// Checks the field warnings or errors.
  378. /// </summary>
  379. /// <param name="value">The value.</param>
  380. /// <param name="propertyChanged">The property changed.</param>
  381. /// <param name="validationType">Type of the validation.</param>
  382. /// <returns>
  383. /// List of warnings or errors returned by the object.
  384. /// </returns>
  385. private static Dictionary<string, string> CheckFieldWarningsOrErrors(object value, string propertyChanged, ValidationType validationType)
  386. {
  387. Dictionary<string, string> warningsOrErrors = new Dictionary<string, string>();
  388. IDataWarningInfo iDataWarningInfo = value as IDataWarningInfo;
  389. if ((validationType == ValidationType.Warning) && (iDataWarningInfo == null))
  390. {
  391. return warningsOrErrors;
  392. }
  393. IDataErrorInfo iDataErrorInfo = value as IDataErrorInfo;
  394. if ((validationType == ValidationType.Error) && (iDataErrorInfo == null))
  395. {
  396. return warningsOrErrors;
  397. }
  398. List<string> propertiesToCheck = new List<string>();
  399. if (!string.IsNullOrEmpty(propertyChanged))
  400. {
  401. propertiesToCheck.Add(propertyChanged);
  402. }
  403. else
  404. {
  405. Type type = value.GetType();
  406. PropertyInfo[] properties = type.GetProperties();
  407. foreach (PropertyInfo property in properties)
  408. {
  409. propertiesToCheck.Add(property.Name);
  410. }
  411. }
  412. foreach (string property in propertiesToCheck)
  413. {
  414. string warningOrError = string.Empty;
  415. switch (validationType)
  416. {
  417. case ValidationType.Warning:
  418. // ReSharper disable PossibleNullReferenceException
  419. warningOrError = iDataWarningInfo[property];
  420. // ReSharper restore PossibleNullReferenceException
  421. break;
  422. case ValidationType.Error:
  423. // ReSharper disable PossibleNullReferenceException
  424. warningOrError = iDataErrorInfo[property];
  425. // ReSharper restore PossibleNullReferenceException
  426. break;
  427. }
  428. if (!string.IsNullOrEmpty(warningOrError))
  429. {
  430. warningsOrErrors[property] = warningOrError;
  431. }
  432. }
  433. return warningsOrErrors;
  434. }
  435. /// <summary>
  436. /// Gets the warning or error message for the object.
  437. /// </summary>
  438. /// <param name="value">The value.</param>
  439. /// <param name="type">The type.</param>
  440. /// <returns>
  441. /// Warning or error message formatted for the object.
  442. /// </returns>
  443. private static string GetWarningOrError(object value, ValidationType type)
  444. {
  445. string message = null;
  446. switch (type)
  447. {
  448. case ValidationType.Warning:
  449. var valueAsIDataWarningInfo = value as IDataWarningInfo;
  450. if (valueAsIDataWarningInfo != null)
  451. {
  452. message = valueAsIDataWarningInfo.Warning;
  453. }
  454. break;
  455. case ValidationType.Error:
  456. var valueAsIDataErrorInfo = value as IDataErrorInfo;
  457. if (valueAsIDataErrorInfo != null)
  458. {
  459. message = valueAsIDataErrorInfo.Error;
  460. }
  461. break;
  462. }
  463. return !string.IsNullOrEmpty(message) ? message : null;
  464. }
  465. /// <summary>
  466. /// Raises the events for all errors and warnings.
  467. /// </summary>
  468. private void RaiseEventsForAllErrorsAndWarnings()
  469. {
  470. lock (_objectValidationLock)
  471. {
  472. foreach (var objectValidationKeyValue in _objectValidation)
  473. {
  474. var obj = objectValidationKeyValue.Key;
  475. var validation = objectValidationKeyValue.Value;
  476. foreach (var fieldWarning in validation.FieldWarnings)
  477. {
  478. RaiseBusinessValidationWarningOrError(obj, fieldWarning.Message, ValidationEventAction.Refresh, ValidationType.Warning);
  479. }
  480. foreach (var businessWarning in validation.BusinessWarnings)
  481. {
  482. RaiseBusinessValidationWarningOrError(obj, businessWarning.Message, ValidationEventAction.Refresh, ValidationType.Warning);
  483. }
  484. foreach (var fieldError in validation.FieldErrors)
  485. {
  486. RaiseBusinessValidationWarningOrError(obj, fieldError.Message, ValidationEventAction.Refresh, ValidationType.Error);
  487. }
  488. foreach (var businessError in validation.BusinessErrors)
  489. {
  490. RaiseBusinessValidationWarningOrError(obj, businessError.Message, ValidationEventAction.Refresh, ValidationType.Error);
  491. }
  492. }
  493. }
  494. }
  495. /// <summary>
  496. /// Raises the events for differences.
  497. /// </summary>
  498. /// <param name="value">The value.</param>
  499. /// <param name="oldValidationData">The old validation data.</param>
  500. /// <param name="newValidationData">The new validation data.</param>
  501. private void RaiseEventsForDifferences(object value, ValidationData oldValidationData, ValidationData newValidationData)
  502. {
  503. #region Warnings - fields
  504. RaiseEventsForDifferencesInFields(value, oldValidationData.FieldWarnings, newValidationData.FieldWarnings, ValidationType.Warning);
  505. #endregion
  506. #region Warnings - business
  507. RaiseEventsForDifferencesInBusiness(value, oldValidationData.BusinessWarnings, newValidationData.BusinessWarnings, ValidationType.Warning);
  508. #endregion
  509. #region Errors - fields
  510. RaiseEventsForDifferencesInFields(value, oldValidationData.FieldErrors, newValidationData.FieldErrors, ValidationType.Error);
  511. #endregion
  512. #region Errors - business
  513. RaiseEventsForDifferencesInBusiness(value, oldValidationData.BusinessErrors, newValidationData.BusinessErrors, ValidationType.Error);
  514. #endregion
  515. }
  516. /// <summary>
  517. /// Raises the events for differences in fields.
  518. /// </summary>
  519. /// <param name="value">The value.</param>
  520. /// <param name="oldFieldData">The old field data.</param>
  521. /// <param name="newFieldData">The new field data.</param>
  522. /// <param name="validationType">Type of the validation.</param>
  523. private void RaiseEventsForDifferencesInFields(object value, ICollection<FieldWarningOrErrorInfo> oldFieldData,
  524. ICollection<FieldWarningOrErrorInfo> newFieldData, ValidationType validationType)
  525. {
  526. foreach (FieldWarningOrErrorInfo info in oldFieldData)
  527. {
  528. if (!newFieldData.Contains(info))
  529. {
  530. RaiseBusinessValidationWarningOrError(value, info.Message, ValidationEventAction.Removed, validationType);
  531. }
  532. }
  533. foreach (FieldWarningOrErrorInfo info in newFieldData)
  534. {
  535. if (!oldFieldData.Contains(info))
  536. {
  537. RaiseBusinessValidationWarningOrError(value, info.Message, ValidationEventAction.Added, validationType);
  538. }
  539. }
  540. }
  541. /// <summary>
  542. /// Raises the events for differences in business.
  543. /// </summary>
  544. /// <param name="value">The value.</param>
  545. /// <param name="oldBusinessData">The old business data.</param>
  546. /// <param name="newBusinessData">The new business data.</param>
  547. /// <param name="validationType">Type of the validation.</param>
  548. private void RaiseEventsForDifferencesInBusiness(object value, ICollection<BusinessWarningOrErrorInfo> oldBusinessData,
  549. ICollection<BusinessWarningOrErrorInfo> newBusinessData, ValidationType validationType)
  550. {
  551. foreach (BusinessWarningOrErrorInfo info in oldBusinessData)
  552. {
  553. if (!newBusinessData.Contains(info))
  554. {
  555. RaiseBusinessValidationWarningOrError(value, info.Message, ValidationEventAction.Removed, validationType);
  556. }
  557. }
  558. foreach (BusinessWarningOrErrorInfo info in newBusinessData)
  559. {
  560. if (!oldBusinessData.Contains(info))
  561. {
  562. RaiseBusinessValidationWarningOrError(value, info.Message, ValidationEventAction.Added, validationType);
  563. }
  564. }
  565. }
  566. /// <summary>
  567. /// Raises an validation warning or error event.
  568. /// </summary>
  569. /// <param name="value">The value.</param>
  570. /// <param name="message">A message.</param>
  571. /// <param name="action">A action for handling the error event.</param>
  572. /// <param name="type">The type.</param>
  573. private void RaiseBusinessValidationWarningOrError(object value, string message, ValidationEventAction action, ValidationType type)
  574. {
  575. Validation.SafeInvoke(this, new ValidationEventArgs(value, message, action, type));
  576. }
  577. /// <summary>
  578. /// Handling changes of properties within entity.
  579. /// </summary>
  580. /// <param name="sender">A sender.</param>
  581. /// <param name="e">The event args.</param>
  582. private void iNotifyPropertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e)
  583. {
  584. CheckObjectValidation(sender, e.PropertyName, null);
  585. }
  586. /// <summary>
  587. /// Handling change of collection updating connections and error messages.
  588. /// </summary>
  589. /// <param name="sender">A sender.</param>
  590. /// <param name="e">Event args.</param>
  591. private void iNotifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  592. {
  593. // If the action is "reset", no OldItems will be available, so clear all items manually
  594. if (e.Action == NotifyCollectionChangedAction.Reset)
  595. {
  596. Collection<object> itemsToRemove = new Collection<object>();
  597. lock (_objectValidationLock)
  598. {
  599. foreach (KeyValuePair<object, ValidationData> validationData in _objectValidation)
  600. {
  601. if (validationData.Value.ParentEnumerable == sender)
  602. {
  603. itemsToRemove.Add(validationData.Key);
  604. }
  605. }
  606. }
  607. // Remove the items that should be removed (outside the lock)
  608. foreach (object itemToRemove in itemsToRemove)
  609. {
  610. RemoveObjectFromWatchList(itemToRemove);
  611. }
  612. return;
  613. }
  614. IEnumerable newItems = e.NewItems;
  615. IEnumerable oldItems = e.OldItems;
  616. if (oldItems != null)
  617. {
  618. RemoveObjectsFromWatchList(oldItems);
  619. }
  620. if (newItems != null)
  621. {
  622. AddObjectsToWatchList(newItems, sender as IEnumerable);
  623. }
  624. }
  625. #endregion
  626. }
  627. #region Data classes
  628. /// <summary>
  629. /// Class containing all validation info (thus warnings and errors) about a specific object.
  630. /// </summary>
  631. internal class ValidationData
  632. {
  633. #region Constructor & destructor
  634. /// <summary>
  635. /// Initializes a new instance of the <see cref="ValidationData"/> class.
  636. /// </summary>
  637. /// <param name="parentEnumerable">The parent ParentEnumerable. <c>Null</c> if the object does not belong to an enumerable.</param>
  638. public ValidationData(IEnumerable parentEnumerable)
  639. {
  640. FieldWarnings = new ObservableCollection<FieldWarningOrErrorInfo>();
  641. BusinessWarnings = new ObservableCollection<BusinessWarningOrErrorInfo>();
  642. FieldErrors = new ObservableCollection<FieldWarningOrErrorInfo>();
  643. BusinessErrors = new ObservableCollection<BusinessWarningOrErrorInfo>();
  644. ParentEnumerable = parentEnumerable;
  645. }
  646. #endregion
  647. #region Properties
  648. /// <summary>
  649. /// Gets or sets the parent enumerable.
  650. /// </summary>
  651. /// <value>The parent enumerable.</value>
  652. public IEnumerable ParentEnumerable { get; private set; }
  653. /// <summary>
  654. /// Gets the field warnings.
  655. /// </summary>
  656. /// <value>The field warnings.</value>
  657. public ObservableCollection<FieldWarningOrErrorInfo> FieldWarnings { get; private set; }
  658. /// <summary>
  659. /// Gets the business warnings.
  660. /// </summary>
  661. /// <value>The business warnings.</value>
  662. public ObservableCollection<BusinessWarningOrErrorInfo> BusinessWarnings { get; private set; }
  663. /// <summary>
  664. /// Gets the field errors.
  665. /// </summary>
  666. /// <value>The field errors.</value>
  667. public ObservableCollection<FieldWarningOrErrorInfo> FieldErrors { get; private set; }
  668. /// <summary>
  669. /// Gets the business errors.
  670. /// </summary>
  671. /// <value>The business errors.</value>
  672. public ObservableCollection<BusinessWarningOrErrorInfo> BusinessErrors { get; private set; }
  673. #endregion
  674. #region Methods
  675. /// <summary>
  676. /// Clears the warnings and errors.
  677. /// </summary>
  678. public void ClearWarningsAndErrors()
  679. {
  680. BusinessWarnings.Clear();
  681. FieldWarnings.Clear();
  682. BusinessErrors.Clear();
  683. FieldErrors.Clear();
  684. }
  685. /// <summary>
  686. /// Creates a new object that is a copy of the current instance.
  687. /// </summary>
  688. /// <returns>
  689. /// A new object that is a copy of this instance.
  690. /// </returns>
  691. public object Clone()
  692. {
  693. ValidationData validationData = new ValidationData(ParentEnumerable);
  694. validationData.FieldWarnings = new ObservableCollection<FieldWarningOrErrorInfo>();
  695. validationData.FieldWarnings.AddRange(FieldWarnings);
  696. validationData.BusinessWarnings = new ObservableCollection<BusinessWarningOrErrorInfo>();
  697. validationData.BusinessWarnings.AddRange(BusinessWarnings);
  698. validationData.FieldErrors = new ObservableCollection<FieldWarningOrErrorInfo>();
  699. validationData.FieldErrors.AddRange(FieldErrors);
  700. validationData.BusinessErrors = new ObservableCollection<BusinessWarningOrErrorInfo>();
  701. validationData.BusinessErrors.AddRange(BusinessErrors);
  702. return validationData;
  703. }
  704. #endregion
  705. }
  706. /// <summary>
  707. /// Information class about business warnings and errors.
  708. /// </summary>
  709. internal class BusinessWarningOrErrorInfo
  710. {
  711. /// <summary>
  712. /// Initializes a new instance of the <see cref="BusinessWarningOrErrorInfo"/> class.
  713. /// </summary>
  714. /// <param name="message">The message.</param>
  715. public BusinessWarningOrErrorInfo(string message)
  716. {
  717. Message = message;
  718. }
  719. #region Properties
  720. /// <summary>
  721. /// Gets the message.
  722. /// </summary>
  723. /// <value>The message.</value>
  724. public string Message { get; private set; }
  725. #endregion
  726. #region Methods
  727. /// <summary>
  728. /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
  729. /// </summary>
  730. /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
  731. /// <returns>
  732. /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
  733. /// </returns>
  734. /// <exception cref="T:System.NullReferenceException">
  735. /// The <paramref name="obj"/> parameter is null.
  736. /// </exception>
  737. public override bool Equals(object obj)
  738. {
  739. if (obj == null)
  740. {
  741. return false;
  742. }
  743. if (obj.GetType() != GetType())
  744. {
  745. return false;
  746. }
  747. if (obj.GetHashCode() != GetHashCode())
  748. {
  749. return false;
  750. }
  751. return true;
  752. }
  753. /// <summary>
  754. /// Returns a hash code for this instance.
  755. /// </summary>
  756. /// <returns>
  757. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
  758. /// </returns>
  759. public override int GetHashCode()
  760. {
  761. return string.Format(CultureInfo.InvariantCulture, "{0}", Message).GetHashCode();
  762. }
  763. #endregion
  764. }
  765. /// <summary>
  766. /// Information class about field warnings and errors.
  767. /// </summary>
  768. internal class FieldWarningOrErrorInfo
  769. {
  770. /// <summary>
  771. /// Initializes a new instance of the <see cref="FieldWarningOrErrorInfo"/> class.
  772. /// </summary>
  773. /// <param name="field">The field.</param>
  774. /// <param name="message">The message.</param>
  775. public FieldWarningOrErrorInfo(string field, string message)
  776. {
  777. Field = field;
  778. Message = message;
  779. }
  780. #region Properties
  781. /// <summary>
  782. /// Gets the field.
  783. /// </summary>
  784. /// <value>The field.</value>
  785. public string Field { get; private set; }
  786. /// <summary>
  787. /// Gets the message.
  788. /// </summary>
  789. /// <value>The message.</value>
  790. public string Message { get; private set; }
  791. #endregion
  792. #region Methods
  793. /// <summary>
  794. /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
  795. /// </summary>
  796. /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
  797. /// <returns>
  798. /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
  799. /// </returns>
  800. /// <exception cref="T:System.NullReferenceException">
  801. /// The <paramref name="obj"/> parameter is null.
  802. /// </exception>
  803. public override bool Equals(object obj)
  804. {
  805. if (obj == null)
  806. {
  807. return false;
  808. }
  809. if (obj.GetType() != GetType())
  810. {
  811. return false;
  812. }
  813. if (obj.GetHashCode() != GetHashCode())
  814. {
  815. return false;
  816. }
  817. return true;
  818. }
  819. /// <summary>
  820. /// Returns a hash code for this instance.
  821. /// </summary>
  822. /// <returns>
  823. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
  824. /// </returns>
  825. public override int GetHashCode()
  826. {
  827. return string.Format(CultureInfo.InvariantCulture, "{0}|{1}", Field, Message).GetHashCode();
  828. }
  829. #endregion
  830. }
  831. #endregion
  832. #region Event args
  833. /// <summary>
  834. /// Event arguments for event <see cref="WarningAndErrorValidator"/> Validation.
  835. /// </summary>
  836. public class ValidationEventArgs : EventArgs
  837. {
  838. #region Variables
  839. #endregion
  840. #region Constructor & destructor
  841. /// <summary>
  842. /// Initializes a new instance of the <see cref="ValidationEventArgs"/> class.
  843. /// </summary>
  844. /// <param name="value">The value that contains the warning or error.</param>
  845. /// <param name="message">The actual warning or error message.</param>
  846. /// <param name="action">The action of the validation event.</param>
  847. /// <param name="type">The type of validation.</param>
  848. internal ValidationEventArgs(object value, string message, ValidationEventAction action, ValidationType type)
  849. {
  850. Value = value;
  851. Message = message;
  852. Action = action;
  853. Type = type;
  854. }
  855. #endregion
  856. #region Properties
  857. /// <summary>
  858. /// Gets the value that contains the warning or error.
  859. /// </summary>
  860. /// <value>The value that contains the warning or error.</value>
  861. public object Value { get; private set; }
  862. /// <summary>
  863. /// Gets the actual warning or error message.
  864. /// </summary>
  865. /// <value>The message.</value>
  866. public string Message { get; private set; }
  867. /// <summary>
  868. /// A action for handling event.
  869. /// </summary>
  870. public ValidationEventAction Action { get; private set; }
  871. /// <summary>
  872. /// Gets the type of the validation.
  873. /// </summary>
  874. /// <value>The type of the validation.</value>
  875. public ValidationType Type { get; private set; }
  876. #endregion
  877. #region Methods
  878. #endregion
  879. }
  880. #endregion
  881. }