ExceptionValidationRule
在 WPF 中,您可以使用或设置基于数据绑定期间数据层中抛出的错误的验证DataErrorValidationRule
。
假设您以这种方式设置了一堆控件,并且有一个保存按钮。当用户单击“保存”按钮时,您需要确保在继续保存之前没有验证错误。如果存在验证错误,您想对它们大喊大叫。
在 WPF 中,如何确定是否有任何数据绑定控件设置了验证错误?
ExceptionValidationRule
在 WPF 中,您可以使用或设置基于数据绑定期间数据层中抛出的错误的验证DataErrorValidationRule
。
假设您以这种方式设置了一堆控件,并且有一个保存按钮。当用户单击“保存”按钮时,您需要确保在继续保存之前没有验证错误。如果存在验证错误,您想对它们大喊大叫。
在 WPF 中,如何确定是否有任何数据绑定控件设置了验证错误?
这篇文章非常有帮助。感谢所有做出贡献的人。这是一个您会喜欢或讨厌的 LINQ 版本。
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);
}
以下代码(来自 Chris Sell 和 Ian Griffiths 的 Programming 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
{
....
}
}
使用 ListBox 时,发布的代码对我不起作用。我重写了它,现在它可以工作了:
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;
}
有同样的问题并尝试了提供的解决方案。H-Man2 和skiba_k 的解决方案组合对我来说几乎可以正常工作,但有一个例外:我的窗口有一个TabControl。并且验证规则仅针对当前可见的 TabItem 进行评估。所以我用LogicalTreeHelper替换了VisualTreeHelper。现在它起作用了。
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;
}
除了 Dean 出色的 LINQ 实现之外,我还很高兴将代码包装到 DependencyObjects 的扩展中:
public static bool IsValid(this DependencyObject instance)
{
// Validate recursivly
return !Validation.GetHasError(instance) && LogicalTreeHelper.GetChildren(instance).OfType<DependencyObject>().All(child => child.IsValid());
}
This makes it extremely nice considering reuseablity.
我会提供一个小的优化。
如果您对同一个控件多次执行此操作,则可以添加上述代码以保留实际具有验证规则的控件列表。然后,每当您需要检查有效性时,只检查那些控件,而不是整个可视化树。如果您有许多这样的控件,这将证明会好得多。
Here is a library for form validation in WPF. Nuget package here.
Sample:
<Border BorderBrush="{Binding Path=(validationScope:Scope.HasErrors),
Converter={local:BoolToBrushConverter},
ElementName=Form}"
BorderThickness="1">
<StackPanel x:Name="Form" validationScope:Scope.ForInputTypes="{x:Static validationScope:InputTypeCollection.Default}">
<TextBox Text="{Binding SomeProperty}" />
<TextBox Text="{Binding SomeOtherProperty}" />
</StackPanel>
</Border>
The idea is that we define a validation scope via the attached property telling it what input controls to track. Then we can do:
<ItemsControl ItemsSource="{Binding Path=(validationScope:Scope.Errors),
ElementName=Form}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type ValidationError}">
<TextBlock Foreground="Red"
Text="{Binding ErrorContent}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
您可以递归地遍历所有控件树并检查附加属性 Validation.HasErrorProperty,然后专注于您在其中找到的第一个。
您还可以使用许多已经编写的解决方案您可以查看此线程以获取示例和更多信息
在回答形式 aogan 中,与其显式地遍历验证规则,不如直接调用expression.UpdateSource():
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;
}
}
您可能对WPF 应用程序框架 (WAF)的BookLibrary示例应用程序感兴趣。它展示了如何在 WPF 中使用验证,以及在存在验证错误时如何控制“保存”按钮。