You can iterate over all your controls tree recursively and check the attached property Validation.HasErrorProperty, then focus on the first one you find in it.
下面的代码(来自 Chris Sell 和 Ian Griffiths 编写的《编程 WPF 》一书)验证了依赖对象及其子对象上的所有绑定规则:
public static class Validator
{
public static bool IsValid(DependencyObject parent)
{
// Validate all the bindings on the parent
bool valid = true;
LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
while (localValues.MoveNext())
{
LocalValueEntry entry = localValues.Current;
if (BindingOperations.IsDataBound(parent, entry.Property))
{
Binding binding = BindingOperations.GetBinding(parent, entry.Property);
foreach (ValidationRule rule in binding.ValidationRules)
{
ValidationResult result = rule.Validate(parent.GetValue(entry.Property), null);
if (!result.IsValid)
{
BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
System.Windows.Controls.Validation.MarkInvalid(expression, new ValidationError(rule, expression, result.ErrorContent, null));
valid = false;
}
}
}
}
// Validate all the bindings on the children
for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (!IsValid(child)) { valid = false; }
}
return valid;
}
}
您可以在页面/窗口的保存按钮单击事件处理程序中调用这个函数
private void saveButton_Click(object sender, RoutedEventArgs e)
{
if (Validator.IsValid(this)) // is valid
{
....
}
}
public static bool IsValid(DependencyObject parent)
{
if (Validation.GetHasError(parent))
return false;
// Validate all the bindings on the children
for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (!IsValid(child)) { return false; }
}
return true;
}
Had the same problem and tried the provided solutions. A combination of H-Man2's and skiba_k's solutions worked almost fine for me, for one exception: My Window has a TabControl. And the validation rules only get evaluated for the TabItem that is currently visible. So I replaced VisualTreeHelper by LogicalTreeHelper. Now it works.
public static bool IsValid(DependencyObject parent)
{
// Validate all the bindings on the parent
bool valid = true;
LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
while (localValues.MoveNext())
{
LocalValueEntry entry = localValues.Current;
if (BindingOperations.IsDataBound(parent, entry.Property))
{
Binding binding = BindingOperations.GetBinding(parent, entry.Property);
if (binding.ValidationRules.Count > 0)
{
BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
expression.UpdateSource();
if (expression.HasError)
{
valid = false;
}
}
}
}
// Validate all the bindings on the children
System.Collections.IEnumerable children = LogicalTreeHelper.GetChildren(parent);
foreach (object obj in children)
{
if (obj is DependencyObject)
{
DependencyObject child = (DependencyObject)obj;
if (!IsValid(child)) { valid = false; }
}
}
return valid;
}
You might be interested in the 图书馆 sample application of the WPF 应用程序框架(WAF). It shows how to use validation in WPF and how to control the Save button when validation errors exists.
private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = IsValid(sender as DependencyObject);
}
private bool IsValid(DependencyObject obj)
{
// The dependency object is valid if it has no errors and all
// of its children (that are dependency objects) are error-free.
return !Validation.GetHasError(obj) &&
LogicalTreeHelper.GetChildren(obj)
.OfType<DependencyObject>()
.All(IsValid);
}
public static List<string> Errors { get; set; } = new();
public static bool IsValid(this DependencyObject parent)
{
Errors.Clear();
return IsValidInternal(parent);
}
private static bool IsValidInternal(DependencyObject parent)
{
// Validate all the bindings on this instance
bool valid = true;
if (Validation.GetHasError(parent) ||
GetRowsHasError(parent))
{
valid = false;
/*
* Find the error message and log it in the Errors list.
*/
foreach (var error in Validation.GetErrors(parent))
{
if (error.ErrorContent is string errorMessage)
{
Errors.Add(errorMessage);
}
else
{
if (parent is Control control)
{
Errors.Add($"<unknow error> on field `{control.Name}`");
}
else
{
Errors.Add("<unknow error>");
}
}
}
}
// Validate all the bindings on the children
for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (IsValidInternal(child) == false)
{
valid = false;
}
}
return valid;
}
private static bool GetRowsHasError(DependencyObject parent)
{
DataGridRow dataGridRow;
if (parent is not DataGrid dataGrid)
{
/*
* This is not a DataGrid, so return and say we do not have an error.
* Errors for this object will be checked by the normal check instead.
*/
return false;
}
foreach (var item in dataGrid.Items)
{
/*
* Not sure why, but under some conditions I was returned a null dataGridRow
* so I had to test for it.
*/
dataGridRow = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem(item);
if (dataGridRow != null &&
Validation.GetHasError(dataGridRow))
{
return true;
}
}
return false;
}