J'ai rencontré récemment un problème (cf. ce post) avec la validation dans WPF : le framework de validation est prévu pour valider des bindings, et non pas directement des contrôles. Dans certains cas, ce n'est pas gênant, mais ça ne convenait pas à ce que je voulais faire.
J'ai donc mis en place un framework de validation "alternatif" pour valider des contrôles indépendemment du Binding. Au cas où ça intéresse quelqu'un, voilà les détails :
L'essentiel de la solution est implémenté dans une classe ValidationService (similaire à System.Windows.Controls.Validation) qui propose les propriétés attachées suivantes :
- Rule (ValidationRule) : la ValidationRule à utiliser sur le contrôle (évidemment ça pourrait être amélioré pour pouvoir en mettre plusieurs, mais pour mon besoin immédiat ce n'était pas nécessaire)
- Property (DependencyProperty) : la propriété à évaluer sur le contrôle
- HasError (bool) : indique que le contrôle comporte des erreurs
- ErrorText (string) : le texte de l'erreur (récupéré du résultat de la ValidationRule)
- GroupName (string) : le "groupe de validation" auquel appartient le contrôle, au cas ou on a besoin de valider les valeurs de plusieurs contrôle à la fois. Cette propriété est héritable, il suffit donc de l'appliquer à un élément parent du formulaire.
Ca utilise le même système d'ErrorTemplate que la validation WPF standard.
(Source en pièce jointe)
Pour l'utiliser, ça marche comme ça :
Code XML : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 <UserControl x:Class="MediaTek.Controls.MovieEditor" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:utilities="clr-namespace:MediaTek.Utilities" Height="420" Width="400"> <UserControl.Resources> <utilities:RequiredFieldValidationRule x:Key="validRequired"/> </UserControl.Resources> <StackPanel Orientation="Vertical" utilities:ValidationService.GroupName="movieEditorFields"> <TextBox Validation.ErrorTemplate="{staticResource tplFieldInError}" utilities:ValidationService.Rule="{StaticResource validRequired}" utilities:ValidationService.Property="{x:Static TextBox.TextProperty}" TextChanged="TextBox_TextChanged"> <TextBox.Style> <Style.Triggers> <Trigger Property="utilities:ValidationService.HasError" Value="True"> <Setter Property="ToolTip" Value="{Binding Path=(utilities:ValidationService.ErrorText), RelativeSource={x:Static RelativeSource.Self}}"/> </Trigger> </Style.Triggers> </TextBox.Style> </TextBox> <TextBox ... /> <ComboBox ... /> <Button Click="Valider_Click">Valider</Button> </StackPanel> </UserControl>
Le code des évènements :
Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 private void TextBox_TextChanged(object sender, TextChangedEventArgs e) { ValidationService.Validate(sender as DependencyObject); } public void Valider_Click(object sender, RoutedEventArgs e) { if (ValidationService.ValidateGroup("movieEditorFields")) { MessageBox.Show("OK"); } else { MessageBox.Show("Il y a des erreurs"); } }
A première vue ça peut sembler un peu lourd... plein de propriétés à définir pour chaque contrôle, des handlers d'évènement à écrire, etc. Mais en fait, on peut tout mettre dans des styles réutilisables (y compris les handlers d'évènement qu'on peut mettre dans le code-behind du style) :
Code XML : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 <Style x:Key="stlRequiredField" TargetType="{x:Type Control}"> <Setter Property="Validation.ErrorTemplate" Value="{StaticResource tplFieldInError}"/> <Setter Property="utilities:ValidationService.Rule" Value="{StaticResource validRequired}"/> <Style.Triggers> <Trigger Property="utilities:ValidationService.HasError" Value="True"> <Setter Property="ToolTip" Value="{Binding Path=(utilities:ValidationService.ErrorText), RelativeSource={x:Static RelativeSource.Self}}"/> </Trigger> </Style.Triggers> </Style> <Style x:Key="stlRequiredTextField" TargetType="{x:Type TextBox}" BasedOn="{StaticResource stlRequiredField}"> <Setter Property="utilities:ValidationService.Property" Value="{x:Static TextBox.TextProperty}"/> <EventSetter Event="TextChanged" Handler="stlTextField_TextChanged"/> </Style> <Style x:Key="stlRequiredComboField" TargetType="{x:Type ComboBox}" BasedOn="{StaticResource stlRequiredField}"> <Setter Property="utilities:ValidationService.Property" Value="{x:Static Selector.SelectedItemProperty}"/> <EventSetter Event="SelectionChanged" Handler="stlComboField_SelectionChanged"/> </Style> <Style x:Key="stlRequiredDateField" TargetType="{x:Type bagotricks:DatePicker}" BasedOn="{StaticResource stlRequiredField}"> <Setter Property="utilities:ValidationService.Property" Value="{x:Static bagotricks:DatePicker.ValueProperty}"/> <EventSetter Event="ValueChanged" Handler="stlDateField_ValueChanged"/> </Style>
Ensuite, plus qu'à affecter le style qui va bien au contrôle à valider. Si on a beaucoup de formulaires à valider ça fait gagner pas mal de temps...
Voilà, n'hésitez pas à l'utiliser si vous en avez l'usage, et/ou à me faire part de vos commentaires !
PS: pour les experts: pour la propriété attachée "Property", je pensais pouvoir mettre seulement le nom "court" de la propriété ("Text" par exemple), mais ça ne marche pas, je suis obligé d'écrire "{x:Static TextBox.TextProperty}". Pourtant ça fonctionne bien avec la propriété Property de Trigger ou de Setter... il suffit d'écrire "Text". Je ne comprends pas trop pourquoi ça ne marche pas dans mon cas (c'est le même type, DependencyProperty)...
PPS: Oui, je sais, c'est très crade ce que j'ai fait pour afficher l'ErrorTemplate...
J'utilise une méthode privée de la classe Validation, sur laquelle je suis tombé avec Reflector...
Partager