检测WPF验证错误


115

在WPF中,您可以使用ExceptionValidationRule或根据数据绑定期间在数据层中引发的错误来设置验证DataErrorValidationRule

假设您以这种方式设置了一堆控件,并且有一个“保存”按钮。用户单击“保存”按钮时,需要确保没有验证错误,然后再继续保存。如果存在验证错误,则要大声疾呼。

在WPF中,如何查找是否有任何数据绑定控件设置了验证错误?

Answers:


137

这篇文章非常有帮助。感谢所有贡献者。这是您会喜欢或讨厌的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);
}

1
我非常喜欢这个特殊的解决方案!
ChristopheD

刚刚偶然发现了这个线程。非常有用的小功能。谢谢!
奥拉夫·豪根

有什么办法只能枚举那些绑定到特定DataContext的DependencyObject?我不喜欢树步道的想法。可能存在绑定到特定数据源的绑定的集合。
ZAB 2014年

5
只是想知道,您如何调用该IsValid函数?我看到您已经设置了一个CanExecute,我猜它与“保存”按钮的命令有关。如果我不使用命令,这可以工作吗?该按钮与需要检查的其他控件有何关系?我唯一想到的方法是调用IsValid每个需要验证的控件。编辑: 似乎您正在验证sender我希望是保存按钮。这似乎不适合我。
尼古拉斯·米勒

1
@Nick Miller a Window也是一个依赖对象。我可能是在上使用某种事件处理程序对其进行设置的Window。另外,您也可以直接IsValid(this)Window类中调用它。
akousmata 2015年

47

以下代码(来自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
   {

    ....
   }
}

33

使用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;
}

1
投票赞成使用我的ItemsControl的解决方案。
Jeff T.

1
我正在使用此解决方案来检查我的数据网格是否存在验证错误。但是,在我的viewmodel命令canexecute方法上调用了此方法,我认为访问可视树对象某种程度上违反了MVVM模式,不是吗?有其他选择吗?
Igor Kondrasovas '16

16

遇到相同的问题,并尝试了提供的解决方案。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;
    }

7

除了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());
}

考虑到可重用性,这非常好。


2

我会提供一个小的优化。

如果对同一控件多次执行此操作,则可以添加上面的代码以保留实际上具有验证规则的控件的列表。然后,每当您需要检查有效性时,只需遍历那些控件即可,而不是整个可视树。如果您有许多这样的控件,这将被证明会更好。


2

这是WPF中用于表单验证的Nuget包在这里

样品:

<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>

这个想法是我们通过附加的属性定义一个验证范围,告诉它要跟踪哪些输入控件。然后我们可以做:

<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>

0

您可以递归遍历所有控件树,并检查附加的属性Validation.HasErrorProperty,然后专注于在其中找到的第一个控件。

您还可以使用许多已经编写的解决方案,可以查看线程的示例和更多信息



0

在回答形式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;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.