diff --git a/RequiredValidationRule.cs b/RequiredValidationRule.cs deleted file mode 100644 index 0de045b..0000000 --- a/RequiredValidationRule.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Globalization; -using System.Windows.Controls; - -namespace ChrisKaczor.Wpf.Validation -{ - public class RequiredValidationRule : ValidationRule - { - public static string GetErrorMessage(object? fieldValue) - { - var errorMessage = string.Empty; - - if (fieldValue == null || string.IsNullOrWhiteSpace(fieldValue.ToString())) - errorMessage = "Required"; - - return errorMessage; - } - - public override ValidationResult Validate(object value, CultureInfo cultureInfo) - { - var error = GetErrorMessage(value); - - return !string.IsNullOrEmpty(error) ? new ValidationResult(false, error) : ValidationResult.ValidResult; - } - } -} diff --git a/WindowExtensions.cs b/WindowExtensions.cs index 539dee0..d29c161 100644 --- a/WindowExtensions.cs +++ b/WindowExtensions.cs @@ -1,126 +1,157 @@ -using System.Collections.Generic; +using DebounceThrottle; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media; +using System.Windows.Threading; -namespace ChrisKaczor.Wpf.Validation +namespace ChrisKaczor.Wpf.Validation; + +public class BindingExpressionInfo { - public class BindingExpressionInfo - { - public FrameworkElement FrameworkElement { get; } - public BindingExpression BindingExpression { get; } + public FrameworkElement FrameworkElement { get; } + public BindingExpression BindingExpression { get; } - public BindingExpressionInfo(FrameworkElement frameworkElement, BindingExpression bindingExpression) + public BindingExpressionInfo(FrameworkElement frameworkElement, BindingExpression bindingExpression) + { + FrameworkElement = frameworkElement; + BindingExpression = bindingExpression; + } +} + +public static class WindowExtensions +{ + public static List GetBindingExpressions(this DependencyObject parent) + { + return GetBindingExpressions(parent, null); + } + + public static List GetBindingExpressions(this DependencyObject parent, UpdateSourceTrigger[]? triggers) + { + // Create a list of framework elements and binding expressions + var bindingExpressions = new List(); + + // Get all explicit bindings into the list + GetBindingExpressions(parent, triggers, ref bindingExpressions); + + return bindingExpressions; + } + + private static void GetBindingExpressions(DependencyObject parent, UpdateSourceTrigger[]? triggers, ref List bindingExpressions) + { + // Get the number of children + var childCount = VisualTreeHelper.GetChildrenCount(parent); + + // Loop over each child + for (var childIndex = 0; childIndex < childCount; childIndex++) { - FrameworkElement = frameworkElement; - BindingExpression = bindingExpression; + // Get the child + var dependencyObject = VisualTreeHelper.GetChild(parent, childIndex); + + // Check if the object is a tab control + if (dependencyObject is TabControl tabControl) + { + // Loop over each tab + foreach (TabItem tabItem in tabControl.Items) + GetBindingExpressions((DependencyObject) tabItem.Content, triggers, ref bindingExpressions); + } + else + { + // Cast to framework element + if (dependencyObject is FrameworkElement frameworkElement) + { + // Get the list of properties + IEnumerable dependencyProperties = TypeDescriptor.GetProperties(frameworkElement) + .Cast() + .Select(DependencyPropertyDescriptor.FromProperty) + .Where(dependencyPropertyDescriptor => dependencyPropertyDescriptor != null) + .Select(dependencyPropertyDescriptor => dependencyPropertyDescriptor.DependencyProperty) + .ToList(); + + // Loop over each dependency property in the list + foreach (var dependencyProperty in dependencyProperties) + { + // Try to get the binding expression for the property + var bindingExpression = frameworkElement.GetBindingExpression(dependencyProperty); + + // If there is a binding expression and it is set to explicit then make it update the source + if (bindingExpression != null && (triggers == null || triggers.Contains(bindingExpression.ParentBinding.UpdateSourceTrigger))) + bindingExpressions.Add(new BindingExpressionInfo(frameworkElement, bindingExpression)); + } + } + + // If the dependency object has any children then check them + if (VisualTreeHelper.GetChildrenCount(dependencyObject) > 0) + GetBindingExpressions(dependencyObject, triggers, ref bindingExpressions); + } } } - public static class WindowExtensions + public static void UpdateAllSources(this DependencyObject _, IEnumerable bindingExpressions) { - public static List GetBindingExpressions(this DependencyObject parent) + foreach (var expression in bindingExpressions) + expression.BindingExpression.UpdateSource(); + } + + public static void ClearAllValidationErrors(this DependencyObject _, IEnumerable bindingExpressions) + { + foreach (var expression in bindingExpressions) + System.Windows.Controls.Validation.ClearInvalid(expression.BindingExpression); + } + + public static bool IsValid(this DependencyObject window) + { + return IsValid(window, null); + } + + private static readonly DebounceDispatcher FocusDispatcher = new(50); + + public static bool IsValid(this DependencyObject window, TabControl? tabControl) + { + // Get a list of all framework elements and binding expressions + var bindingExpressions = window.GetBindingExpressions(); + + // Loop over each binding expression and clear any existing error + window.ClearAllValidationErrors(bindingExpressions); + + // Force all explicit bindings to update the source + window.UpdateAllSources(bindingExpressions); + + // See if there are any errors + if (!bindingExpressions.Any(b => b.BindingExpression.HasError)) + return true; + + // Get the first framework element with an error + var firstErrorElement = bindingExpressions.First(b => b.BindingExpression.HasError).FrameworkElement; + + if (tabControl == null) { - return GetBindingExpressions(parent, null); - } - - public static List GetBindingExpressions(this DependencyObject parent, UpdateSourceTrigger[]? triggers) - { - // Create a list of framework elements and binding expressions - var bindingExpressions = new List(); - - // Get all explicit bindings into the list - GetBindingExpressions(parent, triggers, ref bindingExpressions); - - return bindingExpressions; - } - - private static void GetBindingExpressions(DependencyObject parent, UpdateSourceTrigger[]? triggers, ref List bindingExpressions) - { - // Get the number of children - var childCount = VisualTreeHelper.GetChildrenCount(parent); - - // Loop over each child - for (var childIndex = 0; childIndex < childCount; childIndex++) - { - // Get the child - var dependencyObject = VisualTreeHelper.GetChild(parent, childIndex); - - // Check if the object is a tab control - if (dependencyObject is TabControl tabControl) - { - // Loop over each tab - foreach (TabItem tabItem in tabControl.Items) - GetBindingExpressions((DependencyObject) tabItem.Content, triggers, ref bindingExpressions); - } - else - { - // Cast to framework element - if (dependencyObject is FrameworkElement frameworkElement) - { - // Get the list of properties - IEnumerable dependencyProperties = (from PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(frameworkElement) - select DependencyPropertyDescriptor.FromProperty(propertyDescriptor) - into dependencyPropertyDescriptor - where dependencyPropertyDescriptor != null - select dependencyPropertyDescriptor.DependencyProperty).ToList(); - - // Loop over each dependency property in the list - foreach (var dependencyProperty in dependencyProperties) - { - // Try to get the binding expression for the property - var bindingExpression = frameworkElement.GetBindingExpression(dependencyProperty); - - // If there is a binding expression and it is set to explicit then make it update the source - if (bindingExpression != null && (triggers == null || triggers.Contains(bindingExpression.ParentBinding.UpdateSourceTrigger))) - bindingExpressions.Add(new BindingExpressionInfo(frameworkElement, bindingExpression)); - } - } - - // If the dependency object has any children then check them - if (VisualTreeHelper.GetChildrenCount(dependencyObject) > 0) - GetBindingExpressions(dependencyObject, triggers, ref bindingExpressions); - } - } - } - - public static void UpdateAllSources(this DependencyObject window, IEnumerable bindingExpressions) - { - foreach (var expression in bindingExpressions) - expression.BindingExpression.UpdateSource(); - } - - public static void ClearAllValidationErrors(this DependencyObject window, IEnumerable bindingExpressions) - { - foreach (var expression in bindingExpressions) - System.Windows.Controls.Validation.ClearInvalid(expression.BindingExpression); - } - - public static bool IsValid(this DependencyObject window) - { - // Get a list of all framework elements and binding expressions - var bindingExpressions = window.GetBindingExpressions(); - - // Loop over each binding expression and clear any existing error - window.ClearAllValidationErrors(bindingExpressions); - - // Force all explicit bindings to update the source - window.UpdateAllSources(bindingExpressions); - - // See if there are any errors - if (!bindingExpressions.Any(b => b.BindingExpression.HasError)) - return true; - - // Get the first framework element with an error - var firstErrorElement = bindingExpressions.First(b => b.BindingExpression.HasError).FrameworkElement; - // Set focus firstErrorElement.Focus(); return false; } + + // Loop over each tab item + foreach (TabItem tabItem in tabControl.Items) + { + // Cast the content as visual + var content = (Visual) tabItem.Content; + + // See if the control with the error is a descendant + if (!firstErrorElement.IsDescendantOf(content)) + continue; + + // Select the tab + tabItem.IsSelected = true; + } + + var dispatcher = Dispatcher.CurrentDispatcher; + FocusDispatcher.Debounce(() => dispatcher.Invoke(() => firstErrorElement.Focus())); + + return false; } -} +} \ No newline at end of file diff --git a/Wpf.Validation.csproj b/Wpf.Validation.csproj index a5ebabd..e931779 100644 --- a/Wpf.Validation.csproj +++ b/Wpf.Validation.csproj @@ -14,6 +14,9 @@ README.md ChrisKaczor.Wpf.Validation + + + True