Обнаружение ошибок проверки WPF

в WPF вы можете настроить проверку на основе ошибок, возникших в слое данных во время привязки данных, используя ExceptionValidationRule или DataErrorValidationRule.

предположим, что у вас куча контроля и у тебя была кнопка Сохранить. Когда пользователь нажимает кнопку Сохранить, перед продолжением сохранения необходимо убедиться в отсутствии ошибок проверки. Если есть ошибки проверки, вы хотите кричать на них.

в WPF, как вы узнаете, связан ли какой-либо из ваших данных контроль ошибок валидации?

10 ответов


этот пост был очень полезен. Спасибо всем, кто внес свой вклад. Вот версия 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);
}

следующий код (из книги программирования 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 в том, что в настоящее время видны. Поэтому Я заменил VisualTreeHelper на LogicalTreeHelper. Теперь это работает.

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

в дополнение к большой LINQ-реализации Dean, мне было весело обернуть код в расширение для DependencyObjects:

public static bool IsValid(this DependencyObject instance)
{
   // Validate recursivly
   return !Validation.GetHasError(instance) &&  LogicalTreeHelper.GetChildren(instance).OfType<DependencyObject>().All(child => child.IsValid());
}

это делает его чрезвычайно приятным, учитывая reuseablity.


Я бы предложил небольшую оптимизацию.

Если вы делаете это много раз за те же элементы управления, вы можете добавить приведенный выше код, чтобы сохранить список элементов управления, которые на самом деле имеют правила проверки. Затем, когда вам нужно проверить правильность, перейдите только через эти элементы управления, а не по всему визуальному дереву. Это было бы намного лучше, если бы у вас было много таких элементов управления.


здесь библиотека для проверки формы в 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>

вы можете перебирать все дерево элементов управления рекурсивно и проверять проверку вложенного свойства.HasErrorProperty, затем сосредоточьтесь на первом, который вы найдете в нем.

вы также можете использовать многие уже написанные решения вы можете проверить этой поток для примера и дополнительной информации


вас может заинтересовать BookLibrary пример применения WPF Application Framework (WAF). Он показывает, как использовать проверку в WPF и как управлять кнопкой Сохранить при наличии ошибок проверки.


в форме ответа 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;
    }
}