Getting Started

Create Self-Validating Controls

Learn everything you need to know to write self-validating Windows Forms controls using regular expressions and inheritance.

Technology Toolbox: C#, Windows Forms

Microsoft .NET lets you build complex applications quickly and easily, but its selection of controls does have notable gaps. Fortunately, you can address part of this deficiency yourself by building your own self-validating text input controls.

.NET's existing controls restrict the user input based on the type of input required. The canonical example of this type of control is a TextBox control that lets the user enter only numbers. This kind of control should also provide real-time feedback indicating whether the text is valid as the user enters it. Microsoft Foundation Classes (MFC) includes these types of TextBox controls, such as the Edit control, but the MFC numerical Edit control takes the simplistic and less useful interpretation of this requirement to mean that you can enter only the characters 0 through 9 into the textbox. The MFC controls don't let you enter numbers such as 1.25 or -12 if you need to.

.NET lacks an equivalent to the MFC numerical Edit control, but it does have rich support for inheritance with regard to Windows Forms Controls. You can use inheritance to build an entire family of self-validating controls that go far beyond restricting input to the characters 0 through 9. You can use the power of inheritance and regular expressions to develop a set of TextBox-derived controls that support a variety of input.

The first step is to create a base class that serves as the foundation control. Next, build a TextBox control that supports restricting the input to a variety of number types, such as floating point numbers, integers, positive integers, and so on. Additionally, you can create a textbox that allows only Social Security numbers in the form of XXX-XX-XXXX—using only two lines of code outside the standard wizard-generated code (see Figure 1).

While Windows Forms Controls do include a built-in mechanism for input validation, that mechanism isn't the best way to build self-validating controls. You can provide realtime feedback and cancel invalid input by subscribing to the Validating and Validated events of a TextBox control and setting the CausesValidation property of the parent form. Unfortunately, this procedure gives no architectural guidance beyond allowing your form to decide whether the particular input is valid. Further, the validation code for the control itself is pushed into the consuming application, rather than encapsulated within the control—a significant drawback.

This increased level of encapsulation in self-validating controls has many benefits. For example, you can add your specialized controls to a common control library shared across many applications, contributing to a significantly higher level of code reuse. Another benefit: You can maintain and upgrade the validation process more easily because the implementation exists in only one location.

The built-in method is fine for cases where validating user input is not purely structural in nature. Validation is a good candidate for encapsulation in a self-validating control if the input is independent of the rest of your application's state.

Employ Regular Expressions
Regular expressions give you powerful tools for matching structured text. They're ideal for validating phone numbers, Social Security numbers, and even raw numerical input. The validation mechanism described in this article uses a regular expression system because it provides flexibility and power while retaining the familiar validation mechanism.

It's worth a high-level look at how you'll put the regular expressions to use before examining the details of creating the control proper. Don't be concerned if you're not familiar with regular expressions. The regular expressions used in this article aren't complex, and I'll explain everything you need to know to implement them in the validation control.

You can create a control that provides realtime feedback by letting the user type the input and have that input validated for each character as it's entered. This means you need two regular expressions in the validation process: one that validates partial input, and one that validates the final input.

You need to validate both as the characters are entered and when all the text has been entered, because the user might enter a negative number. You need a regular expression that considers "-" valid if you want to allow negative integers because that is the first character entered when typing a negative number such as -125. Nevertheless, you cannot have a final regular expression that considers the "-" character a valid number.

These regular expression patterns validate integer input (negative and positive whole numbers as well as 0):

string patIntegerFull = "^-?[0-9]+$";
string patIntegerPartial = "^-?[0-9]*$";

These patterns are similar, but have a key difference. The partial pattern matches "-" and "-125", whereas the final matches "-125" but not "-".

Take a look at what these expressions represent (see Table 1). You can translate the validation patterns into plain English once you define the regular expression elements. The partial pattern matches strings that meet three criteria. First, the first character is a single minus sign, unless there isn't a minus sign, in which case the expression goes onto the next condition. Second, the next set of characters is a series of digits, and they can appear zero or more times. Third, you reach the end of the string. The final pattern is similar, with the key difference that the digits must appear one or more times in the second condition.

Build the Control
Once you understand how these conditions work, you can begin building the controls. Object inheritance provides the easiest way to override the behavior of a .NET Windows Forms Control. You will also need to employ some attributes from the System.ComponentModel namespace to make your control friendly to the Visual Studio .NET designer (see Figure 2).

Begin by defining a base ValidatingTextBox class, which handles the validation logic as well as the undo logic when a user enters invalid characters. First, create a Class Library project in Visual Studio .NET, then add a reference to System.Windows.Forms. Next, add a new class named ValidatingTextBox to the project. You don't need all the features provided in the "Inherited User Control" scenario, so using the Add New Class wizard will suffice. You need to edit the class for your particular circumstances:

public abstract class ValidatingTextBox 
   : TextBox
   public event CancelEventHandler ValidationFailed;
   public event EventHandler ValueChanged;

   public ValidatingTextBox()
      this.TextChanged += new 
   private void 
      ValidatingTextBox_TextChanged(object sender, EventArgs e)

Note that the code omits the namespace and using statements for the sake of brevity.

The ValidatingTextBox class is an abstract class. This enables you to prevent the control from being used directly; the control is designed to be used solely as a foundation for the specialized controls such as the NumericalTextBox.

This code includes everything you need to drive the validation process. Remember: The built-in validation scheme doesn't quite accomplish everything you need the control to do. Instead of trying to edge out the built-in scheme and use its events, the ValidatingTextBox class defines two similar but different events. The ValidationFailed event is an optional event that consumers of the control can use to override the validation behavior. It's fired when a user enters invalid characters. The ValueChanged event replaces the TextChanged event and fires when a user enters (partially) valid characters.

Now you can take advantage of the partial and full regular expressions to validate the characters as they're entered (see Listing 1). The listing omits the details of most of the implementation of this class, but it covers the most important method: the internal TextChanged event handler.

The TextChanged method uses the lock statement to provide thread safety to its member variables. You need to define a Boolean member variable named _supressTextEvent. The SetTextWithoutEvents method uses this variable to do just what the name implies: It replaces the entered text without triggering extraneous validation events.

After the previous and current text has been saved in case an undo is needed, the current text is tested using the partial regular expression. If it's a match, the current text is considered valid, and the ValueChanged event fires. If the text is not a match, the ValidationFailed event fires. The control gives the consumer the option to allow the text to be entered anyway by setting the Cancel property of the CancelEventArgs to False. However, I expect this case would occur rarely, at best. The TextChanged method then undoes the entered text and moves the cursor to where it was when the user typed the offending character.

Extend the Control
The source code for this article includes two specialized validating controls based on the ValidatingTextBox class. These controls illustrate the ease with which you can extend the control, as well as how powerful such additions can be.

The first control is the NumericalTextBox. The regular expressions involved in validating numerical input are fairly simple, so this control also serves as a good introductory example. The version included in the sample code is highly configurable. It supports design-time enumerations that control the types of numbers it will accept.

The second specialized control is a Social Security number textbox called SSNTextBox. This control restricts its input to the form XXX-XX-XXXX, where X is any digit:

public class SSNTextBox : ValidatingTextBox
   public SSNTextBox()
      this.PatternFull = @"^\d\d\d-\d\d-\d\d\d\d$";
      this.PatternPartial = 
         @"^(?>^\d{3}-\d\d-\d{4}$|^(?>" + 
         @"^\d{3}-\d\d-\d{3}$|^(?>^\d{3}" + 
         @"-\d\d-\d{2}$|^(?>^\d{3}-a" + 
         @"\d\d-" + @"\d$|^(?>^\d{3}-\d\d-$|^(?>" + 
         @"^\d{3}-\d\d$|^(?>^\d{3}-" + 
         @\d$|" + @"^(?>^\d{3}-$|^(?>^\d{3}$|^" +

Note that you create the SSNTextBox control from the base ValidatingTextBox class simply by changing the regular expression patterns.

The partial pattern in the SSNTextBox might be a bit frightening if you're unfamiliar with regular expressions. On the other hand, you might have a more succinct version if you're a guru with regular expressions. Either way, the partial pattern consists of nothing more than 11 different cases, covering each of the possible states when valid input is entered. You can see the basic idea of the partial regular expression in this conditional pattern:


This pattern works by first attempting to match the pattern A. If that succeeds, then it's a match. If it fails, the regular expression then attempts to match B. If that succeeds, then it is again a match. If it fails, then the overall match fails. The partial pattern is this basic idea, but with 11 conditions rather than two. It's best to start with the most restrictive pattern and work your way down to the least restrictive pattern. That is, test for XXX-XX-XXXX first, then XXX-XX-XXX, and so on. At each step, remove an element from the end of the pattern you're attempting to locate.

Note that these patterns match only the format of the Social Security number. They don't account for all the rules and restrictions. For example, these patterns will accept 999-99-9999, which is not a valid number.

The ValidatingTextBox control and derived classes use attributes in the System.ComponentModel to make them Visual Studio .NET designer-friendly. The most significant of these attributes is the DefaultEvent attribute. For example, take a look at how the control sets the DefaultEvent attribute to the ValueChanged event:

public abstract class ValidatingTextBox : TextBox
   // ...

Setting the default event to the ValueChanged event means that an event handler is added automatically for the control's default event when the control is placed on a form and doubled-clicked on in the Visual Studio .NET designer. This was the TextChanged event before you overrode it with the ValueChanged event. It is important that you do this because ValueChanged is fired once every time the text appears to change from a user's perspective. TextChanged also fires once when the text changes from a user's perspective, but twice when the validation rejects the new text. From a user's perspective, ValueChanged is what one would think of as TextChanged.

You might want to improve the design-time experience with other attributes such as DefaultProperty, Category, Description, and DefaultValue.

About the Author

Michael Kennedy is a founding partner and software engineer at United Binary. He has been developing software for more than nine years. The last three of those years, he has been focused solidly on .NET development. Reach Michael at [email protected].

comments powered by Disqus


  • GitHub Copilot for Azure Gets Preview Glitches

    This reporter, recently accepted to preview GitHub Copilot for Azure, has thus far found the tool to be, well, glitchy.

  • New .NET 9 Templates for Blazor Hybrid, .NET MAUI

    Microsoft's fifth preview of .NET 9 nods at AI development while also introducing new templates for some of the more popular project types, including Blazor Hybrid and .NET MAUI.

  • What's Next for ASP.NET Core and Blazor

    Since its inception as an intriguing experiment in leveraging WebAssembly to enable dynamic web development with C#, Blazor has evolved into a mature, fully featured framework. Integral to the ASP.NET Core ecosystem, Blazor offers developers a unique combination of server-side rendering and rich client-side interactivity.

  • Nearest Centroid Classification for Numeric Data Using C#

    Here's a complete end-to-end demo of what Dr. James McCaffrey of Microsoft Research says is arguably the simplest possible classification technique.

  • .NET MAUI in VS Code Goes GA

    Visual Studio Code's .NET MAUI workload, which evolves the former Xamarin.Forms mobile-centric framework by adding support for creating desktop applications, has reached general availability.

Subscribe on YouTube