В WPF вы можете настроить проверку на основе ошибок, возникающих на уровне данных во время привязки данных, с помощью ExceptionValidationRule или DataErrorValidationRule.
Предположим, у вас есть набор элементов управления, настроенных таким образом, и у вас есть кнопка «Сохранить». Когда пользователь нажимает кнопку «Сохранить», вам необходимо убедиться, что нет ошибок проверки, прежде чем продолжить сохранение. Если есть ошибки проверки, вы хотите на них кричать.
Как в WPF узнать, есть ли в каких-либо элементах управления привязкой данных ошибки проверки?





Вы можете рекурсивно перебирать все свое дерево элементов управления и проверять прикрепленное свойство Validation.HasErrorProperty, а затем сосредоточиться на первом из них, которое вы найдете в нем.
вы также можете использовать множество уже написанных решений вы можете проверить поток это для примера и дополнительной информации
Следующий код (из книги «Программирование 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;
}
Проголосуйте за свое решение для работы с моим ItemsControl.
Я использую это решение, чтобы проверить, есть ли в моем сетке данных ошибки проверки. Однако этот метод вызывается в моем методе canexecute команды viewmodel, и я думаю, что доступ к объектам визуального дерева каким-то образом нарушает шаблон MVVM, не так ли? Любые альтернативы?
В форме ответа 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;
}
}
Была та же проблема и попробовала предоставленные решения. Комбинация решений 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;
}
Я бы предложил небольшую оптимизацию.
Если вы сделаете это много раз для одних и тех же элементов управления, вы можете добавить приведенный выше код, чтобы сохранить список элементов управления, которые действительно имеют правила проверки. Затем всякий раз, когда вам нужно проверить правильность, просматривайте только эти элементы управления, а не все визуальное дерево. Было бы намного лучше, если бы у вас было много таких элементов управления.
Возможно, вас заинтересует пример приложения КнигаБиблиотека для Платформа приложений WPF (WAF). В нем показано, как использовать проверку в 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);
}
Мне очень нравится именно это решение!
Просто наткнулся на эту ветку. Очень полезная маленькая функция. Спасибо!
Есть ли способ перечислить только те DependencyObject, которые были привязаны к определенному DataContext? Мне не нравится идея прогулки по деревьям. Может быть набор привязок, связанных с конкретным источником данных.
Просто интересно, как вы вызываете функцию IsValid? Я вижу, вы настроили CanExecute, что, как я полагаю, связано с командой кнопки «Сохранить». Будет ли это работать, если я не использую команды? И как кнопка связана с другими элементами управления, которые необходимо проверить? Я думал только о том, как использовать это, вызывая IsValid для каждого элемента управления, который необходимо проверить. Редактировать: Похоже, вы проверяете sender, который, как я ожидаю, будет кнопкой сохранения. Мне это кажется неправильным.
@Nick Miller Window также является объектом зависимости. Возможно, он настраивает это с помощью какого-то обработчика событий на Window. В качестве альтернативы вы можете просто вызвать его напрямую с помощью IsValid(this) из класса Window.
деф любить это !!
В дополнение к великолепной 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());
}
Это делает его очень приятным, учитывая возможность повторного использования.
Вот библиотека для проверки формы в 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>
Мне нравится этот ответ, потому что код также проверяет нетронутые привязки.