In a recent project I was creating for evaluating investment properties I found I was going to need as many as 20 or so text boxes to accept monetary amounts, the purchase price, down payment, taxes, rental income, miscellaneous income, and a number of expenses beyond just the mortgage payment. I initially searched the web for examples of a control that would only accept digits, but was disappointed with what I found; likewise when I considered formatting the user input as currency. The solutions I encountered didn't do anything to ensure that the user couldn't make a mistake that wasn't caught and immediately rejected by the code, and the formatting examples I found were inspirational, but not quite what I wanted to accomplish. So, I decided that I would use pieces of what I'd found to create a custom cotrol that did exactly what I wanted it to do. The criteria were as follows:
- Any character other than a digit or a single decimal point was to be simply rejected as user input, and not even displayed in the textbox. In this way, since users couldn't possibly make any input errors, there'd be no need for error handling, and mercifully, no annoying pop-up error messages.
- On entering the control, the control's background color should change so there's no question about which field the user is in; when exiting the control, the background color changes back.
- Since all user input was to be "money" I wanted to format the user input on leaving the control to a currency format where the appropriate monetary symbol and thousand's separator would be automatically added to the text display, precision was set to two decimal places, and all the figures lined up neatly on the form.
As I found during testing, which as it turns out was amazingly simple once I opened and had running two instances of Visual Studio 2008 - one for developing my "DecimalBox" and one to test the "DecimalBox" - anything that can possibly go wrong, will. You'll find code that on first blush seems to add unnecessary code, or unnecessarily complicate things, but actually precludes the unhandled errors I was able to somewhat accidentally introduce during testing. I'll explain them as we go.
If you've tended to shy away from custom controls in the past thinking them either unnecessary or too time-consuming to develop, I hope the simple code here will allow you to see how, in the long run, they'll actually save time and cut development time next time you need something like a "DecimalBox" on your form. C# encourages reuse, encapsulation and code efficiency, and using custom controls can help you achieve that in your own code. In much the same way that using Microsoft-provided controls saves you development and support time, the one-time creation of a custom control will pay off in not only your current project, but the next time you need to accept, for example, only decimal user input, and the time after that, and the time after that as well. Developing custom control is a great skill to develop. I hope this simple control inspires you to consider developing your own custom controls - and contributing your successes to this forum. Truly, the entire community benefits from every contribution, and we all become better programmers.
One reviewer asked a wonderful question: "Why would I use your control rather than a numeric up-down counter?" which led me to consider the implications of why use a custom control at all. My response follows:
For any control I think there are three interwoven issues, functionality, maintainability, and user perception or experience. Remember, I needed 20 or more controls capable of:
- accepting user input in the form of a decimal value, excluding any and all extraneous characters
- I wanted to format the user input as currency on leaving the control, adding the appropriate culture-specific monetary symbol and the appropriate thousand's separator
- I wanted to change the background color on entering (to focus user attention to the appropriate control) and on leaving.
While requirement a) could be handled by the numeric up-down counter, requirement b) couldn't be accomplished because the control only displays digit or decimal values and not text (it's a 'numeric' up-down counter).
But more importantly - addressing requirement c) - is the question of managing 20 such identical controls. Should they be managed individually, that is, 20 separate "Focus - Leave" event handlers each containing identical code either to format the user's decimal input as currency or calling on a method in a separate class that formats the user input as currency? Or, could I create a custom control where in one place that format-as-currency code could be created and maintained, and the result displayed in one, 20, 100, or 10,000 controls, if so needed? From the perspective of maintainability, and not to forget the development cycle of creating this behavior, a single place, that is a single custom control, made the most logical sense.
Further, in the process of development, my initial effort was to simply add a '$' character (and later, to detect the presence of one to prevent a formatting exception error from occurring), and in testing I found that the simple approach didn't prevent or resolve problems. The point is, that doing development on one (custom) control allowed me to modify its code in one place and have the behavior propagate to the 20-or-so instances of the control in my form. The alternative, had it been necessary, would have been to change the code in either 20 separate locations (very inefficient) or in a single method in a separate class (more efficient, but requiring dedicated code in my project whereas in a custom control that code is in a separate). Additionally, of course, I now have a custom control that I can use in subsequent projects with no further development necessary.
I hope it's clear that focusing the development efforts in one place made both development and subsequent maintenance far simpler. Philosophically this results in code re-usability, one of the supposed hallmarks of the C# language. From another philosophical angle, this isn't much different from using a Microsoft-provided control that already has certain functionality already built in, much like your question of why not use the already-provided numeric up-down control, and the answer should be obvious: though it has its advantages, it does not do exactly what I want the control to do. The solution is a custom control, which as I found out, wasn't as difficult to develop as I initially thought, and allowed me to add a control to my form that does do exactly what I want it to do.
The user experience is not be ignored either. Users see text boxes all the time and are used to entering values in them, whether test or numeric. When they see a numeric up-down they see a control with arrows they have to use to alter the displayed value. Unless they get a visual cue, say a change in background color, they may not be aware that they can change the value directly by typing it in. In my project - real estate evaluation - values could vary from a few hundred dollars in one field to tens of millions of dollars in another. Clearly, users should be encouraged to enter the value they wish rather than use arrow keys to describe or select it.
To take this one step further in encouraging the use of custom controls, I also have dozens of TextBoxes where users can enter text information, names, addresses, expense categories, etc. Suppose I want to have the background color of each of these controls change as they user tabs through my forms? Should I add an event handler for "Focus - Enter" and "Focus - Leave" for each individual instance of these dozens of TextBoxes, changing the background color on entering, changing it back on leaving, or should I create a custom TextBox control with the appropriate background color changes "built-in" so that adding an instance of that control to my form - or any other project for that matter - carries with it the changes of background color that I wish the control to exhibit as the user tabs through the form? I hope the answer is obvious, that is, that the little effort required to create a custom control saves tremendous amounts of development and maintenance time in the longer term even though it cost a bit of time on the front end to develop it.
Using the Code
- Open Visual Studio 2008, click the “File” pull-down at the top of the screen, then “New” and “Project…”
- Click on “Windows Forms Control Library, then name it “DecimalBox”.
- In the “Solution Explorer” on the right of the screen, right-click the entry ending in “.cs” (by default this will be “UserControl1” and delete it.