Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

Validating Edit Controls

Rate me:
Please Sign up or sign in to vote.
4.88/5 (119 votes)
22 Feb 200416 min read 598.6K   5.1K   219   240
The classes presented in this article provide benefits for the most popular types of data that may be entered.

Contents

Introduction

Textbox controls are often used to enter dates, times, currency values or preformatted numeric data such as phone numbers. A control that allows its user to only enter a specific type of data is useful for several reasons:

  1. The user may do less typing if the control automatically fills in characters such as dashes or slashes.
  2. The user gets immediate feedback if he/she enters an invalid character.
  3. The entered data is much more likely to be valid when the user "submits" it.

The classes presented in this article provide these benefits for the most popular types of data that may be entered: dates, times, decimals, integers, currency amounts, formatted numerics and restricted alphanumerics.

Background

This is my first real project using C#/.NET, having come from C++/MFC and Java. I decided that a great way to learn was to take something I'm already familiar with and convert it to C#. This would allow me to learn the new technology as well as to make note of the differences. This article is the result of converting my Validating Edit Controls code, originally written in C++/MFC. The effort took around a month to complete and was a terrific learning experience. Enjoy!

Classes

There are two groups of classes, the behavior classes and the textbox classes, both of which are contained in the AMS.TextBox namespace. The behavior classes are designed to alter the standard behavior of textboxes so that the user can only enter a specific type of text into the control. For example, the DateBehavior only allows a date value to be entered into the textbox associated with it. The rest of the classes are simple TextBox-derived controls containing specific behaviors. For example, the DateTextBox control has the DateBehavior field inside of it and a property used to retrieve it.

Here's a listing of all the classes. If you need specific documentation on the available methods and properties, you may view the AMS.TextBox.chm help file, or just use the editor's Intellisense.

Class NameDescription
Behavior

Base class for all behavior classes. It has some basic functionality.

AlphanumericBehavior

Prohibits input of one or more characters and restricts length.

NumericBehavior

Used to input a decimal number with a maximum number of digits before and/or after the decimal point.

IntegerBehavior

Only allows a whole number to be entered.

CurrencyBehavior

Inserts a monetary symbol in front of the value and a separator for the thousands.

DateBehavior

Allows input of a date in the mm/dd/yyyy or dd/mm/yyyy format, depending on the locale.

TimeBehavior

Allows input of a time with or without seconds and in 12 or 24 hour format, depending on the locale.

DateTimeBehaviorAllows input of a date and time by combining the above two classes.
MaskedBehavior

Takes a mask containing '#' symbols, each one corresponding to a digit. Any characters between the #s are automatically inserted as the user types. It may be customized to accept additional symbols.

AlphanumericTextBox

Supports the Alphanumeric behavior.

NumericTextBox

Supports the Numeric behavior.

IntegerTextBox

Supports the Integer behavior.

CurrencyTextBox

Supports the Currency behavior.

DateTextBox

Supports the Date behavior.

TimeTextBox

Supports the Time behavior.

DateTimeTextBox

Supports the DateTime behavior.

MaskedTextBox

Supports the Masked behavior.

MultiMaskedBehavior

Takes a mask and adopts its behavior to any of the above classes.

Usage

I've built all these classes into a DLL so that the TextBox-derived classes may be used inside the Visual Studio .NET IDE as custom controls. Here are the steps required for adding them to your project and using them in your code:

  1. Open your own project inside Visual Studio .NET.
  2. Open the file containing the form you wish to add the control(s) to.
  3. Right click on the Toolbox and select "Customize Toolbox".
  4. Click on the .NET Components tab.
  5. Click the Browse button and select the AMS.TextBox.dll file.
  6. The 9 controls will be added to the Customize toolbox and will be check marked. Click on the Namespace column to sort by it, so you can see them more easily.
  7. If you wish, remove the checkmark from the ones you're not going to use.
  8. Click OK. The controls will then appear on the toolbox.
  9. Drag and drop them to your form and use them like regular TextBoxes.
  10. The behavior-related properties will appear under the Behavior category in the Properties toolbox of the form designer.

Points of Interest

While porting these classes from C++, I came upon several important differences in how things are done. I decided to make note of these differences for future reference and added them here for anyone who may work on a similar task. If you want to read on, I recommend you first become familiar with the C++ classes.

Names

In the .NET world, Edit boxes are called TextBoxes (and Text controls are called Labels). So I knew that to keep things as .NETish as possible I had to rename my classes. I replaced the "Edit" suffix with "TextBox" and I put everything into a namespace called AMS.TextBox to keep the names as simple as possible.

No Hungarian Notation

For years I've used my own variation of Hungarian notation in C++ code. I thought it was a great way to ease the pain of maintenance.

Then I started writing Java code and I had to conform to the standards set by my group: no Hungarian notation. So I stopped using it, at least for Java. And to my surprise after about a month, I wasn't missing it! It was actually liberating to write variables without the extra baggage of the type prefix. It also made the code easier to read. I came to the realization that in well written, modular code it's rarely necessary to make each variable's type evident within the name itself. It's just overkill and it makes the name larger and more cluttered.

So I gave it up for good in Java, while I kept it in my existing C++ code for the sake of consistency. I had even replaced the m_ prefix (for member variables) with the this. prefix.

Then came C# and this project. I knew that Microsoft's convention had been to drop Hungarian notation for .NET, so I happily continued on as I had with Java. But I faced a small problem. I wanted to make the code CLS compliant so that it could be used and extended by any .NET language. This caused a problem for protected variables with names like "separator" and then a corresponding property called "Separator". Since the names were the same except for the casing, the code was not CLS compliant (for case-insensitive languages). So I decided to switch from using this. for member variables back to the old m_ convention, which I had always liked.

And that's the notation I use in this project. No Hungarian notation except for the m_ for all member variables (fields).

No Multiple Inheritance

When I originally wrote my C++ classes, I made the decision to split the CEdit classes from their behaviors - I believe this follows the Bridge pattern. This strategy gave me the flexibility of being able to plug the behaviors into multiple CEdit-derived classes as needed, which worked great for the CAMSMultiMaskedEdit class. In addition, I used C++ multiple-inheritance feature to conveniently inherit the classes from CEdit and their respective behavior(s) simultaneously.

As we all know, .NET does not support multiple implementation inheritance, so I had to come up with an alternative. I initially decided to forgo the idea of the TextBox-derived classes having the methods and properties of their respective behaviors, which multiple inheritance so conveniently allowed me. Instead I added a read-only property to each class called Behavior which returns the Behavior-derived object currently associated with the TextBox object. So any behavior-specific action would be taken via this property.

This approach made life much easier for me, the library developer, but not for anyone using the library. Whereas in C++ you could do this:

C#
CAMSDateTimeEdit dt ...
int day = dt.GetDay(); 

In C# now you would need to do this:

C#
DateTimeTextBox dt ...
int day = dt.Behavior.Day;

I went with this design for some time until I came to the conclusion that it just wasn't right. It was a step backward, and all because I didn't want to spend extra time wrapping the Behavior's public members in the TextBox-derived classes. So I bit the bullet and did it; I went through each TextBox class and added its corresponding Behavior's public methods and properties as members of the class. This essentially turned the TextBox classes as wrappers of their Behavior classes. It was a lot of extra work (caused by the lack of MI), but I think it was worth it. Now the TextBox classes are similar to their C++ counterparts:

C#
DateTimeTextBox dt ...
int day = dt.Day;

This is not only more intuitive but also makes the TextBox classes more friendly for the form designer.

Less Coupling

For the C++ classes, I decided to make the CAMSEdit::Behavior class work only with classes derived from CAMSEdit. This definitely made life easier, since the CAMSEdit class was mine and I could enhance it with whatever methods were needed by the Behavior classes (i.e. GetText, GetValidText, IsReadOnly). But the problem was that this created a tight coupling between the Behavior classes and CAMSEdit, which any new class would have to account for.

For C#, I changed the Behavior classes to be much more independent. Now they work with any classes derived from System.Windows.Forms.TextBoxBase. This gives them the flexibility to be associated with just about any TextBox class and not just the ones derived some class of mine.

Additionally, I made it very easy to associate Behavior classes to textboxes. You just instantiate the behavior class and pass the textbox object in the constructor. Here's an example:

C#
MyTrustyTextBox textbox = new MyTrustyTextBox();

// Alter the textbox to only take Time values
TimeBehavior behavior = new TimeBehavior(textbox);

That's it! From that point on, the textbox behaves according to the rules of that behavior. This is in sharp contrast to C++ where not only did the class need to be derived from CAMSEdit, but you also needed to forward several message handlers to the associated Behavior, as explained next.

Events

If you look at the C++ code, you'll notice that the Behavior classes rely on their associated CAMSEdit object to forward the relevant messages to them (OnChar calls _OnChar, OnKeyDown calls _OnKeyDown, etc.). Well, thanks to delegates I didn't need to do that in C#. All I had to do was make the Behavior class add event handlers to the textbox object that would call methods in the Behavior class. And since these methods are declared virtual in the Behavior class, then all the derived classes needed to do was, override them to provide their own functionality. This is a more elegant approach that in C++ would have ended up looking more like a hack if I had decided to implement it.

In addition, while in C++ I handled the messages directly (i.e., OnChar for WM_CHAR, OnKeyDown for WM_KEYDOWN, etc.), .NET does not provide direct handlers for some of these messages. The only way to do it, as far as I could see, was to override WndProc inside the textbox classes themselves and trap the messages there inside a switch statement.

Instead, I decided to try the available event handlers to see if they could do the job. So I used the KeyPress event for WM_CHAR, KeyDown for WM_KEYDOWN, TextChanged for WM_SETTEXT and LostFocus for WM_KILLFOCUS. They worked just fine and allowed the Behavior classes do all the work, as described above.

Validations

The Behavior classes are all about validations - basically ensuring that the user enters the proper data into the textbox. Some validations are performed as the user types while others happen when the user leaves the control.

As you may know, the System.Windows.Forms.Control class contains properties and events designed to help the developer validate the control's value when control loses focus. I decided to take advantage of this built-in mechanism and move a lot of the functionality in the old OnKillFocus handlers to a Validating event hander I added to the Behavior class. This handler not only validates the data, but also gives error feedback to the user via a message beep, message box, or a small icon (ErrorProvider). It can even be configured to automatically set the control to a valid value if necessary.

This is all accomplished by modifying the Flags property and setting the corresponding ValidationFlag value(s). Here's an example of how to make a beeping sound and show an icon if the control's value is empty or not valid when the Validating event is triggered:

C#
DateTimeTextBox dt ...
dt.ModifyFlags((int)ValidatingFlag.ShowIcon
    | (int)ValidatingFlag.Beep,  true);

Of course, this also requires that the textbox's CausesValidation property is set to true, which by default it is. As an alternative, you may invoke all this functionality yourself via the textbox's Validate method. It is called by the Validating event handler to set the Cancel property.

Properties

First of all, I just have to say that I love properties! They're a welcome addition to C# (and they should have been part of Java). When converting these classes, a lot of methods became excellent candidates for properties. So I happily went and converted all of them.

Then I took a second look and reconsidered what I had done. I found that while a lot of methods were undeniably property-material, others were a bit more questionable. The most prominent one was Behavior.GetValidText. This one initially appeared like another method worthy of becoming a property with a getter. However, I later decided that properties should be treated by the programmer as convenient ways of quickly reading attributes of an object. If you look at the code for most GetValidText implementations, there is quite a bit of processing going on in there, much more than the typical return someField; which you find in most property getters. In other words, the "valid text" is not really a property of the behavior. It needs to be deciphered every time the method is called. So leaving it as a method does not give the impression that it's readily available and quickly retrieved.

This was the criteria I used when deciding which methods to turn into properties. If the property's getter had a simple implementation and the property itself made sense for the class, then I converted it; otherwise I left it as a method.

Documentation

After I had finished porting the classes, I decided it would be nice to document the code. The C++ code already had comments on the top of every method, so that gave me a head start. However I decided to explore the XML Documentation tags to see what additional benefits I could gain.

My first impression is that they were very verbose. Most of them require an opening and closing tag, which if written on separate lines can make each section take at least 3 lines! For methods that take multiple parameters that would mean an extra 3 lines of comment per parameter. That's a lot of space taken up in comments!

The benefits were another story. You spend some time up-front putting up with all the verbosity, but the end result is nicely formatted online help. This would also mean that I wouldn't have to spend extra time documenting every method within this article, like I did for the C++ one. You just add the DLL and its XML file to your project - Visual Studio takes care of the IntelliSense for you automatically!

So I did it! I manually documented every method, property and field in the classes, even the private ones, right in the code. To cut down on the waste of space produced by the opening and closing tags, I decided to only put the opening tags on lines by themselves. Closing tags would simply go as part of the last line of the section. I also added a couple of spaces for indentation to the contents to make them easier to read. Here's an example:

C#
/// <summary>
///     Initializes a new instance of the class by also setting its 
///     mask and corresponding Behavior class. </summary>
/// <param name="mask">
///     The mask string to use for determining the behavior. </param>
/// <seealso cref="Mask" />

There's a tool called NDoc that generates help files from source code, in a variety of formats. I used it to generate an MSDN-style help file, AMS.TextBox.chm, which I've included in the download. Enjoy!

Acknowledgments

I'd like to thank Gerd Klevesaat for helping me understand the complexities of dealing with controls having sub-properties. I wanted to give users of my textbox controls, the ability to directly manipulate the various properties of the Behavior property, right from the form designer. After many trials and tribulations, I decided not to implement such functionality since it doesn't work as it should, but it was fine since I ended up wrapping most of the behavior public methods and properties inside the textbox classes.

History

  • Version 1.0 - Sep 15, 2003
    • Initial release. :-)
  • Version 1.1 - Nov 7, 2003
    • Fixed a problem related to the form designer, which was generating code for properties that I had marked with the Browsable(false) attribute, such as Day, Month and Year. The designer would set these to 0 and at run time they would raise exceptions since 0 isn't a valid value. To prevent this problem, I used the ControlDesigner class to manually remove those properties at design time. Thanks to Spiros Prantalos for reporting this problem.
    • Fixed a bug in the NumericBehavior class reported by Wojtek Swieboda (see below).
    • Added a description for the AMS.TextBox namespace to the help file (AMS.TextBox.chm).
  • Version 1.2 - Nov 26, 2003
    • Added a couple of NumericBehavior.LostFocusFlag values to allow the LostFocus handler to be called when the Text property is set or the text changes.
    • Added the new CallHandlerWhenTextPropertyIsSet flag to CurrencyBehavior so that when the Text property is set, the value is automatically appended with ".00" (if necessary). Thanks to Yan Khai Ng for bringing it to my attention.
  • Version 1.3 - Jan 9, 2004
    • Changed the Month, Day, Hour, Minute, and Second getters to be less strict about the expected format. This allows the Text property of the Date, Time, and DateTime controls to be properly bound to database columns. Thanks to Terry Carroll for reporting the problem.
    • Fixed a bug in the Alphanumeric behavior that wasn't removing invalid characters as expected.
  • Version 2.0 - Jan 30, 2004
    • Added extra event handlers to intercept when objects are added to the DataBindings collection of the control. This fixes problems with Date, Time, DateTime, and Numeric controls bound to nullable database columns. It also fixes problems with the Currency textbox which was not being properly converted to a Decimal when bound. Thanks to Terry Carroll, ACanadian, and MattH for reporting the problem.
    • Added a check to the ReadOnly property to prevent the Up/Down arrows from changing the Date or Time controls when read-only. Thanks to Terry Carroll for reporting this problem.
    • Changed the default range of values for the Integer textbox to the min and max 32-bit values to prevent a crash in the VS IDE. Thanks to mdenzin for reporting the problem and suggesting this fix.
    • Fixed a bug that was causing validations to fail when the control was empty. Thanks to marcelloz for helping me notice the bug.
    • Added a static ErrorCaption property to the Behavior class to be used in error message boxes for failed validations. Thanks to marcelloz for giving me the idea.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
I've done extensive work with C++, MFC, COM, and ATL on the Windows side. On the Web side, I've worked with VB, ASP, JavaScript, and COM+. I've also been involved with server-side Java, which includes JSP, Servlets, and EJB, and more recently with ASP.NET/C#.

Comments and Discussions

 
QuestionGood ! Pin
xuanhieu1234521-Jun-13 5:18
xuanhieu1234521-Jun-13 5:18 
GeneralMy vote of 4 Pin
Flash2009-MX29-Nov-12 8:47
Flash2009-MX29-Nov-12 8:47 
GeneralGood One! Pin
Sushant Joshi7-Sep-10 2:18
Sushant Joshi7-Sep-10 2:18 
GeneralMy vote of 3 Pin
scandstone25-Jun-10 22:09
scandstone25-Jun-10 22:09 
QuestionOn the place to put of a prefix, put a suffix Pin
Gerber Samuel23-Mar-10 22:33
Gerber Samuel23-Mar-10 22:33 
QuestionBug Fix? Pin
snowman718613-Jan-10 8:37
snowman718613-Jan-10 8:37 
GeneralDecimal error Pin
Stephcml3-Dec-09 5:01
Stephcml3-Dec-09 5:01 
GeneralNumericTextBox Fix Pin
wahida21610-Jun-09 7:19
wahida21610-Jun-09 7:19 
GeneralMy vote of 2 Pin
erik20558-Mar-09 22:25
erik20558-Mar-09 22:25 
AnswerBug Fix Pin
Baltazar_qc28-Nov-08 12:48
Baltazar_qc28-Nov-08 12:48 
GeneralRe: Bug Fix Pin
factorx2222-May-09 12:20
factorx2222-May-09 12:20 
QuestionTimeTextBox Pin
Cristiano de Morais Lima1-Sep-08 7:38
Cristiano de Morais Lima1-Sep-08 7:38 
AnswerRe: TimeTextBox Pin
s.kleinschmidt15-Jan-09 4:38
s.kleinschmidt15-Jan-09 4:38 
GeneralRe: TimeTextBox Pin
longlybits10297-Dec-10 14:22
longlybits10297-Dec-10 14:22 
GeneralCompile error Pin
bvr6327-Jul-08 7:20
bvr6327-Jul-08 7:20 
GeneralRe: Compile error Pin
mtwombley27-Sep-11 11:56
mtwombley27-Sep-11 11:56 
QuestionCan it be used in VC++/MFC (VS 6.0) Pin
rupanu12-Jun-08 20:24
rupanu12-Jun-08 20:24 
GeneralFix for if you get "Value cant be -1" bug Pin
fc27-May-08 0:35
fc27-May-08 0:35 
GeneralVisual Studio 2005 Pin
Member 24630176-Apr-08 10:33
Member 24630176-Apr-08 10:33 
GeneralMinor addition to NumericTextBox for binding Pin
Ken Teague30-Oct-07 8:57
Ken Teague30-Oct-07 8:57 
GeneralDate Range Input Pin
dmenne14-Aug-07 21:36
dmenne14-Aug-07 21:36 
QuestionWhat License? Pin
Kunalpatel4-Jun-07 5:59
Kunalpatel4-Jun-07 5:59 
QuestionIs not beeping or showing any error messages anywhere Pin
corsairX10-May-07 4:29
corsairX10-May-07 4:29 
QuestionRangeMax and Numbers with comma Pin
HomerGER17-Apr-07 9:11
HomerGER17-Apr-07 9:11 
GeneralNumericTextBox [modified] Pin
aljay3-Apr-07 20:56
aljay3-Apr-07 20:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.