diff --git a/src/AtomUI.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/AtomUI.Controls.DataGrid/DataGridRowGroupHeader.cs
new file mode 100644
index 000000000..dc39749fd
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/DataGridRowGroupHeader.cs
@@ -0,0 +1,8 @@
+using Avalonia.Controls.Primitives;
+
+namespace AtomUI.Controls;
+
+public class DataGridRowGroupHeader : TemplatedControl
+{
+
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridAutoGeneratingColumnEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridAutoGeneratingColumnEventArgs.cs
new file mode 100644
index 000000000..31907527b
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridAutoGeneratingColumnEventArgs.cs
@@ -0,0 +1,57 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System.ComponentModel;
+
+namespace AtomUI.Controls;
+
+public class DataGridAutoGeneratingColumnEventArgs : CancelEventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The name of the property bound to the generated column.
+ ///
+ ///
+ /// The of the property bound to the generated column.
+ ///
+ ///
+ /// The generated column.
+ ///
+ public DataGridAutoGeneratingColumnEventArgs(string propertyName, Type propertyType, DataGridColumn column)
+ {
+ Column = column;
+ PropertyName = propertyName;
+ PropertyType = propertyType;
+ }
+
+ ///
+ /// Gets the generated column.
+ ///
+ public DataGridColumn Column
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets the name of the property bound to the generated column.
+ ///
+ public string PropertyName
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the of the property bound to the generated column.
+ ///
+ public Type PropertyType
+ {
+ get;
+ private set;
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridBeginningEditEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridBeginningEditEventArgs.cs
new file mode 100644
index 000000000..64ebf6bdc
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridBeginningEditEventArgs.cs
@@ -0,0 +1,62 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System.ComponentModel;
+using Avalonia.Interactivity;
+
+namespace AtomUI.Controls;
+
+public class DataGridBeginningEditEventArgs: CancelEventArgs
+{
+ ///
+ /// Initializes a new instance of the
+ /// class.
+ ///
+ ///
+ /// The column that contains the cell to be edited.
+ ///
+ ///
+ /// The row that contains the cell to be edited.
+ ///
+ ///
+ /// Information about the user gesture that caused the cell to enter edit mode.
+ ///
+ public DataGridBeginningEditEventArgs(DataGridColumn column,
+ DataGridRow row,
+ RoutedEventArgs editingEventArgs)
+ {
+ Column = column;
+ Row = row;
+ EditingEventArgs = editingEventArgs;
+ }
+
+ ///
+ /// Gets the column that contains the cell to be edited.
+ ///
+ public DataGridColumn Column
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets information about the user gesture that caused the cell to enter edit mode.
+ ///
+ public RoutedEventArgs EditingEventArgs
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the row that contains the cell to be edited.
+ ///
+ public DataGridRow Row
+ {
+ get;
+ private set;
+ }
+
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellEditEndedEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellEditEndedEventArgs.cs
new file mode 100644
index 000000000..3b97a7a17
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellEditEndedEventArgs.cs
@@ -0,0 +1,50 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+namespace AtomUI.Controls;
+
+public class DataGridCellEditEndedEventArgs : System.EventArgs
+{
+ ///
+ /// Instantiates a new instance of this class.
+ ///
+ /// The column of the cell that has just exited edit mode.
+ /// The row container of the cell container that has just exited edit mode.
+ /// The editing action that has been taken.
+ public DataGridCellEditEndedEventArgs(DataGridColumn column, DataGridRow row, DataGridEditAction editAction)
+ {
+ Column = column;
+ Row = row;
+ EditAction = editAction;
+ }
+
+ ///
+ /// The column of the cell that has just exited edit mode.
+ ///
+ public DataGridColumn Column
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The edit action taken when leaving edit mode.
+ ///
+ public DataGridEditAction EditAction
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The row container of the cell container that has just exited edit mode.
+ ///
+ public DataGridRow Row
+ {
+ get;
+ private set;
+ }
+
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellEditEndingEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellEditEndingEventArgs.cs
new file mode 100644
index 000000000..4a2499fe0
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellEditEndingEventArgs.cs
@@ -0,0 +1,67 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System.ComponentModel;
+using Avalonia.Controls;
+
+namespace AtomUI.Controls;
+
+public class DataGridCellEditEndingEventArgs : CancelEventArgs
+{
+ ///
+ /// Instantiates a new instance of this class.
+ ///
+ /// The column of the cell that is about to exit edit mode.
+ /// The row container of the cell container that is about to exit edit mode.
+ /// The editing element within the cell.
+ /// The editing action that will be taken.
+ public DataGridCellEditEndingEventArgs(DataGridColumn column,
+ DataGridRow row,
+ Control editingElement,
+ DataGridEditAction editAction)
+ {
+ Column = column;
+ Row = row;
+ EditingElement = editingElement;
+ EditAction = editAction;
+ }
+
+ ///
+ /// The column of the cell that is about to exit edit mode.
+ ///
+ public DataGridColumn Column
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The edit action to take when leaving edit mode.
+ ///
+ public DataGridEditAction EditAction
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The editing element within the cell.
+ ///
+ public Control EditingElement
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The row container of the cell container that is about to exit edit mode.
+ ///
+ public DataGridRow Row
+ {
+ get;
+ private set;
+ }
+
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellEventArgs.cs
new file mode 100644
index 000000000..414b2aa1f
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellEventArgs.cs
@@ -0,0 +1,23 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System.Diagnostics;
+
+namespace AtomUI.Controls;
+
+public class DataGridCellEventArgs : EventArgs
+{
+ internal DataGridCellEventArgs(DataGridCell dataGridCell)
+ {
+ Debug.Assert(dataGridCell != null);
+ Cell = dataGridCell;
+ }
+
+ internal DataGridCell Cell
+ {
+ get;
+ private set;
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellPointerPressedEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellPointerPressedEventArgs.cs
new file mode 100644
index 000000000..8fe04240d
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridCellPointerPressedEventArgs.cs
@@ -0,0 +1,49 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using Avalonia.Input;
+
+namespace AtomUI.Controls;
+
+public class DataGridCellPointerPressedEventArgs : EventArgs
+{
+ ///
+ /// Instantiates a new instance of this class.
+ ///
+ /// The cell that has been pressed.
+ /// The row container of the cell that has been pressed.
+ /// The column of the cell that has been pressed.
+ /// The pointer action that has been taken.
+ public DataGridCellPointerPressedEventArgs(DataGridCell cell,
+ DataGridRow row,
+ DataGridColumn column,
+ PointerPressedEventArgs e)
+ {
+ Cell = cell;
+ Row = row;
+ Column = column;
+ PointerPressedEventArgs = e;
+ }
+
+ ///
+ /// The cell that has been pressed.
+ ///
+ public DataGridCell Cell { get; }
+
+ ///
+ /// The row container of the cell that has been pressed.
+ ///
+ public DataGridRow Row { get; }
+
+ ///
+ /// The column of the cell that has been pressed.
+ ///
+ public DataGridColumn Column { get; }
+
+ ///
+ /// The pointer action that has been taken.
+ ///
+ public PointerPressedEventArgs PointerPressedEventArgs { get; }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridColumnEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridColumnEventArgs.cs
new file mode 100644
index 000000000..b1ac9d383
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridColumnEventArgs.cs
@@ -0,0 +1,29 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System.ComponentModel;
+
+namespace AtomUI.Controls;
+
+public class DataGridColumnEventArgs : HandledEventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The column that the event occurs for.
+ public DataGridColumnEventArgs(DataGridColumn column)
+ {
+ Column = column ?? throw new ArgumentNullException(nameof(column));
+ }
+
+ ///
+ /// Gets the column that the event occurs for.
+ ///
+ public DataGridColumn Column
+ {
+ get;
+ private set;
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridColumnReorderingEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridColumnReorderingEventArgs.cs
new file mode 100644
index 000000000..9cc23003a
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridColumnReorderingEventArgs.cs
@@ -0,0 +1,48 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System.ComponentModel;
+using Avalonia.Controls;
+
+namespace AtomUI.Controls;
+
+public class DataGridColumnReorderingEventArgs : CancelEventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public DataGridColumnReorderingEventArgs(DataGridColumn dataGridColumn)
+ {
+ Column = dataGridColumn;
+ }
+
+ ///
+ /// The column being moved.
+ ///
+ public DataGridColumn Column
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The popup indicator displayed while dragging. If null and Handled = true, then do not display a tooltip.
+ ///
+ public Control? DragIndicator
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// UIElement to display at the insertion position. If null and Handled = true, then do not display an insertion indicator.
+ ///
+ public Control? DropLocationIndicator
+ {
+ get;
+ set;
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridPreparingCellForEditEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridPreparingCellForEditEventArgs.cs
new file mode 100644
index 000000000..d5d5f68d4
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridPreparingCellForEditEventArgs.cs
@@ -0,0 +1,67 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+
+namespace AtomUI.Controls;
+
+public class DataGridPreparingCellForEditEventArgs: EventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The column that contains the cell to be edited.
+ /// The row that contains the cell to be edited.
+ /// Information about the user gesture that caused the cell to enter edit mode.
+ /// The element that the column displays for a cell in editing mode.
+ public DataGridPreparingCellForEditEventArgs(DataGridColumn column,
+ DataGridRow row,
+ RoutedEventArgs editingEventArgs,
+ Control editingElement)
+ {
+ Column = column;
+ Row = row;
+ EditingEventArgs = editingEventArgs;
+ EditingElement = editingElement;
+ }
+
+ ///
+ /// Gets the column that contains the cell to be edited.
+ ///
+ public DataGridColumn Column
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the element that the column displays for a cell in editing mode.
+ ///
+ public Control EditingElement
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets information about the user gesture that caused the cell to enter edit mode.
+ ///
+ public RoutedEventArgs EditingEventArgs
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the row that contains the cell to be edited.
+ ///
+ public DataGridRow Row
+ {
+ get;
+ private set;
+ }
+
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowDetailsEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowDetailsEventArgs.cs
new file mode 100644
index 000000000..f1dce88cf
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowDetailsEventArgs.cs
@@ -0,0 +1,40 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using Avalonia.Controls;
+
+namespace AtomUI.Controls;
+
+public class DataGridRowDetailsEventArgs : EventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The row that the event occurs for.
+ /// The row details section as a framework element.
+ public DataGridRowDetailsEventArgs(DataGridRow row, Control detailsElement)
+ {
+ Row = row;
+ DetailsElement = detailsElement;
+ }
+
+ ///
+ /// Gets the row details section as a framework element.
+ ///
+ public Control DetailsElement
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the row that the event occurs for.
+ ///
+ public DataGridRow Row
+ {
+ get;
+ private set;
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowEditEndedEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowEditEndedEventArgs.cs
new file mode 100644
index 000000000..d1db8d72b
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowEditEndedEventArgs.cs
@@ -0,0 +1,38 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+namespace AtomUI.Controls;
+
+public class DataGridRowEditEndedEventArgs : EventArgs
+{
+ ///
+ /// Instantiates a new instance of this class.
+ ///
+ /// The row container of the cell container that has just exited edit mode.
+ /// The editing action that has been taken.
+ public DataGridRowEditEndedEventArgs(DataGridRow row, DataGridEditAction editAction)
+ {
+ Row = row;
+ EditAction = editAction;
+ }
+
+ ///
+ /// The editing action that has been taken.
+ ///
+ public DataGridEditAction EditAction
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The row container of the cell container that has just exited edit mode.
+ ///
+ public DataGridRow Row
+ {
+ get;
+ private set;
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowEditEndingEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowEditEndingEventArgs.cs
new file mode 100644
index 000000000..05c4c9d3e
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowEditEndingEventArgs.cs
@@ -0,0 +1,40 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System.ComponentModel;
+
+namespace AtomUI.Controls;
+
+public class DataGridRowEditEndingEventArgs : CancelEventArgs
+{
+ ///
+ /// Instantiates a new instance of this class.
+ ///
+ /// The row container of the cell container that is about to exit edit mode.
+ /// The editing action that will be taken.
+ public DataGridRowEditEndingEventArgs(DataGridRow row, DataGridEditAction editAction)
+ {
+ Row = row;
+ EditAction = editAction;
+ }
+
+ ///
+ /// The editing action that will be taken.
+ ///
+ public DataGridEditAction EditAction
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// The row container of the cell container that is about to exit edit mode.
+ ///
+ public DataGridRow Row
+ {
+ get;
+ private set;
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowEventArgs.cs
new file mode 100644
index 000000000..7b9b6281c
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowEventArgs.cs
@@ -0,0 +1,27 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+namespace AtomUI.Controls;
+
+public class DataGridRowEventArgs : EventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The row that the event occurs for.
+ public DataGridRowEventArgs(DataGridRow dataGridRow)
+ {
+ Row = dataGridRow;
+ }
+
+ ///
+ /// Gets the row that the event occurs for.
+ ///
+ public DataGridRow Row
+ {
+ get;
+ private set;
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowGroupHeaderEventArgs.cs b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowGroupHeaderEventArgs.cs
new file mode 100644
index 000000000..6b92dd36d
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/EventArgs/DataGridRowGroupHeaderEventArgs.cs
@@ -0,0 +1,27 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+namespace AtomUI.Controls;
+
+public class DataGridRowGroupHeaderEventArgs : EventArgs
+{
+ ///
+ /// Constructs a DataGridRowGroupHeaderEventArgs instance
+ ///
+ ///
+ public DataGridRowGroupHeaderEventArgs(DataGridRowGroupHeader rowGroupHeader)
+ {
+ RowGroupHeader = rowGroupHeader;
+ }
+
+ ///
+ /// DataGridRowGroupHeader associated with this instance
+ ///
+ public DataGridRowGroupHeader RowGroupHeader
+ {
+ get;
+ private set;
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls.DataGrid/Utils/ValidationUtils.cs b/src/AtomUI.Controls.DataGrid/Utils/ValidationUtils.cs
new file mode 100644
index 000000000..956d4e1cc
--- /dev/null
+++ b/src/AtomUI.Controls.DataGrid/Utils/ValidationUtils.cs
@@ -0,0 +1,161 @@
+using System.ComponentModel.DataAnnotations;
+using Avalonia.Data;
+
+namespace AtomUI.Controls.Utils;
+
+internal static class ValidationUtils
+{
+ ///
+ /// Searches a ValidationResult for the specified target member name. If the target is null
+ /// or empty, this method will return true if there are no member names at all.
+ ///
+ /// ValidationResult to search.
+ /// Member name to search for.
+ /// True if found.
+ public static bool ContainsMemberName(this ValidationResult validationResult, string target)
+ {
+ int memberNameCount = 0;
+ foreach (string memberName in validationResult.MemberNames)
+ {
+ if (string.Equals(target, memberName))
+ {
+ return true;
+ }
+ memberNameCount++;
+ }
+ return (memberNameCount == 0 && string.IsNullOrEmpty(target));
+ }
+
+ ///
+ /// Finds an equivalent ValidationResult if one exists.
+ ///
+ /// ValidationResults to search through.
+ /// ValidationResult to find.
+ /// Equal ValidationResult if found, null otherwise.
+ public static ValidationResult? FindEqualValidationResult(this ICollection collection, ValidationResult target)
+ {
+ foreach (ValidationResult oldValidationResult in collection)
+ {
+ if (oldValidationResult.ErrorMessage == target.ErrorMessage)
+ {
+ bool movedOld = true;
+ bool movedTarget = true;
+ IEnumerator oldEnumerator = oldValidationResult.MemberNames.GetEnumerator();
+ IEnumerator targetEnumerator = target.MemberNames.GetEnumerator();
+ while (movedOld && movedTarget)
+ {
+ movedOld = oldEnumerator.MoveNext();
+ movedTarget = targetEnumerator.MoveNext();
+
+ if (!movedOld && !movedTarget)
+ {
+ return oldValidationResult;
+ }
+ if (movedOld != movedTarget || oldEnumerator.Current != targetEnumerator.Current)
+ {
+ break;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public static bool IsValid(this ValidationResult? result)
+ {
+ return result == null || result == ValidationResult.Success;
+ }
+
+ public static IEnumerable UnpackException(Exception? exception)
+ {
+ if (exception != null)
+ {
+ var exceptions = exception is AggregateException aggregate ?
+ aggregate.InnerExceptions :
+ (IEnumerable)new[] { exception };
+
+ return exceptions.Where(x => !(x is BindingChainException)).ToList();
+ }
+
+ return Array.Empty();
+ }
+
+ public static object? UnpackDataValidationException(Exception? exception)
+ {
+ if (exception is DataValidationException dataValidationException)
+ {
+ return dataValidationException.ErrorData;
+ }
+
+ return exception;
+ }
+
+ ///
+ /// Determines whether the collection contains an equivalent ValidationResult
+ ///
+ /// ValidationResults to search through
+ /// ValidationResult to search for
+ ///
+ public static bool ContainsEqualValidationResult(this ICollection collection, ValidationResult target)
+ {
+ return collection.FindEqualValidationResult(target) != null;
+ }
+
+ ///
+ /// Adds a new ValidationResult to the collection if an equivalent does not exist.
+ ///
+ /// ValidationResults to search through
+ /// ValidationResult to add
+ public static void AddIfNew(this ICollection collection, ValidationResult value)
+ {
+ if (!collection.ContainsEqualValidationResult(value))
+ {
+ collection.Add(value);
+ }
+ }
+
+ private static bool ExceptionsMatch(Exception e1, Exception e2)
+ {
+ return e1.Message == e2.Message;
+ }
+ public static void AddExceptionIfNew(this ICollection collection, Exception value)
+ {
+ if(!collection.Any(e => ExceptionsMatch(e, value)))
+ {
+ collection.Add(value);
+ }
+ }
+
+ ///
+ /// Performs an action and catches any non-critical exceptions.
+ ///
+ /// Action to perform
+ public static void CatchNonCriticalExceptions(Action action)
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception exception)
+ {
+ if (IsCriticalException(exception))
+ {
+ throw;
+ }
+ // Catch any non-critical exceptions
+ }
+ }
+
+ ///
+ /// Determines if the specified exception is un-recoverable.
+ ///
+ /// The exception.
+ /// True if the process cannot be recovered from the exception.
+ public static bool IsCriticalException(Exception exception)
+ {
+ return (exception is OutOfMemoryException) ||
+ (exception is StackOverflowException) ||
+ (exception is AccessViolationException) ||
+ (exception is ThreadAbortException);
+ }
+}
\ No newline at end of file