Validation de l'interface utilisateur WinForm

Je dois implémenter la validation d'entrée dans mon application winform. Il existe de nombreuses formes différentes où les données peuvent être saisies et je voudrais ne pas aller contrôler par contrôle par formulaire et créer isValid etc par élément. Comment les autres ont-ils géré cela?

Je vois que la plupart des articles connexes traitent des applications Web et / ou mentionnent Bloc D'Application de Validation de bibliothèque D'entreprise . Maintenant, j'admets que je n'ai pas fait de recherches approfondies sur ELVAB, mais il semble comme exagéré pour ce dont j'ai besoin. Mon la pensée actuelle est d'écrire une bibliothèque de classes avec les différentes exigences et de lui transmettre un contrôle en tant que paramètre. J'ai déjà une bibliothèque de fonctions RegEx pour des choses comme isValidZipCode et telles que cela peut être un endroit pour commencer.

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

41
demandé sur Mohit Arora 2009-04-20 21:00:36

11 réponses

Dans ma propre application, je dois valider les dimensions telles qu'elles sont saisies. La séquence que j'ai utilisée est la suivante

  1. l'utilisateur sélectionne ou tape puis se déplace de contrôle.
  2. le contrôle perd le focus et notifie la vue envoyant son ID et le entrée de texte.
  3. la vue vérifie quelle forme programme (une classe implémentant une interface) créé le formulaire et le passe le ID et texte d'entrée
  4. le programme Shape renvoie un réponse.
  5. si la réponse est OK vue met à jour l'entrée correcte de la forme Classe.
  6. si la réponse est OK, la vue indique le formulaire à travers une Interface qu'il est OK pour déplacer le focus à l'entrée suivante.
  7. si la réponse n'est pas correcte, la vue regarde la réponse et en utilisant le Interface de formulaire indique le formulaire quoi faire. Cela signifie généralement l'accent retourne à l'entrée incriminée avec un message affiché indiquant le utilisateur ce qui est arrivé.

L'avantage de cette approche que la validation est centralisé 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 m'inquiéter des différents types de contrôles sur le formulaire. Chemin du retour quand j'ai conçu le logiciel, j'ai décidé comment l'interface utilisateur allait fonctionner pour les zones de texte, les zones de liste, les zones de liste déroulante, etc. Les différents niveaux de gravité sont également traités différemment.

La vue prend soin de cela en indiquant au formulaire ce qu'il faut faire via l'Interface. Comment il est réellement implémenté est géré par le formulaire lui même dans sa mise en œuvre de l'Interface. La vue ne se soucie pas si le formulaire affiche jaune pour l'avertissement et rouge pour l'erreur. Seulement qu'il gère ces deux niveaux. Plus tard, si une meilleure idée d'Afficher des erreurs d'avertissement vs arrive, je peux faire le changement dans le formulaire lui-même plutôt avec la logique de vue ou le programme validate in Shape.

Vous êtes déjà à mi-chemin si vous envisagez de créer une classe pour contenir votre logique de validation, cela vous permettra de faire le reste du chemin dans votre nouveau design.

8
répondu RS Conley 2009-04-20 17:16:20

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

Chaque objet dérivé de Control a deux événements nommés Validating et Validated. Aussi, il a une propriété appelée CausesValidation. Lorsque cela est défini sur true (c'est vrai par défaut), le contrôle participe à la validation. Sinon, il ne le fait pas.

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

Lorsque vous modifiez le focus en utilisant clavier (TAB, MAJ + TAB, et ainsi de suite), en appelant le Sélectionner ou Méthodes SelectNextControl, ou par réglage de la ContainerControl..::.ActiveControl propriété au formulaire actuel, focus des événements se produisent dans l'ordre suivant:

  1. entrez
  2. GotFocus
  3. quitter
  4. valider
  5. Validé
  6. LostFocus

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

  1. entrez
  2. GotFocus
  3. LostFocus
  4. quitter
  5. valider
  6. Validé

Si la propriété CausesValidation est défini sur false, la validation et Les événements validés sont supprimés.

Si la propriété Cancel du CancelEventArgs est défini sur true dans le Validation du délégué d'événement, tous les événements cela se produirait généralement après la Les événements de validation sont supprimés.

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

61
répondu Matt Brunell 2009-04-20 19:58:04

Je me rends compte que ce fil est assez vieux mais j'ai pensé que je publierais 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 dans une zone de texte, puis cliquer ailleurs pour que la routine de validation s'exécute. C'est très bien si votre uniquement préoccupés par les données d'entrée est correcte. Mais cela ne fonctionne pas bien si vous essayez de vous assurer qu'un utilisateur ne l'a pas fait laisser une zone de texte vide en sautant dessus.

Dans ma solution, lorsque l'utilisateur clique sur le bouton Soumettre pour un formulaire, je vérifie chaque contrôle sur le formulaire (ou quel que soit le conteneur spécifié) et utilise 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) Copiez et collez simplement cette section de code dans votre projet. Nous utilisons la réflexion, vous devez donc ajouter un système.Réflexion sur 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é AutoValidatesur EnableAllowFocusChange. Cela permettra la tabulation à un autre contrôle même lorsque la validation échoue.

4) Enfin, dans votre méthode de bouton de soumission, appelez la méthode de 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();
    }

Bon Codage!

42
répondu Bruce 2015-07-06 17:52:19

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

Comme certains niveau, vous aurez à définir ce que signifie être valid pour chaque contrôle, à moins que tout ce qui vous intéresse, c'est que le contrôle a une valeur quelconque.

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

4
répondu Joel Coehoorn 2009-04-20 17:09:11

Nous avons eu de la chance avec le Noogen ValidationProvider . Il est simple pour les cas simples (contrôles de type de données et champs obligatoires) et facile d'ajouter une validation personnalisée pour les cas plus complexes.

3
répondu Jamie Ide 2009-04-20 17:08:26

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 son SetError(...) méthode pour définir l'erreur sur le contrôle en question avec des informations pertinentes sur la raison pour laquelle il est faux.

Edit> je devrais noter que j'utilise généralement le modèle mvc en faisant cela, donc la validation spécifique pour ce contrôle / membre du modèle se produit au modèle, donc l'isValidating semble un peu comme ça:

private uicontrol_isValidating(...)
{
    if(!m_Model.MemberNameIsValid())
    {
        errorProvider.SetError(...);
    }
}
2
répondu Steven Evers 2009-04-20 17:53:13

Soit de cette façon. Ou vous pouvez avoir un seul événement de validation associé à tous les contrôles ou qui nécessitent des validations similaires. Cela supprimera la boucle du code. Disons que vous avez quatre zones de texte qui ne peuvent avoir que des entiers. Ce que vous pouvez faire est d'avoir un seul événement pour chacun d'eux. Je n'ai aucun 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'événement:

  1. convertit l'expéditeur en zone de texte.
  2. vérifiez si la valeur dans la zone de texte est numérique.

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

J'espère que cela aide.

2
répondu danish 2009-04-20 17:59:03

Si vous combinez les idées ci-dessus avec ce gestionnaire d'événements de validation Générique, vous obtiendrez un bon "framework" d'erreur de validation avec toutes les méthodes de validation dans vos classes commerciales. 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. Amusez-vous bien!

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

}

Méthode de validation de L'échantillon:

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
répondu Rey 2012-05-15 11:15:34

Parcourir les contrôles peut fonctionner mais il est sujet aux erreurs. J'ai travaillé sur un projet qui utilisait cette technique (certes c'était un projet Delphi pas C#) et cela a fonctionné comme prévu mais il était très difficile à mettre à jour si un contrôle était ajouté ou modifié. Cela peut avoir été corrigible. Je ne suis pas sûr.

Quoi qu'il en soit, cela a fonctionné en créant un seul gestionnaire d'événements qui a ensuite été attaché à chaque contrôle. Le gestionnaire utiliserait alors RTTI pour déterminer le type de contrôle. Alors il faudrait utiliser le 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 été mis au point. Pour rendre les choses plus complexes, le formulaire a été divisé en plusieurs onglets et l'onglet approprié devait être visible pour enfant de contrôle pour obtenir le focus.

C'est mon expérience.

Je préférerais utiliser un modèle de conception de vue Passive pour supprimer toutes les règles métier du formulaire et les pousser dans un Classe de présentateur. Selon l'état de votre formulaire qui peut être plus de travail que votre volonté d'investir.

1
répondu Kenneth Cochran 2009-04-20 17:15:56

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 ne boucler que les contrôles si vous voulez éviter de boucler d'autres contrôles.

1
répondu Will Eddins 2009-04-20 17:35:57

Pourquoi n'utilisez-vous pas L'événement de validation? Vous pouvez avoir un seul événement de validation et valider les contrôles là-bas. Il n'y aura pas besoin d'utiliser des boucles et chaque contrôle sera validé au fur et à mesure que les données seront saisies.

1
répondu danish 2009-04-20 17:45:14