Click here to Skip to main content
Click here to Skip to main content

A Numeric Textbox with a Twist

, 3 Jan 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
A textbox which accepts formatted or unformatted numerics only, plus a suffix multiplier.
image002.jpg

image004.jpg

Introduction

For a project I was recently working on, I needed to specify a quota size. For production, it could always be GB, but for testing purposes, I wanted to use MB or even KB values. That got me thinking about how to enter this kind of information more easily where the magnitude of the number could be specified as part of the number, much like the exponent of a floating-point number, only more general. So, I have invented yet another numeric textbox but, with an additional twist. This textbox allows a user-specified set of suffix strings which are defined with an associated multiplier for each, such that one could enter “1GB” into the textbox and the internal numeric value of the enhanced textbox would return 1,073,741,824. Similarly, entering “1.5 Euro”, as of this writing, would return 2.211555, the value in dollars.

Implementation

There are two issues to discuss here. The first is the use of a Regular Expression evaluator to limit the acceptable values in the textbox. The second, of course, is the implementation of the optional suffixes.

Validating the Textbox Data with a Regular Expression

I tried to make this control as general as possible by adding internationalization to it. Consequently, the Regular Expression used to validate the text needs to be created dynamically depending upon the locale in which the program is run.

Every time the text in the textbox is changed, the new string value is validated with the Regular Expression. If it does not match, then the numeric value associated with the textbox is set to 0. If there is a match, then the numeric string is converted to a decimal value which will be returned as the numeric value of the textbox. I realize that this is a little inefficient since it requires evaluating each and every change to the textbox, but in reality, the number of keystrokes that a user will enter into a single-line textbox is rather small, making the overhead inconsequential. The advantage is that the calculated value can be displayed immediately, as I have done in the associated demo program.

The basic Regular Expression I use for the US locale is:

^\s*([+-]?\s* (?: (?: (?:\d{1,3}[\,]) (?:\d{3}[\,])* (?:\d{3}) ) | \d+ | 
(?=\.\d)) (?:\.\d*)? )\s* ([^\,\.\s\d].*)? \s*$ 

Note that this Regular Expression supports numbers containing an optional leading sign, an optional decimal separator, and optional grouping characters, e.g., -123,456.789, optionally followed by an arbitrary suffix string. A valid number consists of an optional sign followed by either:

  1. a string of digits, or
  2. a set of groups (group size is determined by locale), where the leftmost group consists of 1 or more digits followed by a separator; the second group is optional, and consists of the group size number of digits followed by a separator, and the rightmost group consists of a group size number of digits, optionally followed by a decimal separator, followed by zero or more digits.

The internationalization of this requires changing, at runtime, the default group separator, “,”, the default decimal separator, “.”, and the group sizes, typically 3. You can check out the details of this in the source code.

At the end of the expression, I have added an additional expression to match any string not starting with whitespace, a separator character, or a digit. This string is extracted from the number text, and is used to determine the scale-factor, viz., the multiplier value to apply to the numeric value of the string.

Calculating the Scaled Numeric Value of the Textbox

If the scale factor is omitted, then we don't have to do anything more. If one is specified, we look up the scale factor in a collection to be discussed below, and simply multiply the textbox numeric value by the numeric value associated with the scale factor. Note that the search is brute force. It could have been implemented via a hash table, but in general, the number of scale factors to be used for this particular type of control is so small that a sequential search is usually optimal.

Retrieving the Scale Factors

The scale factor names and associated values are stored in a collection of name-value pairs, where the name is the uppercased suffix, and the decimal value is the multiplier to be used when the associated name is specified.

I created a strongly typed collection class which inherits from CollectionBase. Doing this allows my UserScaleItems property to be modified at design time. Each item in the NVPCollection class is a member of the NVP class, which merely contains two properties, the string name and the decimal value.

Retrieving the scale factor decimal value is done by iterating through the collection, looking for a match with the scale factor name, e.g.:

foreach (NVP nvp in scaleItems)
{
    if (nvp.Name == scaleSuffix) 
    { 
        numericValue *= nvp.DecimalValue; 
        goodValue = true;
    }
}

Storing the Scale Factors

The scale factor data, which is stored in scaleItems, which is an instance of the NVPCollection class, may be entered at design-time or via one of several ways at run-time. Since I used a strongly typed collection class to store the name-value pairs and exposed it as the UserScaleItems property, the user can click on the ellipsis after UserScaleItems to add a set of name-value pairs to the collection. Note that once they have been added this way, they will not subsequently be overridden by any run-time settings. Once a name is entered into the collection, all subsequent additions of the same name will be ignored.

image006.jpg

Name-value pairs may be entered at run-time in a couple of different ways:

// Here are two ways to add suffixes at run-time. Note that run-time items
// will be ignored if the item name already exists in the collection, e.g., 
// from a development-time entry: 
 
dtb.UserScaleItems.Add("+TAX", 1.0875M);
dtb.LoadSuffixes("%", ".01", "Euro", 
                 "1.47437", "Yen", "0.009168");
 
// Here is a quick way to load common, built-in suffixes:
dtb.LoadDefaultSuffixes();
 
/* The above method is equivalent to the following:
dtb.LoadSuffixes("B", "1M", 
                 "M", "1000", 
                 "MM", (1000M * 1000M).ToString(),
                 "MMM", (1000M * 1000M  1000M).ToString(),
                 "KB", "1024",
                 "MB", (1024M * 1024M).ToString(),
                 "GB", (1024M * 1024M * 1024M).ToString(),
                 "TB", (1024M * 1024M * 1024M * 1024M).ToString()); 
*/

The attached demo will let you enter numeric text (including group separators), optionally followed by one of the above suffixes, or one of the design-time suffixes which you can set by clicking on the UserScaleItems ellipsis as described above. I use it primarily to enter large byte values without having to multiply them beforehand by the appropriate power of 1024. However, as hinted above, one could download the current FX symbols and exchange rates and allow the user to enter data in any currency directly, without having to have a separate dropdown listbox or additional textbox to specify the appropriate currency.

Using the Control in your Projects

First, compile the control and put the resulting DLL in the folder of your choice, preferably one containing your reusable assemblies. Alternatively, you can just copy the files DecimalTextBox.dll and DecimalTextBox.xml from the supplied zip file.

Open a project and display the toolbox. Go to the Common Controls section, right click, and select “Choose items”. In the “Choose Toolbox Items” dialog box, press the Browse button, go to the folder containing DecimalTextBox.dll, and select it for your project. Now, you can use this control to enter numeric values and do automatic conversions.

History

  • Release 1.0.7.5 – 02/26/08
  • Release 1.0.8.0 – 01/01/09 - This release adds a conditional allowing one to generate a version for case-sensitive suffixes.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Tony Zackin
Software Developer (Senior) Takamomto, LLC
United States United States
I have been doing database related programming in the financial services industry for over 20 years on various platforms. I used to be a UNIX Sybase DBA but now prefer programming in the .NET environment using C# and SQL Server.

Comments and Discussions

 
GeneralThanks! PinmemberDaun Yeagley28-Dec-08 16:35 
QuestionA question about a more specialized use. PinmemberDaun Yeagley20-Dec-08 17:05 
AnswerRe: A question about a more specialized use. PinmemberTony Zackin28-Dec-08 16:02 
GeneralBad image links PinmemberRavi Bhavnani5-Mar-08 20:18 
GeneralRe: Bad image links PinmemberTony Zackin6-Mar-08 9:05 
GeneralRe: Bad image links PinmemberRavi Bhavnani6-Mar-08 9:10 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.141015.1 | Last Updated 3 Jan 2009
Article Copyright 2008 by Tony Zackin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid