Detecção de Erros WPF Validação
-
02-07-2019 - |
Pergunta
Em WPF você pode validação de configuração com base em erros lançados em sua camada de dados durante a vinculação de dados usando o ExceptionValidationRule
ou DataErrorValidationRule
.
Suponha que você tinha um monte de controles criados desta forma e você tinha um botão Salvar. Quando o usuário clica no botão Save, você precisa ter certeza de que não há erros de validação antes de prosseguir com o salvamento. Se houver erros de validação, você quer gritar com eles.
Em WPF, como você descobrir se algum de seus controles ligados a dados têm erros de validação definido?
Solução
Este post foi extremamente útil. Obrigado a todos que contribuíram. Aqui está uma versão LINQ que você vai amar ou odiar.
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);
}
Outras dicas
O código a seguir (de Programação WPF livro de Chris Sell & Ian Griffiths) valida todas as regras obrigatórias para um objeto de dependência e seus filhos:
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;
}
}
Você pode chamar isso no seu save clique de botão manipulador de eventos como este em sua página / janela
private void saveButton_Click(object sender, RoutedEventArgs e)
{
if (Validator.IsValid(this)) // is valid
{
....
}
}
O código postado não funcionou para mim quando se usa um ListBox. Eu reescrevi-lo e agora ele funciona:
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;
}
teve o mesmo problema e tentou as soluções fornecidas. A combinação de soluções da skiba_k H-Man2 de e trabalhou quase bem para mim, para uma exceção: My Window tem um TabControl. E só as regras de validação obter avaliados para o TabItem que está atualmente visível. Então eu substituído VisualTreeHelper por LogicalTreeHelper. Agora ele funciona.
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;
}
Além da grande LINQ-implementação de Dean, eu me diverti envolvendo o código em uma extensão para DependencyObjects:
public static bool IsValid(this DependencyObject instance)
{
// Validate recursivly
return !Validation.GetHasError(instance) && LogicalTreeHelper.GetChildren(instance).OfType<DependencyObject>().All(child => child.IsValid());
}
Isto torna extremamente agradável reuseablity considerando.
Gostaria de oferecer uma pequena otimização.
Se você fizer isso muitas vezes nos mesmos controles, você pode adicionar o código acima para manter uma lista de controles que realmente têm regras de validação. Então sempre que você precisa verificar a validade, só vão sobre esses controles, em vez de toda a árvore visual. Este viria a ser muito melhor se você tem muitos desses controles.
Aqui é um biblioteca para validação de formulário no WPF. pacote Nuget aqui .
Amostra:
<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>
A idéia é que podemos definir um escopo de validação através da propriedade anexada dizendo o que controles de entrada para acompanhar. Então, podemos fazer:
<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>
Você pode iterar sobre toda a sua árvore de controles de forma recursiva e verificar a propriedade anexada Validation.HasErrorProperty, então o foco sobre o primeiro que encontrar nele.
Você também pode usar muitas soluções já escritos você pode verificar este thread para um exemplo e obter mais informações
Você pode estar interessado no BookLibrary aplicação de exemplo do WPF Application Framework ( WAF) . Ele mostra como usar a validação em WPF e como controlar o botão Salvar quando existe erros de validação.
Em forma de resposta aogan, em vez de explicitamente iterate através de regras de validação, melhor apenas invocar 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;
}
}