Validation de l’interface utilisateur WinForm

Je dois mettre en œuvre la validation des entrées dans toute mon application winform. Il y a beaucoup de formes différentes où les données peuvent être saisies et je ne voudrais pas contrôler par formulaire et créer isValid etc. par article. Comment les autres ont-ils traité cela?

Je constate que la plupart des publications associées traitent des applications Web et / ou mentionnent le bloc d’application de validation de la bibliothèque d’entreprise . Maintenant, j’avoue que je n’ai pas fait de recherches approfondies sur ELVAB mais cela semble exagéré pour ce dont j’ai besoin. Ma pensée actuelle est d’écrire une bibliothèque de classes avec les différentes exigences et de lui passer un contrôle en tant que paramètre. J’ai déjà une bibliothèque de fonctions RegEx pour des choses comme isValidZipCode, et cela peut être un endroit pour commencer.

Ce que je voudrais, c’est un bouton Valider que onClick fait défiler tous les contrôles de cette page de formulaire et effectue la validation nécessaire. Comment puis-je accomplir cela?

Dans ma propre application, je dois valider les dimensions au fur et à mesure de leur saisie. La séquence que j’ai utilisée est la suivante

  1. L’utilisateur sélectionne ou tape puis s’éloigne du contrôle.
  2. Le contrôle perd le focus et notifie la vue en envoyant son identifiant et le texte de l’entrée.
  3. La vue vérifie quel programme de forme (une classe implémentant une interface) a créé le formulaire et lui transmet l’ID et le texte d’entrée.
  4. Le programme Shape renvoie une réponse.
  5. Si la réponse est correcte, la vue met à jour l’entrée correcte de la classe de forme.
  6. Si la réponse est OK, la vue indique au formulaire via une interface qu’il est correct de déplacer le focus vers l’entrée suivante.
  7. Si la réponse n’est pas correcte, la vue examine la réponse et, à l’aide de l’interface de formulaire, indique au formulaire quoi faire. Cela signifie généralement que le focus revient à l’entrée incriminée avec un message indiquant à l’utilisateur ce qui s’est passé.

L’avantage de cette approche est que la validation est centralisée en un seul endroit pour un programme de forme donné. Je n’ai pas besoin de modifier chaque contrôle ou même de m’inquiéter des différents types de contrôles sur le formulaire. À l’époque où j’ai conçu le logiciel, j’ai décidé comment l’interface utilisateur fonctionnerait pour les zones de texte, les zones de liste, les listes déroulantes, etc. De même, différents niveaux de gravité sont traités différemment.

La vue prend en charge cette instruction au formulaire à suivre via l’interface. La manière dont il est implémenté est gérée par le formulaire lui-même dans son implémentation de l’interface. La vue ne se soucie pas si le formulaire affiche le jaune pour l’avertissement et le rouge pour l’erreur. Seulement qu’il gère ces deux niveaux. Plus tard, si une meilleure idée d’afficher des messages d’avertissement contre des erreurs se présente, je peux apporter le changement dans le formulaire lui-même plutôt avec la logique de vue ou avec le programme de validation de forme.

Vous êtes déjà à mi-chemin si vous envisagez de créer un cours dans le cadre de votre logique de validation, cela vous permettra de continuer à utiliser votre nouveau design.

La validation est déjà intégrée à la bibliothèque WinForms.

Chaque object dérivé de Control a deux événements nommés Validating and Validated . En outre, il a une propriété appelée CausesValidation . Lorsque cette option est définie sur true (elle est vraie par défaut), le contrôle participe à la validation. Sinon, ce n’est pas le cas.

La validation intervient dans le cadre de la mise au point. Lorsque vous désactivez un contrôle, ses événements de validation sont déclenchés. En fait, les événements de focus sont déclenchés dans un ordre spécifique. De MSDN :

Lorsque vous modifiez le focus à l’aide du clavier (TAB, MAJ + TAB, etc.), en appelant les méthodes Select ou SelectNextControl ou en définissant la propriété ContainerControl .. ::. ActiveControl sur le formulaire actuel, les événements de focus se produisent dans l’ordre suivant:

  1. Entrer
  2. Focalisé
  3. Laisser
  4. Valider
  5. Validé
  6. LostFocus

Lorsque vous modifiez le focus à l’aide de la souris ou en appelant la méthode Focus, les événements de focus se produisent dans l’ordre suivant:

  1. Entrer
  2. Focalisé
  3. LostFocus
  4. Laisser
  5. Valider
  6. Validé

Si la propriété CausesValidation est définie sur false, les événements Validating et Validated sont supprimés.

Si la propriété Cancel du CancelEventArgs est définie sur true dans le délégué d’événement Validating, tous les événements qui se produisent généralement après l’événement Validating sont supprimés.

De plus, un ContainerControl a une méthode appelée ValidateChildren() qui passera en revue les contrôles contenus et les validera.

Je me rends compte que ce fil est assez vieux mais je pensais que je posterais la solution que j’ai trouvée.

Le plus gros problème avec la validation sur WinForms est que la validation n’est exécutée que lorsque le contrôle a “perdu le focus”. Ainsi, l’utilisateur doit réellement cliquer dans une zone de texte, puis cliquer ailleurs pour que la routine de validation s’exécute. Cela ne pose aucun problème si vous êtes uniquement concerné par les données saisies. Mais cela ne fonctionne pas bien si vous essayez de vous assurer qu’un utilisateur n’a pas laissé une zone de texte vide en la sautant.

Dans ma solution, lorsque l’utilisateur clique sur le bouton d’envoi d’un formulaire, je vérifie chaque contrôle du formulaire (ou quel que soit le conteneur spécifié) et utilise la reflection pour déterminer si une méthode de validation est définie pour le contrôle. Si c’est le cas, la méthode de validation est exécutée. Si l’une des validations échoue, la routine renvoie un échec et permet au processus de s’arrêter. Cette solution fonctionne bien surtout si vous avez plusieurs formulaires à valider.

1) Il suffit de copier et coller cette section de code dans votre projet. Nous utilisons Reflection, vous devez donc append System.Reflection à vos instructions d’utilisation

 class Validation { public static bool hasValidationErrors(System.Windows.Forms.Control.ControlCollection controls) { bool hasError = false; // Now we need to loop through the controls and deterime if any of them have errors foreach (Control control in controls) { // check the control and see what it returns bool validControl = IsValid(control); // If it's not valid then set the flag and keep going. We want to get through all // the validators so they will display on the screen if errorProviders were used. if (!validControl) hasError = true; // If its a container control then it may have children that need to be checked if (control.HasChildren) { if (hasValidationErrors(control.Controls)) hasError = true; } } return hasError; } // Here, let's determine if the control has a validating method attached to it // and if it does, let's execute it and return the result private static bool IsValid(object eventSource) { ssortingng name = "EventValidating"; Type targetType = eventSource.GetType(); do { FieldInfo[] fields = targetType.GetFields( BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic); foreach (FieldInfo field in fields) { if (field.Name == name) { EventHandlerList eventHandlers = ((EventHandlerList)(eventSource.GetType().GetProperty("Events", (BindingFlags.FlattenHierarchy | (BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(eventSource, null))); Delegate d = eventHandlers[field.GetValue(eventSource)]; if ((!(d == null))) { Delegate[] subscribers = d.GetInvocationList(); // ok we found the validation event, let's get the event method and call it foreach (Delegate d1 in subscribers) { // create the parameters object sender = eventSource; CancelEventArgs eventArgs = new CancelEventArgs(); eventArgs.Cancel = false; object[] parameters = new object[2]; parameters[0] = sender; parameters[1] = eventArgs; // call the method d1.DynamicInvoke(parameters); // if the validation failed we need to return that failure if (eventArgs.Cancel) return false; else return true; } } } } targetType = targetType.BaseType; } while (targetType != null); return true; } } 

2) Utilisez l’événement de validation standard sur tous les contrôles que vous souhaitez valider. Soyez sûr d’utiliser e.Cancel lorsque la validation échoue!

 private void txtLastName_Validating(object sender, CancelEventArgs e) { if (txtLastName.Text.Trim() == Ssortingng.Empty) { errorProvider1.SetError(txtLastName, "Last Name is Required"); e.Cancel = true; } else errorProvider1.SetError(txtLastName, ""); } 

3) Ne sautez pas cette étape! Définissez la propriété AutoValidate du formulaire sur EnableAllowFocusChange . Cela permettra la tabulation vers un autre contrôle même lorsque la validation échoue.

4) Enfin, dans votre méthode Submit Button, appelez la méthode Validation et spécifiez le conteneur à vérifier. Il peut s’agir de la forme entière ou simplement d’un conteneur sur la fiche, comme un panneau ou un groupe.

 private void btnSubmit_Click(object sender, EventArgs e) { // the controls collection can be the whole form or just a panel or group if (Validation.hasValidationErrors(frmMain.Controls)) return; // if we get here the validation passed this.close(); } 

Heureux codage!

Je ne voudrais pas avoir à contrôler par formulaire et créer isValid etc par article.

En tant que niveau, vous devrez définir ce que signifie être valid pour chaque contrôle, à moins que tout ce qui vous intéresse ne concerne pas la valeur du contrôle.

Cela dit, vous pouvez utiliser un composant ErrorProvider qui fonctionne très bien.

Nous avons eu de la chance avec le Noogen ValidationProvider . C’est simple pour les cas simples (vérifications de type de données et champs obligatoires) et facile à append à la validation personnalisée pour les cas plus complexes.

Dans tous mes formulaires, j’implémente l’événement isValidating pour le contrôle particulier en question et si les données ne valident pas, j’ai un errorProvider sur le formulaire et j’utilise sa méthode SetError (…) pour définir l’erreur sur le contrôle. en question avec des informations pertinentes sur la raison pour laquelle il a tort.

edit> Je dois noter que j’utilise généralement le modèle mvc pour ce faire, donc la validation spécifique de ce contrôle / membre du modèle se produit au niveau du modèle, de sorte que isValidating ressemble à ceci:

 private uicontrol_isValidating(...) { if(!m_Model.MemberNameIsValid()) { errorProvider.SetError(...); } } 

Soit de cette façon. Ou vous pouvez avoir un seul événement de validation associé à tous ou à des contrôles nécessitant des validations similaires. Cela supprimera la boucle du code. Disons que vous avez quatre zones de texte qui peuvent avoir uniquement un entier. Ce que vous pouvez faire, c’est avoir un seul événement pour chacun d’eux. Je n’ai pas d’IDE, donc le code ci-dessous est le meilleur que je puisse trouver.

 this.textbox1.Validated +=  this.textbox2.Validated +=  this.textbox3.Validated +=  this.textbox4.Validated +=  

Dans l’éventualité:

  1. Lancer l’expéditeur en tant que zone de texte.
  2. Vérifiez si la valeur dans la zone de texte est numérique.

Et ainsi de suite, vous avez des événements alignés.

J’espère que cela t’aides.

Si vous combinez les idées ci-dessus avec un gestionnaire d’événement de validation générique, vous obtiendrez un «cadre» d’erreur de validation avec toutes les méthodes de validation de vos classes professionnelles. Je viens d’étendre le code de Bruce avec l’idée danoise. Cela a été fait pour les composants Entity Framework et Dev Express, mais ces dépendances peuvent être facilement supprimées. Prendre plaisir!

 public class ValidationManager { ///  /// Call this method to validate all controls of the given control list /// Validating event will be called on each one ///  ///  ///  public static bool HasValidationErrors(System.Windows.Forms.Control.ControlCollection controls) { bool hasError = false; // Now we need to loop through the controls and deterime if any of them have errors foreach (Control control in controls) { // check the control and see what it returns bool validControl = IsValid(control); // If it's not valid then set the flag and keep going. We want to get through all // the validators so they will display on the screen if errorProviders were used. if (!validControl) hasError = true; // If its a container control then it may have children that need to be checked if (control.HasChildren) { if (HasValidationErrors(control.Controls)) hasError = true; } } return hasError; } ///  /// Attach all youe Validating events to this event handler (if the controls requieres validation) /// A method with name Validate + PropertyName will be searched on the binded business entity, and if found called /// Throw an exception with the desired message if a validation error is detected in your method logic ///  ///  ///  public static void ValidationHandler(object sender, CancelEventArgs e) { BaseEdit control = sender as BaseEdit; if (control.DataBindings.Count > 0) //control is binded { ssortingng bindedFieldName = control.DataBindings[0].BindingMemberInfo.BindingField; object bindedObject = control.BindingManager.Current; if (bindedObject != null) //control is binded to an object instance { //find and call method with name = Validate + PropertyName MethodInfo validationMethod = (from method in bindedObject.GetType().GetMethods() where method.IsPublic && method.Name == Ssortingng.Format("Validate{0}",bindedFieldName) && method.GetParameters().Count() == 0 select method).FirstOrDefault(); if (validationMethod != null) //has validation method { try { validationMethod.Invoke(bindedObject, null); control.ErrorText = Ssortingng.Empty; //property value is valid } catch (Exception exp) { control.ErrorText = exp.InnerException.Message; e.Cancel = true; } } } } } // Here, let's determine if the control has a validating method attached to it // and if it does, let's execute it and return the result private static bool IsValid(object eventSource) { ssortingng name = "EventValidating"; Type targetType = eventSource.GetType(); do { FieldInfo[] fields = targetType.GetFields( BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic); foreach (FieldInfo field in fields) { if (field.Name == name) { EventHandlerList eventHandlers = ((EventHandlerList)(eventSource.GetType().GetProperty("Events", (BindingFlags.FlattenHierarchy | (BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(eventSource, null))); Delegate d = eventHandlers[field.GetValue(eventSource)]; if ((!(d == null))) { Delegate[] subscribers = d.GetInvocationList(); // ok we found the validation event, let's get the event method and call it foreach (Delegate d1 in subscribers) { // create the parameters object sender = eventSource; CancelEventArgs eventArgs = new CancelEventArgs(); eventArgs.Cancel = false; object[] parameters = new object[2]; parameters[0] = sender; parameters[1] = eventArgs; // call the method d1.DynamicInvoke(parameters); // if the validation failed we need to return that failure if (eventArgs.Cancel) return false; else return true; } } } } targetType = targetType.BaseType; } while (targetType != null); return true; } } 

Méthode de validation de l’échantillon:

 partial class ClientName { public void ValidateFirstName() { if (Ssortingng.IsNullOrWhiteSpace(this.FirstName)) throw new Exception("First Name is required."); } public void ValidateLastName() { if (Ssortingng.IsNullOrWhiteSpace(this.LastName)) throw new Exception("Last Name is required."); } } 

Faire défiler les contrôles peut fonctionner mais il est sujet aux erreurs. J’ai travaillé sur un projet qui utilisait cette technique (il s’agissait d’un projet Delphi et non de C #) et il fonctionnait comme prévu, mais il était très difficile de le mettre à jour si un contrôle était ajouté ou modifié. Cela peut avoir été corrigible. Je ne suis pas sûr.

En tout cas, cela a fonctionné en créant un gestionnaire d’événement unique qui était ensuite attaché à chaque contrôle. Le gestionnaire utilise alors RTTI pour déterminer le type du contrôle. Ensuite, il utilisera la propriété name du contrôle dans une grande instruction select pour rechercher le code de validation à exécuter. Si la validation a échoué, un message d’erreur a été envoyé à l’utilisateur et le contrôle a reçu le focus. Pour rendre les choses plus complexes, le formulaire a été divisé en plusieurs tabs et l’onglet approprié devait être visible pour que le contrôle enfant puisse obtenir le focus.

Donc c’est mon expérience.

Je préfère plutôt utiliser un modèle de conception de vue passive pour supprimer toutes les règles métier du formulaire et les transférer dans une classe Presenter. Selon l’état de votre formulaire, cela peut représenter plus de travail que vous êtes prêt à investir.

Juste une idée approximative:

void btnValidate_Click(object sender, EventArgs e) { foreach( Control c in this.Controls ) { if( c is TextBox ) { TextBox tbToValidate = (TextBox)c; Validate(tbToValidate.Text); } } }
void btnValidate_Click(object sender, EventArgs e) { foreach( Control c in this.Controls ) { if( c is TextBox ) { TextBox tbToValidate = (TextBox)c; Validate(tbToValidate.Text); } } } 

Vous pouvez coller les zones de texte à l’intérieur d’un panneau et ne faire que boucler les contrôles si vous souhaitez éviter de passer en boucle avec d’autres contrôles.

Pourquoi n’utilisez-vous pas un événement de validation? Vous pouvez avoir un seul événement de validation et valider les contrôles. Il ne sera pas nécessaire d’utiliser des boucles et chaque contrôle sera validé au fur et à mesure de la saisie des données.