views:

4424

answers:

10

I need to implement input validation throughout my winform app. THere are many different forms where data can be entered and I would like to not have to go control by control by form and create isValid etc per item. How have others dealt with this?

I see that most related posts deal with Web Apps and/or mention Enterprise Library Validation Application Block. Now I admit I haven't thouroghly researchud ELVAB but it seems like overkill for what I need. My current thought is to write a class library with the various requiremnts and pass it a control as a parameter. I already have a Libarary of RegEx functions for things like isValidZipCode and such so that may be a place for me to start.

What I would like to have is a Validate button that onClick cycles through all the controls on that Form Page and performs the needed validation. How can I accomplish this?

+1  A: 

We've had good luck with the Noogen ValidationProvider. It's simple for simple cases (data type checks and required fields) and easy to add custom validation for more complex cases.

Jamie Ide
+2  A: 

I would like to not have to go control by control by form and create isValid etc per item.

As some level you will have to define what it means to be valid for each control, unless all you care about is that the control has a value of some kind.

That said, there's an ErrorProvider component you can use that works pretty well.

Joel Coehoorn
+1  A: 

Cycling through controls can work but it's error prone. I worked on a project that used that technique (granted it was a Delphi project not C#) and it did work as expected but it was very difficult to update if a control was added or changed. This may have been correctible. I'm not sure.

Anyway it worked by creating a single event handler which was then attached to each control. The handler would then use RTTI to determine the type of the control. Then it would use the control's name property in a large select statement to find the validation code to run. If the validation failed, an error message was sent to the user and the control was given focus. To make things more complex, the form was divided into several tabs and the proper tab had to be visible for it's child control to get the focus.

So that's my experience.

I would much rather use a Passive View design pattern to remove all business rules from the form and push them into a Presenter class. Depending on the state of your form that may be more work than your willing to invest.

codeelegance
A Passive View design pattern? I have zero exsposure to Design Patterns so I will look into this. Expect a post called "How do I implement a Passive View Design Pattern?" :-)
Refracted Paladin
Actually its more of an architectural pattern. http://www.martinfowler.com/eaaDev/PassiveScreen.html
codeelegance
+1  A: 

In my own application I need to validate dimensions as they are typed in. The sequence I used is as follows

  1. The user selects or types then moves away from the control.
  2. The control loses focus and notifies the View sending it's ID and the entry text.
  3. The View checks what Shape Program (a class implementing a interface) created the Form and passes it the ID and entry text
  4. The Shape Program returns a response.
  5. If the Response is OK the View updates correct Entry of the Shape Class.
  6. If the Response is OK the View tells the Form through a Interface that it is OK to shift the focus to the next entry.
  7. If the Response is not OK, the View looks at the response and using the Form Interface tells the form what to do. This usually means the focus shifts back to the offending entry with a message displayed telling the user what happened.

The advantage of this approach that validation is centralized in one location for a given Shape Program. I don't have to go modify each control or even really worry about the different types of controls on the form. Way back when I designed the software I decided how the UI going to work for textboxes, listboxes, combo boxes, etc. Also different levels of severity is handled differently.

The View takes care of that instructing the Form what to do through the Interface. How it actually is implemented is handled by the Form itself in it's implementation of the Interface. The View doesn't care if the Form is displaying yellow for warning and red for error. Only that it handles those two levels. Later if a better idea of displaying warning vs errors comes along I can make the change in the Form itself rather mucking around with the View logic or the validate in Shape Program.

You are already halfway there if you are considering making a class to hold your validation logic this will get you the rest of the way in your new design.

RS Conley
+1  A: 

Just a rough idea:


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

You could stick the textboxes inside a panel and only loop through controls in there if you want to avoid looping through other controls.

Will Eddins
+1  A: 

Why are you not using Validating event? You can have a single validating event and validate the controls there. There will be no need of using loops and each control will be validated as the data is entered.

danish
Validating Event? So set the Validating event on each control to the specific validation? Would it be possible for you to provide a more detailed example or a link? Thank you!
Refracted Paladin
Also, what if they never go to that field? What kicks off the Validating Event?
Refracted Paladin
For second comment: AFAIK in this case you have no option but loop through. At most, you can avoid checking those controls which have already been validated.
danish
+1  A: 

In all of my forms, I implement the isValidating event for the particular control in question and if the data doesn't validate I have an errorProvider on the form and I use its SetError(...) method to set the error to the control in question with relevant information as to why it's wrong.

edit> I should note that I generally use the mvc pattern when doing this, so the specific validation for that control/member of the model happens at the model, so the isValidating looks kinda like this:

private uicontrol_isValidating(...)
{
    if(!m_Model.MemberNameIsValid())
    {
        errorProvider.SetError(...);
    }
}
SnOrfus
+2  A: 

Either that way. Or you can have a single validating event associated with all or controls which need similar validations. This will remove the looping from the code. Say you have four textboxes which can have integer only. What you can do is have a single event for each of them. I am not having any IDE so code below is the best I can come up with.

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

In the event:

  1. Cast sender as textbox.
  2. Check if the value in the textbox is numeric.

And so forth you have events lined up.

Hope this helps.

danish
+9  A: 

Validation is already built into the WinForms library.

Each Control-derived object has two events named Validating and Validated. Also it has a property called CausesValidation. When this is set to true (it is true by default) then the control participates in validation. Otherwise, it does not.

Validation occurs as part of focus. When you focus off of a control, its validation events are fired. In fact the focus events are fired in a specific order. From MSDN:

When you change the focus by using the keyboard (TAB, SHIFT+TAB, and so on), by calling the Select or SelectNextControl methods, or by setting the ContainerControl..::.ActiveControl property to the current form, focus events occur in the following order:

  1. Enter
  2. GotFocus
  3. Leave
  4. Validating
  5. Validated
  6. LostFocus

When you change the focus by using the mouse or by calling the Focus method, focus events occur in the following order:

  1. Enter
  2. GotFocus
  3. LostFocus
  4. Leave
  5. Validating
  6. Validated

If the CausesValidation property is set to false, the Validating and Validated events are suppressed.

If the Cancel property of the CancelEventArgs is set to true in the Validating event delegate, all events that would usually occur after the Validating event are suppressed.

Also a ContainerControl has a method called ValidateChildren() which will loop through contained controls, and validate them.

Matt Brunell
+2  A: 

I realize this thread is pretty old but I thought I'd post the solution I came up with. Basically when the form is submitted, a routine will check the controls and determine if validation is defined on each control. If it is, the validating method is executed on each one and if any of them fail, the routine returns a failure allowing the process to stop. This solution works well especially if you have several forms to validate.

1) Add this code to your project. We're using Reflection so you need to add System.Refelection to your using statements

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

    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) Use the Validating event on any control you want to validate. Be Sure to use e.Cancel when the validation fails!

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) Don't skip this step! Set the AutoValidate property on the form to EnableAllowFocusChange. This will allow tabbing to another control even when the validation fails.

4) Finally call the form validation on the button click

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

Happy Coding!

Bruce