mirror of
https://github.com/ckaczor/ChrisKaczor.Wpf.Validation.git
synced 2026-01-13 17:22:33 -05:00
Handle tab controls when focusing on error
This commit is contained in:
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +1,157 @@
|
|||||||
using System.Collections.Generic;
|
using DebounceThrottle;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using System.Windows.Media;
|
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<BindingExpressionInfo> GetBindingExpressions(this DependencyObject parent)
|
||||||
|
{
|
||||||
|
return GetBindingExpressions(parent, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<BindingExpressionInfo> GetBindingExpressions(this DependencyObject parent, UpdateSourceTrigger[]? triggers)
|
||||||
|
{
|
||||||
|
// Create a list of framework elements and binding expressions
|
||||||
|
var bindingExpressions = new List<BindingExpressionInfo>();
|
||||||
|
|
||||||
|
// Get all explicit bindings into the list
|
||||||
|
GetBindingExpressions(parent, triggers, ref bindingExpressions);
|
||||||
|
|
||||||
|
return bindingExpressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GetBindingExpressions(DependencyObject parent, UpdateSourceTrigger[]? triggers, ref List<BindingExpressionInfo> bindingExpressions)
|
||||||
|
{
|
||||||
|
// Get the number of children
|
||||||
|
var childCount = VisualTreeHelper.GetChildrenCount(parent);
|
||||||
|
|
||||||
|
// Loop over each child
|
||||||
|
for (var childIndex = 0; childIndex < childCount; childIndex++)
|
||||||
{
|
{
|
||||||
FrameworkElement = frameworkElement;
|
// Get the child
|
||||||
BindingExpression = bindingExpression;
|
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<DependencyProperty> dependencyProperties = TypeDescriptor.GetProperties(frameworkElement)
|
||||||
|
.Cast<PropertyDescriptor>()
|
||||||
|
.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<BindingExpressionInfo> bindingExpressions)
|
||||||
{
|
{
|
||||||
public static List<BindingExpressionInfo> GetBindingExpressions(this DependencyObject parent)
|
foreach (var expression in bindingExpressions)
|
||||||
|
expression.BindingExpression.UpdateSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ClearAllValidationErrors(this DependencyObject _, IEnumerable<BindingExpressionInfo> 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<BindingExpressionInfo> GetBindingExpressions(this DependencyObject parent, UpdateSourceTrigger[]? triggers)
|
|
||||||
{
|
|
||||||
// Create a list of framework elements and binding expressions
|
|
||||||
var bindingExpressions = new List<BindingExpressionInfo>();
|
|
||||||
|
|
||||||
// Get all explicit bindings into the list
|
|
||||||
GetBindingExpressions(parent, triggers, ref bindingExpressions);
|
|
||||||
|
|
||||||
return bindingExpressions;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GetBindingExpressions(DependencyObject parent, UpdateSourceTrigger[]? triggers, ref List<BindingExpressionInfo> 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<DependencyProperty> 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<BindingExpressionInfo> bindingExpressions)
|
|
||||||
{
|
|
||||||
foreach (var expression in bindingExpressions)
|
|
||||||
expression.BindingExpression.UpdateSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ClearAllValidationErrors(this DependencyObject window, IEnumerable<BindingExpressionInfo> 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
|
// Set focus
|
||||||
firstErrorElement.Focus();
|
firstErrorElement.Focus();
|
||||||
|
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,9 @@
|
|||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<AssemblyName>ChrisKaczor.Wpf.Validation</AssemblyName>
|
<AssemblyName>ChrisKaczor.Wpf.Validation</AssemblyName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="DebounceThrottle" Version="2.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="README.md">
|
<None Update="README.md">
|
||||||
<Pack>True</Pack>
|
<Pack>True</Pack>
|
||||||
|
|||||||
Reference in New Issue
Block a user