Виявлення помилок перевірки 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

Просто наткнувся на цю нитку. Дуже корисна маленька функція. Дякую!
Олав Хауген

Чи є спосіб перерахувати лише ті DependencyObjects, які були прив'язані до певного DataContext? Мені не подобається ідея прогулянки. Можливо, існує колекція прив’язок, пов'язаних з певним джерелом даних.
ЗАБ

5
Просто цікаво, як ви називаєте IsValidфункцію? Я бачу, ви налаштували таку, на CanExecuteяку я б здогадався, пов’язану з командою Зберегти. Чи спрацює це, якщо я не використовую команди? І як кнопка пов'язана з іншими елементами управління, які потрібно перевірити? Моя єдина думка про те, як це використовувати, - зателефонувавши IsValidдо кожного елемента керування, який потрібно перевірити. Редагувати: Схоже, ви перевіряєте, на senderякий я очікую кнопку збереження. Мені здається, це не так.
Ніколас Міллер

1
@ Nick Miller a Windowтакож є об'єктом залежності. Я, мабуть, налаштовує це разом із якимсь обробником подій на Window. Крім того , ви могли б просто назвати це безпосередньо IsValid(this)з Windowкласу.
akousmata

47

Наступний код (із книги програмування WPF від Chris Sell & Ian Griffiths) підтверджує всі обов'язкові правила щодо об'єкта залежності та його дітей:

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
Проголосуйте за рішення для роботи над моїми пунктами ControlControl.
Джефф Т.

1
Я використовую це рішення, щоб перевірити, чи є в моїй мережі даних помилки перевірки. Однак цей метод викликається моїм методом команд viewmodel canexecute, і я думаю, що доступ до об’єктів візуального дерева якимось чином порушує скоромовки 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. Нугет пакет тут .

Зразок:

<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

Можливо, вас зацікавить зразок програми BookLibrary у Рамковій програмі WPF (WAF) . Він показує, як використовувати перевірку в WPF та як керувати кнопкою Зберегти, коли є помилки перевірки.


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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.