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

115

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

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

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

Кевин Берридж
источник

Ответы:

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
Просто наткнулся на эту ветку. Очень полезная маленькая функция. Спасибо!
Olav Haugen
Есть ли способ перечислить только те DependencyObject, которые были привязаны к определенному DataContext? Мне не нравится идея прогулки по деревьям. Может существовать набор привязок, связанных с конкретным источником данных.
ZAB
5
Просто интересно, как вы вызываете IsValidфункцию? Я вижу, вы настроили команду, CanExecuteкоторая, как я полагаю, связана с командой кнопки «Сохранить». Будет ли это работать, если я не использую команды? И как кнопка связана с другими элементами управления, которые необходимо проверить? Я думал только о том, как использовать это, вызывая IsValidкаждый элемент управления, который необходимо проверить. Изменить: похоже, вы подтверждаете, senderчто я ожидаю, что это кнопка сохранения. Мне это кажется неправильным.
Николас Миллер
1
@Nick Miller a Windowтакже является объектом зависимости. Я, вероятно, настраивает его с помощью какого-то обработчика событий на Window. В качестве альтернативы вы можете просто вызвать его напрямую IsValid(this)из Windowкласса.
akousmata
47

Следующий код (из книги «Программирование 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
   {

    ....
   }
}
aogan
источник
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;
}
Н-man2
источник
1
Проголосуйте за свое решение для работы с моим ItemsControl.
Джефф Т.
1
Я использую это решение, чтобы проверить, есть ли в моем сетке данных ошибки проверки. Однако этот метод вызывается в моем методе canexecute команды viewmodel, и я думаю, что доступ к объектам визуального дерева каким-то образом нарушает шаблон MVVM, не так ли? Любые альтернативы?
Игорь Кондрасовас
16

Была та же проблема и попробовала предоставленные решения. Комбинация решений 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;
    }

источник
7

В дополнение к великолепной 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());
}

Это делает его очень приятным, учитывая возможность повторного использования.

Маттиас Лорке
источник
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, а затем сосредоточиться на первом из них, которое вы найдете в нем.

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

user21243
источник
0

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

JBE
источник
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;
    }
}
Дэн возится при свете костра
источник