Dans WPF, vous pouvez configurer la validation en fonction des erreurs générées dans votre couche de données lors de la liaison de données à l'aide de ExceptionValidationRule
ou DataErrorValidationRule
.
Supposons que vous disposiez de plusieurs commandes de cette manière et que vous disposiez d'un bouton Enregistrer. Lorsque l'utilisateur clique sur le bouton Enregistrer, vous devez vous assurer qu'il n'y a aucune erreur de validation avant de procéder à l'enregistrement. S'il y a des erreurs de validation, vous voulez les crier.
Dans WPF, comment savoir si l'un de vos contrôles Data Bound a des erreurs de validation définies?
Ce message a été extrêmement utile. Merci à tous ceux qui ont contribué. Voici une version LINQ que vous aimerez ou détesterez.
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);
}
Le code suivant (extrait du livre Programming WPF de Chris Sell et Ian Griffiths) valide toutes les règles de liaison sur un objet de dépendance et ses enfants:
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;
}
}
Vous pouvez appeler cela dans votre bouton d'enregistrement, cliquez sur le gestionnaire d'événements comme ceci dans votre page/fenêtre
private void saveButton_Click(object sender, RoutedEventArgs e)
{
if (Validator.IsValid(this)) // is valid
{
....
}
}
Le code publié ne fonctionnait pas pour moi lors de l'utilisation d'un ListBox. Je l'ai réécrit et maintenant ça marche:
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;
}
Eu le même problème et essayé les solutions fournies. Une combinaison des solutions de H-Man2 et de skiba_k a très bien fonctionné pour moi, à une exception près: Ma fenêtre a un TabControl. Et les règles de validation ne sont évaluées que pour le TabItem qui est actuellement visible. J'ai donc remplacé VisualTreeHelper par LogicalTreeHelper. Maintenant ça marche.
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;
}
En plus de la grande implémentation LINQ de Dean, je me suis amusé à envelopper le code dans une extension pour DependencyObjects:
public static bool IsValid(this DependencyObject instance)
{
// Validate recursivly
return !Validation.GetHasError(instance) && LogicalTreeHelper.GetChildren(instance).OfType<DependencyObject>().All(child => child.IsValid());
}
Cela le rend extrêmement agréable compte tenu de la réutilisation.
Je proposerais une petite optimisation.
Si vous effectuez cette opération plusieurs fois sur les mêmes contrôles, vous pouvez ajouter le code ci-dessus pour conserver une liste des contrôles qui ont réellement des règles de validation. Ensuite, chaque fois que vous devez vérifier la validité, ne parcourez que ces contrôles, au lieu de l'arborescence visuelle entière. Cela s'avérerait beaucoup mieux si vous disposez de nombreux contrôles de ce type.
Voici un bibliothèque pour la validation de formulaire dans WPF. Paquet Nuget ici .
Échantillon:
<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>
L'idée est que nous définissons une étendue de validation via la propriété jointe en lui indiquant les contrôles d'entrée à suivre. Ensuite, nous pouvons faire:
<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>
Sous forme de réponse, au lieu de parcourir explicitement les règles de validation, mieux vaut simplement appeler 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;
}
}
Vous pouvez parcourir de manière récursive l'ensemble de votre arborescence de contrôles et vérifier la propriété attachée Validation.HasErrorProperty, puis vous concentrer sur la première que vous y trouvez.
vous pouvez également utiliser de nombreuses solutions déjà écrites que vous pouvez vérifier this thread pour un exemple et plus d'informations
Vous pourriez être intéressé par l'exemple d'application BookLibrary de WPF Application Framework (WAF) = . Il montre comment utiliser la validation dans WPF et comment contrôler le bouton Enregistrer en cas d'erreurs de validation.