devxlogo

Exploring Secrets of Windows Form Validation

Exploring Secrets of Windows Form Validation

nput validation is something that every program needs, and every programmer dreads. Most developers craft hard-coded program logic to verify that a phone number consists of 7 or 10 digits; that an IP address consists of 4 octets; that a country name matches 1 of 193 choices, etc. While .NET 2.0 provides some support for streamlining input validation, it barely scratches the surface of automating and simplifying the input validation process. If you ever search MSDN for the topic, you’ll find most of the articles discuss hard-coded validation?but maintaining hand-written, custom code for each element that requires validation is a maintenance nightmare.

Furthermore, it’s unnatural to conceptualize the steps of validation as programming steps (i.e. if this then that…); it’s far more intuitive to consider the qualities of the data to be validated. For example, if you were describing the input, you might say, “It should be between five and eight characters; it should be numeric and non-negative; it should always end in the digit ‘5,’ etc.”

The concept of considering the data qualities rather than the validation process leads to the validation engine discussed in this article. A modular, data-driven validation engine approach provides a more natural interface, more flexibility, and is easier to maintain. Visual Studio lets you specify property values at design time for a variety of components; using that approach for defining validation attributes is a natural extension, and makes incorporating robust validation simpler for developers. The flexibility of storing validation settings in a configuration file makes validation easy to change, allowing developers and administrators to modifiy or customize validation properties without recompiling and redeploying?and without needing the source code. You’ll find that using the validation engine discussed here, you can instrument a wide range of validation criteria with very few lines of code.

The Problem
You want your program to validate form input, provide feedback to the user, and perhaps take other actions to handle the condition. And you want to do this simply and easily.

Limitations
This article presents a useful solution, though not a complete solution for every case. Instead, it follows the 80/20 rule: The validation engine will be useful in about 80 percent of the use cases you might need to address. Also following the 80/20 rule, to cover the remaining use cases would require 80 percent of the effort (i.e. the effort expended to get this far would be multiplied five-fold).

So, for example, you’ll find that the engine doesn’t handle validation interaction between multiple fields; instead, I opted to focus on single-field validation for this introductory article.

Initial Solution: The ErrorProvider Component
To begin, you need to be aware of the handy Microsoft component called ErrorProvider (see the MSDN article “ErrorProvider Component Overview“). Dragging an ErrorProvider onto the visual designer (or instantiating it in code) gives you the basic component required to display validation messages. For example, here’s a typical example that uses an ErrorProvider (from the MSDN article “How to: Display Error Icons for Form Validation with the Windows Forms ErrorProvider Component“):

   protected void textBox1_Validating(object sender, CancelEventArgs e)   {      try      {         int x = Int32.Parse(textBox1.Text);         errorProvider1.SetError(textBox1, "");      }      catch (Exception e)      {         errorProvider1.SetError(textBox1, "Not an integer value.");      }   }

This code validates input into a TextBox, requiring that it be an integer. To test the entered value, the code uses the Int32.Parse method, which throws an exception when the TextBox’s string value can not be converted to an integer. When the input passes this validation test, the ErrorProvider.SetError invocation clears the existing error message, if any; otherwise the catch block calls the ErrorProvider.SetError method to display the “Not an integer value” message.

You’d attach this method to the Validating event of a particular TextBox control called textBox1, either with the visual designer or programmatically with this code:

   this.textBox1.Validating +=       new CancelEventHandler(this.textBox1_Validating);

You should be aware of several important facts that are not terribly clear from the documentation.

The Validating event occurs when a control yields focus to another control. So the event occurs when you use the tab key to move away from the current control or when you click another control with the mouse.

Note that the SetError method in the example takes a Control as its first argument. That allows the single ErrorProvider to simultaneously handle error messages for a multiplicity of controls.

The second argument to SetError is the message to display. Passing an empty string tells the ErrorProvider to suppress the error message indicator completely, so it’s important not only to set the error message, but also to remove it when no error condition exists.

An ErrorProvider has these additional advantages over other error-display techniques.

  • If you used a pop-up dialog box the message will disappear when the user clicks “OK” and it would require specific coding to make it reappear. The ErrorProvider automatically redisplays the error whenever users wish.
  • If you used a single TextBox or Label control to display error messages (thereby retaining it longer than a pop-up dialog) you could persist only a single error message; for forms with multiple errors each additional error would supplant the previous error. An ErrorProvider automatically provides both display and persistence for as many error messages as you have Controls on your form.
  • Adding a dedicated TextBox or Label control for each validated control is a possible solution?you can make each visible only when required to display an error message. In fact, that’s even marginally better than the ErrorProvider because users don’t have to use the mouse to display each error, but it also requires you to create and maintain considerably more UI code, not to mention the form real estate and resources that all these TextBoxes and Labels would require.

Preventing Users from Leaving Invalid Controls
In a Validating event handler, you can prevent users from leaving the control containing the invalid entry until they clear or fix the error. If you look back to the code that assigned the event handler to the control, you will note that it wrapped the method in a CancelEventHandler. That provides the flexibility to add this line in the textBox1_Validating method, just after the error message is set (in the catch block):

   e.Cancel = true;

That line of code suppresses any events that would normally follow the Validating event. To users, the code causes the cursor to remain stubbornly in the control; neither tabbing nor mousing will get it to budge until the user corrects the error. For more information on the Validating event, see this MSDN article.

ErrorProvider Rendering
Here’s how an ErrorProvider actually renders on screen. Consider the simple form shown in Figure 1 . I will refer to this form throughout the remainder of this article.

?
Figure 1. Simple Form: This simple form?used as an example in this article?contains an unbound DataGridView, two TextBox controls, and OK and Cancel buttons.
?
Figure 2. ErrorProvider UI: The ErrorProvider displays an icon when a validation error occurs. By hovering over the icon, users can read the error message associated with that error.

By attaching the Validating method discussed earlier to the left text box, and entering characters that do not represent a number, the ErrorProvider pops up a small error icon. When the user hovers the mouse over the error icon, a ToolTip opens to show the error message provided: in this case, the text “Not an integer value.” (see Figure 2)

So as you have seen, the ErrorProvider simplifies building user interfaces for form validation. But so far, everything you’ve seen involves laboriously hand-coding an xxx_Validating method for each and every Control on your form. As Larry Wall said in Programming Perl, one of the three great programming virtues is laziness.

“The quality that makes you go to great effort to reduce overall energy expenditure. It makes you write labour-saving programs that other people will find useful, and document what you wrote so you don’t have to answer so many questions about it.”

Which means in this case that there should be a better way. And there is. Enter the CleanCode.Forms.Validator class.

A Better Solution
Imagine a validation engine that wraps around an ErrorProvider but more flexible?so it allows Label controls to show error messages (for those who do not care for hovering with the mouse). To avoid limiting developers, the engine should allow custom validation to co-exist with the automatic validation features, if not be integrated.

With that in mind, consider the nature of validation and the notion of business rules. For example, suppose you have a form field for entering a US phone number. Implicit in that requirement is a slew of assumptions about what constitutes a phone number. These business rules might include:

  • Only digits and hyphens are allowed.
  • When hyphens are present, the entry must contain exactly 12 characters (10 digits and 2 hyphens).
  • The hyphens must be after the third and the seventh digits.
  • If hyphens are omitted, the entry must be precisely 10 characters.

Typically, people think of business rules as something specific to their business rather than any business, but the rules listed above are still business rules in a broad sense in that they refer to the semantics of the data. Here’s one more business rule that is specific to the example business rather than a universal character of US phone numbers:

  • Area codes must be for California only.

Many software applications that process form input simply hard-code the business rule checking right in with the form processing, using logic something like this:

   read phoneNumber   if (phoneNumber contains only digits and hyphens) and       getAreaCode(phoneNumber) is on the California list of area codes   then       store phoneNumber   else       flag invalid phone number

But then what if the company wants to expand to Oregon and Washington also? That would require changing the code because of a business rule change. This is worse than wearing white shoes after Labor Day?it simply should not be done in polite society?or, in this case, in clean code. Many software developers will let the data be external to the program but far fewer will let the rules be external to the program. And that is where a well-designed validation engine shines, by allowing the business rules to be external to the program and therefore controllable by a business analyst rather than a software designer.

The scope of the validation engine in this article, then, will be:

  • Encapsulate business rules externally.
  • Manifest validation errors with either a form-global ErrorProvider or a control-specific Label.
  • Allow custom validation beyond the scope of the validation engine to co-exist.

Maintaining External Business Rules
This is arguably the most important element in the scope, but it is quite straightforward with the visual designer of Visual Studio 2005. Not to say that it does not have a learning curve; it does. I urge you to review the MSDN article How to: Add or Remove Application Settings or other sources for details on persistent settings in .NET. Just briefly, .NET stores persistent settings either in the application.exe.config or user.config on your local system. You can access these settings in code through the Properties.Settings.Default object and through the visual designer in the “Settings” section of the project properties. So, storing business rules as persistent settings means they’re available in the user.config file, which may be judiciously edited as needed. Even better (and definitely recommended), you can provide a user-friendly interface to edit the business rules by binding the persistent settings directly to controls on a Windows form. That way, when users edit the values on the form, the values will automatically be stored in user.config.

Visual Studio 2005’s visual designer provides a way to access the user interface layer of your application separately from your code; it automatically turns your interactions in the visual designer into code, which it keeps separate. The business rules for form validation are, of course, intimately tied to the user interface?that is, most of the controls on your form will have one or more specific business rules that you want to validate against. So why not specify the validation business rules in the visual designer where you specify the user interface itself?

The visual designer provides a properties pane for each control on your form. All you need to do is add some business rules as properties. But you can’t just add properties to the properties pane, so that is a problem. One easy way around this limitation is to use the Tag property, which is thoughtfully provided as a user-definable property. As there is just one such property per control, you can simply pack the business rules into a package that will fit in the Tag property container. Using this scheme, you can define business rules as you define controls, combining that with persistent settings to externalize the business rules.

To make the connection to the rules, you assign each control to be validated a Tag property value consisting of a series of attributes in the following format:

   =;=;...;=

A more refined solution would be to include an ExtenderProvider control that defines real properties corresponding to the available attributes?leaving the Tag property free for other uses? but I wanted to keep this short and simple.

Table 1 shows the available attributes and their types:

Table 1: Validation Attributes: The table lists the available validation attributes along with their types and a brief description.
AttributeValueDescription
maxLenint > 0field must contain no more than the specified number of characters
minLenint > 0field must contain at least the specified number of characters
maxValFloatfield must contain a number that is no larger than the specified value
minValFloatfield must contain a number that is no smaller than the specified value
patternregular expressionfield must match the specified regular expression

The first two (minLen and maxLen) let you control the length of the value taken as a string. Similarly, the next two (minVal, maxVal) let you control the range of numerical entries. The final one (pattern) requires a regular expression argument. You can “mix and match” the attribute combinations to suit your needs. Table 2 shows a few common attribute combinations that you may find useful. Note that some constraints that may be expressed with minVal and maxVal may also be expressed with regular expression patterns.

Table 2. Common Validation Attribute Combinations: The table lists some of the more common attribute combintations that you might encounter.
Attribute ListMeaning
maxLen=10Value may be no more than 10 characters.
minVal=0Value may not be negative.
minVal=0;maxVal=100Value must be between 0 and 100 inclusive.
minLen=1Value must be non-empty.
pattern=.Value must be non-empty.
minLen=5;maxLen=5Value must contain exactly five characters.
pattern=.{5}Value must contain exactly five characters.
pattern=^S+$Value may not contain spaces.
pattern=^-?(?:d*.?d+|d+.)$Value must be a number.
pattern=^d{3}-d{3}-d{4}$Value must be a canonical US phone number.
pattern=^w[w.]*@w+.w[w.]*$Value must be an e-mail address.

?
Figure 3. Application Settings: The figure shows the Application Settings entry in the properties pane. You can see that the Tag property contains a pattern-type business rule?a regular expression.

You can enter your business rules in the property pane of each control as you’re designing your form in Visual Studio’s visual designer. Then, you need to connect that property to an application setting so that it will be externalized when the application is executed. Here are the steps:

First, enter your business rules in the Tag property for a control. Next, find the “Application Settings” entry in the properties pane . If you have the property pane sorted by category, you’ll find it under the Data category. Expand “(Application Settings)” to get to the “(PropertyBinding)” entry. Click on the value field to make the property editor ellipsis appear (see Figure 3).

Click on the ellipsis button for “(PropertyBinding).” This will open an Application Settings dialog for the selected control (see Figure 4).

?
Figure 4. New Application Settings Dialog: Use this dialog to assign property values to individual application settings for controls.
?
Figure 5. New Application Setting: Assign a name to the new setting.

Find the Tag property, open its drop-down selector, and click “New.” You should see the value you entered in the Tag property listed as the default value (see Figure 5). Enter a name for the property. For example, in Figure 5, because this setting is associated with the leftTextBox control in the sample application, I’ve used the name “leftTextBoxBusinessRules.” The Scope may be either Application (a read-only value, global to all users on a single computer, stored in program.exe.config), or User (the setting may be changed within your program and, if so, is stored as an override in a user-specific user.config file the user’s Application Data folder).

Click OK to close the New Application Setting dialog and OK again to close the Application Settings dialog. Notice that in the properties pane (see Figure 6) the value of the Tag property is gone, replaced with a little icon, and the Tag property now shows up under the “(Application Settings)” section using the chosen name.

?
Figure 6. Completed Settings: After assigning a property to application settings, the property (the Tag property in this case) moves under the (ApplicationSettings) item in the Properties dialog, and substitutes a small icon for the original property value.
?
Figure 7. Missing Value: The Tag property value (the business rule) is missing from the Settings tab of the Project Properties dialog.

?
Figure 8. Force the Tag Value to Appear: To force the assigned business rule values to appear in the Settings dialog, change the type from System.Object to string.

In theory, that completes the property binding, but in reality, it does not. You still need to perform one more step. Open the “Settings” tab of the Project Properties dialog. Continuing with the leftTextBox control example, you should see the leftTextBoxBusinessRules setting just created, but as Figure 7 shows, the value is missing.

When you assigned the Tag property to application settings, Visual Studio removed the value from the Tag property of the control and?even though it was indicated as the default value for this application setting?it does not show up in the Settings tab of the Project Properties dialog.

This is easy to correct. Simply change the setting Type value from System.Object to string and voil?!?the default value appears (see Figure 8).

An Implementation
The validation engine I created for this article is called Validator. You can download it from my open-source web site (look under Products >> Documentation >> C# >> CleanCode.Forms.Validator). This engine validates form controls (TextBox or DataGridView Controls) using attributes you specify in the Tag property as described earlier. When errors occur, it displays both an individual error indication for each invalid entry, and can disable a designated common form submittal button (typically an OK button). You can display validation errors in either individual Label controls unique to each control, or with a standard ErrorProvider component, at your discretion. Using an ErrorProvider carries the advantage that you do not have to create and associate a Label component with each validating component, but the disadvantage that you must use the mouse to see the validation error, as discussed earlier. You can also download the sample application described in this article, which illustrates how to use the Validator.

To validate a control, you add it to a Validator instance, then call the Validate() method inside either the TextChanged or Validating event handler for your control. MSDN documentation suggests the Validating event handler is the appropriate place, but Validating fires only when the focus leaves a control. If you need finer control (such as on every keystroke) then TextChanged is a better choice.

The validation engine evaluates all validation attributes (business rules) for a given control, but reports only one error per control at a time. For example, if a single entry fails both the maximum length test and the maximum value test, the engine reports only one of those failures to the user. After the user corrects that error, then the remaining error condition will be displayed, if still present.

Coding the Example
Here’s how to put all this into practice. For the simple example form given earlier, here is the user code (as distinct from the designer generated code). Each short method shown is an event handler, so it needs to be connected to an appropriate event (which should be evident from the method naming).

   using System;   using System.ComponentModel;   using System.Windows.Forms;   using CleanCode.Forms;      namespace ValidationDemo   {      public partial class Form1 : Form      {         private Validator validator;            public Form1()         {            InitializeComponent();         }            private void Form1_Load(object sender, EventArgs e)         {            validator = new Validator(okButton, errorProvider);            validator.Add(dataGridView);            validator.Add(leftTextBox);            validator.Add(rightTextBox);         }            private void okButton_Click(object sender, EventArgs e)         {            MessageBox.Show("OK clicked");         }            private void cancelButton_Click(object sender, EventArgs e)         {            MessageBox.Show("cancel clicked");         }            private void leftTextBox_Validating(object sender, CancelEventArgs e)         {            if (!validator.Validate(leftTextBox)) { e.Cancel = true; }         }            private void rightTextBox_TextChanged(object sender, EventArgs e)         {            validator.Validate(rightTextBox);         }            private void dataGridView_CellValidating(            object sender, DataGridViewCellValidatingEventArgs e)         {            if (!validator.Validate((DataGridView)sender, e))            {               // e.Cancel = true; // this really traps the user in there!            }         }      }   }

That’s all the validation code need for the sample application (see Figure 1) within Visual Studio 2005. The Validator contains the actual validation code. The only other entries you need to make are the business rules, which I’ll cover shortly.

In the preceding code, the Form1_Load event handler instantiates the validation engine, then adds each control to validate to it. Note that the Validator constructor accepts the form’s OK button. The Validator uses that reference to enable the OK button when all Controls validate and disable it (or “grey it out”) if any controls do not validate. The constructor also accepts an ErrorProvider, which the validation engine uses to display errors.

The leftTextBox and rightTextBox controls use different event handlers merely to show the behavior differences discussed earlier?the former validates when focus leaves the field; the latter validates at every keystroke. The leftTextBox handler also prevents users from leaving as long as an error is present. You could, in fact, use the same event handler method for many controls if you want them all to act on the same event and behave the same way.

For the DataGridView control?a control containing multiple data values?calling the method to perform validation is no more complicated than a singleton control, but the business rule set requires more information as you’ll see next. The possibilities involved in handling DataGridView validation are much more sophisticated, particularly with bound DataGridViews, but those are beyond the scope of this article.

Business Rules for the Example
In addition to the code discussed earlier, you must instrument the leftTextBox, rightTextBox, and dataGridView controls with business rules. Specifically, you set the Tag property of these controls as shown in Table 3:

Table 3. Sample Tag Property Values: The table lists the Tag settings for the three TextBox controls in the sample application.
ControlTag property
leftTextBoxpattern=^w[w.]*@w+.w[w.]*$;minLen=2
rightTextBoxpattern=^-?(?:d*.?d+|d+.)$
dataGridView[Column]pattern=^w+$;[MatchExpr]pattern=(.*)

These rules state?in the language of regular expressions?that the leftTextBox must have an entry resembling an email address; “[email protected]” will pass muster, as will “[email protected]”. The rightTextBox business rule requires numeric input, for example, 5 or -25 or -3.9999 or .81. The dataGridView may have business rules for each column?but does not have to. In this case, the rules specify that the column generically called “Column” must contain only letters or digits and the column called “MatchExpr” must contain an opening and a closing set of parentheses somewhere in the input.

Author’s Note: In Table 3, notice the format used to embed multiple sets of business rules into one property?each column name is in brackets followed by its business rule set.

Notes on Running the Example
Build the application and execute it to see what happens. The button will be disabled if any validation rule fails. In this case, the Validator uses an ErrorProvider to display validation errors. Because your code creates the ErrorProvider independently, you are free to add further validation checks in your event handlers that are beyond the scope of the business rule categories provided by the validation engine.

At this point, I hope you understand both how and why to separate business rules from code, using a validation engine to help do that. I encourage you to study the source code to understand the mechanisms of the validation engine itself. This is actually a simplified version of an earlier validation engine called “PageValidator,” implemented in Perl and JavaScript for use on a web site. The same engine runs on both the client and the server, providing for both rapid response and security.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist