Обнаружение ошибок проверки WPF
-
02-07-2019 - |
Вопрос
В WPF вы можете настроить проверку на основе ошибок, возникающих на вашем уровне данных во время привязки данных, используя команду ExceptionValidationRule
или 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);
}
Другие советы
Следующий код (из книги Криса Селла и Яна Гриффитса «Программирование 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;
}
Помимо великолепной реализации 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());
}
Это делает его чрезвычайно удобным, учитывая возможность повторного использования.
Я бы предложил небольшую оптимизацию.
Если вы делаете это много раз с одними и теми же элементами управления, вы можете добавить приведенный выше код, чтобы сохранить список элементов управления, которые действительно имеют правила проверки.Затем, когда вам понадобится проверить достоверность, просматривайте только эти элементы управления, а не все визуальное дерево.Было бы намного лучше, если бы у вас было много таких элементов управления.
Вот библиотека для проверки формы в 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>
Вы можете рекурсивно перебрать все дерево элементов управления и проверить прикрепленное свойство Validation.HasErrorProperty, а затем сосредоточиться на первом найденном в нем свойстве.
Вы также можете использовать много уже написанных решений, которые вы можете проверить этот тема для примера и дополнительной информации
Возможно, вас заинтересует КнигаБиблиотека образец заявления о Платформа приложений WPF (WAF).В нем показано, как использовать проверку в WPF и как управлять кнопкой «Сохранить» при наличии ошибок проверки.
В форме ответа, вместо того, чтобы явно перебирать правила проверки, лучше просто вызвать 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;
}
}