web-dev-qa-db-fra.com

Validation de l'interface utilisateur WinForm

Je dois implémenter la validation des entrées dans toute mon application Winform. Il existe de nombreux formulaires différents où les données peuvent être saisies et je voudrais ne pas aller contrôle par contrôle par formulaire et créer isValid etc. par article. Comment les autres ont-ils géré cela?

Je constate que la plupart des articles liés traitent des applications Web et/ou mentionnent Enterprise Library Validation Application Block . Maintenant, j'avoue que je n'ai pas fait de recherches approfondies sur ELVAB mais il semble comme une surpuissance 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 comme paramètre. J'ai déjà une bibliothèque de fonctions RegEx pour des choses comme isValidZipCode et ainsi de suite, ce qui peut être un endroit pour commencer.

Ce que j'aimerais avoir, c'est un bouton Valider qui onClick parcourt tous les contrôles sur cette page de formulaire et effectue la validation nécessaire. Comment puis-je accomplir cela?

42
Refracted Paladin

Dans ma propre application, je dois valider les dimensions au fur et à mesure qu'elles sont saisies. 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 ID et le texte d'entrée.
  3. La vue vérifie quel programme Shape (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 OK, 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 OK de déplacer le focus sur l'entrée suivante.
  7. Si la réponse n'est pas correcte, la vue examine la réponse et l'utilisation de l'interface de formulaire indique au formulaire ce qu'il doit faire. Cela signifie généralement que le focus revient à l'entrée incriminée avec un message affiché 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 d'aller modifier chaque contrôle ou même de vraiment m'inquiéter des différents types de contrôles sur le formulaire. Il y a très longtemps, lorsque j'ai conçu le logiciel, j'ai décidé du fonctionnement de l'interface utilisateur pour les zones de texte, les zones de liste, les zones de liste déroulante, etc. De plus, différents niveaux de gravité sont traités différemment.

La vue s'en charge en indiquant au formulaire ce qu'il doit faire via l'interface. La façon dont il est réellement 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, il gère ces deux niveaux. Plus tard, si une meilleure idée d'afficher les avertissements par rapport aux erreurs se présente, je peux effectuer le changement dans le formulaire lui-même plutôt en contournant la logique d'affichage ou le programme de validation dans le programme de forme.

Vous êtes déjà à mi-chemin si vous envisagez de créer une classe pour conserver votre logique de validation, cela vous permettra de continuer dans votre nouvelle conception.

8
RS Conley

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

Chaque objet dérivé Control a deux événements nommés Validating et Validated. Il possède également une propriété appelée CausesValidation . Lorsque ce paramètre est défini sur true (il est vrai par défaut), le contrôle participe à la validation. Sinon, ce n'est pas le cas.

La validation intervient dans le cadre de la concentration. Lorsque vous vous concentrez hors d'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, SHIFT + 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ée
  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ée

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

Si la propriété Cancel de 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.

Un ContainerControl a également une méthode appelée ValidateChildren() qui parcourra les contrôles contenus et les validera.

62
Matt Brunell

Je me rends compte que ce fil est assez ancien mais je pensais publier 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". L'utilisateur doit donc cliquer à l'intérieur d'une zone de texte, puis cliquer ailleurs pour que la routine de validation s'exécute. C'est très bien si vous ne vous souciez que des données entrées qui sont correctes. 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 j'utilise la réflexion pour déterminer si une méthode de validation est définie pour le contrôle. Si tel 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) Copiez et collez simplement cette section de code dans votre projet. Nous utilisons Reflection, vous devez donc ajouter System.Reflection à vos instructions using

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)
    {
        string 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 tout contrôle que vous souhaitez valider. Assurez-vous d'utiliser e.Cancel lorsque la validation échoue!

private void txtLastName_Validating(object sender, CancelEventArgs e)
    {
        if (txtLastName.Text.Trim() == String.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 de tabuler 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 que vous souhaitez vérifier. Il peut s'agir du formulaire entier, ou simplement d'un conteneur sur le formulaire 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();
    }

Codage heureux!

42
Bruce

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

À un certain niveau, vous devrez définir ce que signifie être valid pour chaque contrôle, à moins que vous ne vous souciez que du contrôle qui a une valeur quelconque.

Cela dit, il y a un composant ErrorProvider que vous pouvez utiliser qui fonctionne plutôt bien.

4
Joel Coehoorn

Nous avons eu de la chance avec le Noogen ValidationProvider . C'est simple pour les cas simples (vérifications des types de données et champs obligatoires) et facile d'ajouter une validation personnalisée pour les cas plus complexes.

3
Jamie Ide

Si vous combinez les idées ci-dessus avec ce gestionnaire d'événements de validation générique, vous obtiendrez un bon "cadre" d'erreur de validation avec toutes les méthodes de validation dans vos classes métier. Je viens d'étendre le code Bruce avec une 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
{
    /// <summary>
    /// Call this method to validate all controls of the given control list 
    /// Validating event will be called on each one
    /// </summary>
    /// <param name="controls"></param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 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
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public static void ValidationHandler(object sender, CancelEventArgs e)
    {
        BaseEdit control = sender as BaseEdit;

        if (control.DataBindings.Count > 0) //control is binded
        {
            string 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 == String.Format("Validate{0}",bindedFieldName) &&
                                                     method.GetParameters().Count() == 0
                                               select method).FirstOrDefault();

                if (validationMethod != null) //has validation method
                {
                    try
                    {
                        validationMethod.Invoke(bindedObject, null);

                        control.ErrorText = String.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)
    {
        string 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;
    }

}

Exemple de méthode de validation:

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

    public void ValidateLastName()
    {
        if (String.IsNullOrWhiteSpace(this.LastName))
            throw new Exception("Last Name is required.");
    }
}
2
Rey

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 pour expliquer pourquoi il est faux.

modifier> Je dois noter que j'utilise généralement le modèle mvc lors de cette opération, de sorte que la validation spécifique pour ce contrôle/membre du modèle se produit au niveau du modèle, de sorte que isValidating ressemble un peu à ceci:

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

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

this.textbox1.Validated += <ValidatedEvent>
this.textbox2.Validated += <ValidatedEvent>
this.textbox3.Validated += <ValidatedEvent>
this.textbox4.Validated += <ValidatedEvent>

Dans l'éventualité:

  1. Envoyer 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.

2
danish

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);
    }
  }
}

Vous pouvez coller les zones de texte à l'intérieur d'un panneau et y parcourir uniquement les contrôles si vous voulez éviter de parcourir d'autres contrôles.

1
Will Eddins

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

1
danish

Faire défiler les contrôles peut fonctionner, mais il est sujet aux erreurs. J'ai travaillé sur un projet qui utilisait cette technique (à condition que ce soit un projet Delphi et non C #) et qu'il fonctionnait comme prévu, mais il était très difficile de mettre à jour si un contrôle était ajouté ou modifié. Cela a peut-être pu être corrigé. Je ne suis pas sûr.

Quoi qu'il en soit, cela a fonctionné en créant un gestionnaire d'événements unique qui a ensuite été attaché à chaque contrôle. Le gestionnaire utiliserait alors RTTI pour déterminer le type du contrôle. Ensuite, il utiliserait la propriété name du contrôle dans une grande instruction select pour trouver 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 était divisé en plusieurs onglets et le bon onglet devait être visible pour que le contrôle enfant ait le focus.

Voilà donc mon expérience.

Je préférerais de loin utiliser un modèle de conception Passive View pour supprimer toutes les règles métier du formulaire et les pousser dans une classe Presenter. Selon l'état de votre formulaire, cela peut être plus de travail que votre volonté d'investir.

1
Kenneth Cochran