2002
The best possible way to prevent invalid input is to make it impossible for users to enter it. You accomplish this by forcing users to choose from lists, and creating custom controls that automatically format data and ignore invalid key presses. Of course, sometimes this task is too daunting and you need to settle on the next best thing, which is checking for errors after the fact. If you take this approach, it's important that you report the error as soon as possible, preferably before the user continues to enter more information. The easiest way is to react to validation events.
Validation events were designed to let you check information as soon as it is entered, rather than waiting for the whole form to be submitted. This kind of instantaneous error checking is very useful:
Without it, users might be afraid to submit a form because they know there is a possible error.
Users might enter several pieces of invalid data at the same time. If you don't check the data until the form is submitted, your program then has to find some way to report about all the mistakes at once.
By the time users submit a form, they might have already forgotten about the particular field they entered incorrectly.
Validation solves this information by checking the field as soon as the user is finished entering it and changes focus to another control (either to enter new information, like choosing a text box, or to perform an action, like clicking a button).
In the past, developers have tried to create "do-it-yourself" validation by responding to a control's LostFocus event. The problem with this event is that it occurs after the focus has already moved on. If you reset the focus because of invalid input, another control then receives its own LostFocus event. If both controls have invalid data, they may fight endlessly between each other, trying to move the focus somewhere else.
.NET handles this problem with the Validating and Validated events. These events occur after the user has chosen to move to another control (for example, by pressing the Tab key), but before the focus has been changed, in the following order:
Leave
Validated
LostFocus
The Validated event allows you to respond to correctly entered data. The Validating event is more useful. It allows you to verify the data and, if it fails the test, stop the focus from moving to the new control.
Validation only takes place if the source control (the control to be validated) has the CausesValidaton property set to true. In addition, the validation won't take place until the focus changes to a control that also has its CausesValidation property set to true. Table 4-15 shows some examples of what can happen when tabbing from one control to another.
Table 4-15: .NET Validation
Source ControlDestination ControlResult
CausesValidation is falseDoesn't matterValidation code is ignored.
CausesValidation is trueCausesValidation is trueValidation is performed for the source control.
CausesValidation is trueCausesValidation is falseValidation is postponed until the focus changes to a CausesValidation control. At this point, all the controls that need to be validated are validated in order, until one is found with invalid input and the process is cancelled.
The program shown in Figure 4-20 uses validation to verify that neither text box is left blank. If the user tries to change focus without entering any information, a message box appears, and the focus is reset to the empty text box.
Figure 4-20: A validation example
The validation code for this application is shown here:
private void txtName_Validating(object sender, System.ComponentModel.CancelEventArgs e) { if (((TextBox)sender).Text == ") { MessageBox.Show("You must enter a first and last name.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning); e.Cancel = true; } }
Note that buttons handle validation differently than other controls. They don't validate on a focus change except when they are clicked. If the user tries to click a button and validation fails, the focus is reset, and the Click event is ignored. Attempting to close with the top-right close button (displayed as an "X") also triggers validation. (This creates a problem if you need to let users escape from a form without completing the operation. The solution is to create a Cancel button that closes the form, and has its CausesValidation property set to false.)
Interrupting users with a message box is a relatively crude way of alerting them to an error. It's better to provide some kind of onscreen indication about the problem, like an explanatory error message next to the incorrect input.
The .NET framework provides an elegant way to accomplish this with its new error provider control. The ErrorProvider displays a special error icon next to an invalid control. If the user hovers the mouse above the control, a detailed message appears (see Figure 4-21).
Figure 4-21: The ErrorProvider
The ErrorProvider is a special provider control. You add it once to your form, and you can use it to display an error icon next to any control. To add the ErrorProvider, drag it into the component tray, or create it manually in code. In the latter case, make sure you keep a form-level reference to use later.
You show the error icon next to a control using the ErrorProvider.SetError() method. The following code segment shows the same text box validating code, but is rewritten to indicate the error using the ErrorProvider control instead of a message box.
private void txtName_Validating(object sender, System.ComponentModel.CancelEventArgs e) { Control ctrl = (Control)sender; if (ctrl.Text == ") { errProvider.SetError(ctrl, "You must enter a first and last name."); } else { errProvider.SetError(ctrl, "); } }
The ErrorProvider control can serve any number of input controls on the same form, and display as many simultaneous error icons and warning messages as needed. Every warning icon automatically appears to the immediate right of the input control; there is no way to place it explicitly.Note that you must explicitly clear the error message after validation succeeds. In this example, the validation event doesn't cancel the action; it just displays the error icon. This is a more user-friendly alternative, but it means that you need to explicitly check if the form has any errors before allowing users to continue if they have clicked on the OK button.
private void cdmOK_Click(object sender, System.EventArgs e) { if (errProvider.GetError(txtFirstName) == " && errProvider.GetError(txtLastName) == ") { this.Close(); } else { MessageBox.Show("You still have invalid input.", "Invalid Input", _ MessageBoxButtons.OK, MessageBoxIcon.Warning); } }
If you have a lot of controls, it makes more sense to iterate through the whole collection, rather than writing code checking each control individually. In the following example, the validation controls are all contained inside a single group box named grpValidation, so the code iterates its collection of child controls.
private void cmdOK_Click(object sender, System.EventArgs e) { bool invalidInput = false; foreach (Control ctrl in this.grpValidation.Controls) { if (errProvider.GetError(ctrl) != ") { invalidInput = true; break; } } if (invalidInput) { MessageBox.Show("You still have invalid input.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning); } else { this.Close(); } }
The ErrorProvider control is an ideal way to weave error feedback into your application. However, writing the actual validation code can still be painful and time consuming. One way to streamline your work is to use the .NET regular expression classes, which allow you to search text strings for specific patterns.
Here's an example that validates an email address, by verifying that it contains an "at" symbol (@) and period (.) and doesn't include spaces or special characters. Unlike our previous example, this code is performed in the KeyPress event handler, which ensures that the error provider icon is updated immediately after any change.
private void txtEmail_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) { System.Text.RegularExpressions.Regex regex; regex = new System.Text.RegularExpressions.Regex(@"\S+@\S+\.\S+"); Control ctrl = (Control)sender; if (regex.IsMatch(ctrl.Text)) { errProvider.SetError(ctrl, "); } else { errProvider.SetError(ctrl, "Not a valid email."); } }
In C#, you can precede a string with the "at" symbol (@) in order to indicate that it is a string literal. In this case, all character sequences that start with a backslash (\) will be interpreted as backslashes, not special escape sequences. This ability is very useful when dealing with regular expressions, which use the backslash character frequently.Regular expressions almost constitute an entire language of their own, with special characters and metacharacters. Most programmers and organizations create their own regular expression classes that provide commonly used expressions. One possible example is shown below.
public class RegularExpressions { public const string Email = @"\S+@\S+\.\S+"; // 4-10 character password that starts with a letter. public const string Password = @"[a-zA-Z]\w{3,9}"; // A sequence of 3-2-4 digits, with each group separated by a dash. public const string SSN = @"\d{3}-\d{2}-\d{4}"; }
Once you have created this type of resource class, you can use it easily to create a RegEx object:
Regex expression = new Regex(RegularExpressions.Email);
A brief list of some common regular expression metacharacters is shown in Table 4-16. You can use these characters to create your own regular expressions.
Table 4-16: Regular Expression Metacharacters
CharacterMatches
*Zero or more occurrences of the previous character or subexpression. For example, a*b matches aab or just a.
+One or more occurrences of the previous character or subexpression. For example, a+b matches aab but not a.
( )Groups a subexpression that is treated as a single element. For example, (ab)+ matches ab and ababab.
|Either of two matches. For example, a|b matches a or b.
[ ]Matches one character in a range of valid characters. For example, [A-C] matches A, B, or C.
[^ ]Matches a character that is not in the given range. For example, [^A-C] matches any character except A, B, and C.
.Any character except newline.
\sAny whitespace character (like a tab or space).
\SAny non-whitespace character (like a tab or space).
\dAny digit character.
\DAny character that is not a digit.
\wAny word character (letter, number, or underscore).
However, it's often easier to look up a premade regular expression that suits your data using the Internet or a dedicated book on the subject.