Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SP Numeric Edit Control

0.00/5 (No votes)
16 Nov 2005 1  
Masked numeric edit ActiveX control.

Introduction

When programming GUI, sometimes you need to provide an edit box to input numeric values. Usually the standard edit box is used for that; it has a ES_NUMBER style that lets you restrict user's input, allowing only digits to be entered. It's a useful option but it does not cover all cases met in practice. For example, when you need to input floating-point numbers in exponential notation you should allow the user enter not only digits but also a decimal separator and an exponent symbol. Moreover, the floating-point number format is more complex than a simple sequence of digits, so you have to parse the entered text to make sure that it can be converted to a number and possibly let the user know about detected errors. So I've made a special ActiveX control based on the standard edit box that extends its functionality and offers additional options for handling numbers.

Features

First of all, I'd like to note that this control deals directly with numeric data types, but not text strings. Internally, it performs a conversion of numeric values to their textual representation and back. The conversion is performed according to a certain format defined by a mask and an additional set of parameters (format properties). The mask is a text string that defines a regular-like expression that matches certain syntaxes. You can specify your own mask or use the default one that is automatically generated according to the system/locale settings. Format properties are used during formatting, scanning, and generation of the default masks.

Fig. 1

The control operates in two modes: display and editing. Editing mode is switched on when the control gets the keyboard input focus; otherwise, it stays in display mode. Each mode has its own set of format configuration (mask and format properties). Therefore, the user can see two different textual representations of the same numeric value (see Fig. 1). In display mode, the value is only converted in text, but in editing mode a two-way conversion is performed. Usually, for editing mode, you should use a simplified format while, for display mode, you can enable a full set of features. Consider that you want to handle currency values. It would be convenient for the user to see a number like $4,499.98, but at the same time, during editing, the user should not be forced to enter monetary symbols and separate groups with commas; he or she just has to enter essential data: 4499.98. This is the reason why the two modes are provided.

Mask

A mask consists of the patterns, separated by semicolons. Every pattern corresponds to a certain value range or state. The number of patterns in a mask and their purpose depend on the data type handled by the control. See Table 1 for more information.

Table 1: Mask patterns corresponding to data types.

Datatype Pattern1 Pattern2 Pattern3 Pattern4 Pattern5 Pattern6 Pattern7 Pattern8 Pattern9
vtInt8 (VT_I1) positive number negative number zero null - - - - -
vtInt16 (VT_I2) positive number negative number zero null - - - - -
vtInt32 (VT_I4) positive number negative number zero null - - - - -
vtInt64 (VT_I8) positive number negative number zero null - - - - -
vtUInt8 (VT_UI1) non-zero number zero null - - - - - -
vtUInt16 (VT_UI2) non-zero number zero null - - - - - -
vtUInt32 (VT_UI4) non-zero number zero null - - - - - -
vtUInt64 (VT_UI8) non-zero number zero null - - - - - -
vtFloat (VT_R4) positive number negative number positive zero negative zero positive infinity negative infinity quiet NaN signaling NaN null
vtDouble (VT_R8) positive number negative number positive zero negative zero positive infinity negative infinity quiet NaN signaling NaN null

Any numeric value is formatted according to a definite pattern. For example, a mask for double values (vtDouble) consists of 9 patterns; negative value will be formatted with pattern 2, positive infinity with pattern 5, and so on.

There are two types of patterns: value and literal. Value patterns are used to format definite numeric values such as positive and negative numbers and zero. The rest are literal patterns. Literal patterns are used to represent a special state of the value; for example when the value is NULL or the floating-point value is negative or positive infinity, it will be formatted with the corresponding literal pattern.

In its turn, a pattern consists of segments. All literal patterns have only one segment, but value patterns have at least one segment corresponding to the integer part of a number and can have two additional ones: prefix and suffix. Floating-point patterns additionally have segments for fraction and exponent parts. An exponent part exists only in E-notation (exponential) patterns. Integer, fraction and exponent segments are included in the number part of a pattern.

  • Literal pattern schema:

    { literal }

  • Integer value pattern schema:

    { prefix } | { integer } | { suffix }

  • Floating-point value F-format pattern schema:

    { prefix } | { integer } { . fraction } | { suffix }

  • Floating-point value E-format pattern schema:

    { prefix } | { integer } { . fraction } { e exponent } | { suffix }

Segments are always ordered as it's listed above. Prefixes and suffixes are separated with a "|" symbol from the value part. They are optional, but when used, they both must be present; however, you may specify an empty prefix or suffix. An integer segment begins right after the "|" prefix delimiter if the one is preset or from the pattern's beginning otherwise. Generally, a fraction starts from a "." symbol and exponent from the "e" symbol. However, when the segment is completely included in an optional block (discussed below), it starts from the token opening that block. Integer fractions and exponent segments are mandatory for corresponding patterns.

During formatting and scanning, the digits of a number are handled sequentially in a definite order, depending on what segment is being processed. Integer and exponent parts are processed from right to left, but the fraction part is processed from left to right.

Segments are composed of tokens. Every token specifies a definite instruction for the formatting procedure or is used as a separator for different sections of a mask. Only three types of tokens can be used inside segments: control tokens, placeholders and literals.

Table 2: Tokens

Delimiters
; end of pattern
| prefix/suffix delimiter
Control tokens
( open repeatable block
) close repeatable block
[ open optional block
] close optional block
Placeholders
0 digit placeholder with default zero value
_ digit placeholder with default space value
# digit placeholder
- negative sign
+ positive sign
$ currency symbol
% percent symbol
per mile symbol
, thousand (group) separator
. decimal separator
e exponent
Reserved
{, }, <, > Reserved for future extensions.
Literals
\ Escape symbol.
any character

Any character can be used as a literal. Character literals corresponding to the reserved symbols should be preceded with a back slash "\".

In addition, there are three special escape symbols:

  • \r - generates carriage return (CR)
  • \n - generates line feed (LF)
  • \t - generates tab

Here, you can see a few examples of the masks:

  1. Floating-point number in F-format (non-exponential representation):
    (###,)##0.00(#);-(###,)##0.00(#);0.00;0.00;\+INF;\-INF;QNaN;SNaN;NULL
  2. Floating-point number in E-format (exponential representation):
    (###,)##0.00(#)e\+(#)0;-(###,)##0.00(#)e\-(#)0;
                   0.00e\+0;0.00e\-0;\+INF;\-INF;QNaN;SNaN;NULL
  3. Floating-point number in simplified E-format (exponential representation):
    (#)0[.0(#)][e\+(#)0];-(#)0[.0(#)][e\-(#)0];0[.0][e\+0];
                             0[.0][e\-0];\+INF;\-INF;QNaN;SNaN
  4. Floating-point currency in US-format:
    $(###,)###.00(#);($|(###,)###.00(#)|);$.00;($|.00|);\+INF;\-INF;QNaN;SNaN

By using control tokens, you can define repeatable and optional blocks. The formatting procedure continues to use a repeatable block until all digits have been handled. An optional block is used once, only if there are unhandled digits. Blocks defined by control tokens cannot partially overlap each other, but one block can be nested inside another. Any block must be entirely located within only one segment. Each opened block must be closed with a corresponding token. Repeatable blocks are applicable only for the value part segments such as integer, fraction, and exponent parts.

Placeholders are replaced with the digits or symbols they are related to. For example, "+" will be replaced with a positive sign, "$" with a monetary symbol, and so on. "0", "_", and "#" are digit placeholders; they are used not only for output but also for input during scanning in editing mode. There is a difference between them; during formatting, when there are no digits to handle, "0" and "_" are substituted with zero and space symbols correspondingly but nothing will be generated instead of "#". Digit placeholders are used only inside value part segments.

Literals are just written as they are.

Format properties

By using format properties, you get additional options for the configuration of the formatting and scanning operations and customization of the default mask generation. In fact, fpidNegativeInfinity, fpidPositiveInfinity, fpidQuietNaN, fpidSignalingNaN, fpidNull, fpidLeadingZero, fpidDecimalDigitsNumber, fpidExponentDigitsNumber, fpidGrouping, fpidNegativePattern, and FpidPositivePattern are only used for the generation of the default mask so that it has no effect when a custom mask is used. Other properties are also involved in formatting and scanning. See Table 3 for more information.

Table 3: Format properties

ID Description
fpidWhiteSpace Symbol used as the default substitution of the white space token ("_").
fpidZero Symbol used as the default substitution of the zero token ("0").
fpidNegativeSign String value for the negative sign.
fpidPositiveSign String value for the positive sign. If numbers are written without any sign it should be interpreted as positive, use empty string for this property value.
fpidNegativeInfinity Representation of negative infinity.
fpidPositiveInfinity Representation of positive infinity.
fpidQuietNaN Representation of "quiet not a number" value.
fpidSignalingNaN Representation of "signaling not a number" value.
fpidNull Representation of a Null value.
fpidCurrency String used as the monetary symbol.
fpidPercent String used as the percent symbol.
fpidPermille String used as the permille symbol.
fpidExponent String used as the exponent symbol.
fpidDecimalSeparator Character(s) used as the decimal separator.
fpidGroupSeparator Character(s) used to separate groups of digits to the left of the decimal.
fpidLeadingZero Specifier for leading zeros in decimal fields in the mask generated by default. If set to True - leading zeros will be added, otherwise no leading zeroes will precede the decimal separator.
fpidDecimalDigitsNumber Minimal number of fractional digits to be printed.
fpidExponentDigitsNumber Minimal number of exponent digits to be printed.
fpidGrouping Sizes for each group of digits to the left of the decimal. An explicit size is needed for each group, and sizes are separated by semicolons. If the last value is zero, the preceding value is repeated.
fpidNegativePattern Negative number mode, that is, the format for a negative number.
fpidPositivePattern Positive number mode, that is, the format for a positive number.

Control architecture

The control is implemented with several classes (see Fig. 2).

Fig. 2

The main class of the control is called NumericEditBox. It implements the INumericEditBox interface, which lets you change various properties related to the control's visualization and behavior; by using its Value property you can access the numeric value handled by the control.

NumericEditBox contains another object called Formatter. It can be accessed through the Formatter property at run time, or FormatterParams in the control's designer.

Formatter maintains the value type, format type, masks and format properties for display and editing modes. It actually manages the formatting process, and gives necessary facilities for its configuration.

In its turn, the Formatter contains two collection objects (FormatProperties) that represent format properties for display and editing modes.

How to use it

First of all, make sure that the SpNumericEdit.dll is registered on your PC. If it's not, use the command below to register the COM control.

regsvr32 SpNumericEdit.dll

If you have built the control with Visual Studio, it should be registered automatically.

During design-time, you can use the FormatterParams property (see Fig. 3) to open the special property page (see Fig. 4) that makes the formatter configuration easier.

Fig. 3

Fig. 4

Also, it is possible to change the format parameters at run-time. You can do that using the IFormatter::Configure method.

    HRESULT Configure([in] ValueTypeConstants enValueType, 
                  [in] FormatTypeConstants enFormatType, 
                  [in] VARIANT vDisplayFmtProps,
                  [in] VARIANT vEditingFmtProps,
                  [in, defaultvalue(NULL)] BSTR bsDisplatMask,
                  [in, defaultvalue(NULL)] BSTR bsEditingMask);

The method takes six parameters:

enValueType Data type of the value (vtInt8, vtInt16, etc.).
enFormatType Format type (ftNumeric, ftCurrency, etc.).
vDisplayFmtProps, vEditingFmtProps Format properties for display and editing modes correspondingly. This parameter is a VARIANT that can hold SAFEARRAY or IFormatProperties. If SAFEARRAY is passed, each of its elements corresponds to the property value, while the element index corresponds to the property ID. If the element value is NULL, then the corresponding property will be set to the system default value. To assign default values to all the properties, just pass VARIANT of VT_NULL or VT_EMPTY type.
bsDisplatMask, bsEditingMask Mask expressions for display and editing modes correspondingly. If a NULL value is passed, the default mask is generated according to the system/locale settings.

The following C++/MFC code snippet demonstrates how to configure the formatter at run-time:

// Custom display mask

LPCTSTR lpcwszDisplayMask = _T("It is positive number: \\(+(###,)##0.00(#)\\);") \
                            _T("It is negative number: \\(-(###,)##0.00(#)\\);") \
                            _T("It is positive zero: \\(0.00\\);") \
                            _T("It is negative zero: \\(0.00\\);") \
                            _T("It is positive infinity: \\(\\+INF\\);") \
                            _T("It is negative infinity: \\(\\-INF\\);") \
                            _T("It is quiet not-a-number: \\(QNaN\\);") \
                            _T("It is signaling not-a-number: \\(SNaN\\);") \
                            _T("This is NULL");

// Allocate safe array with bounds corresponding to the range of prorety ID

const long lMin = CNumericeditbox::fpidWhiteSpace;
const long lMax = CNumericeditbox::fpidPositivePattern;

long rgIndices[1];

SAFEARRAYBOUND rgsabound[1];
rgsabound[0].lLbound = lMin;       
rgsabound[0].cElements = static_cast<ULONG>(lMax - lMin + 1);

COleSafeArray arrDisplayFmtProps;
arrDisplayFmtProps.Create(VT_BSTR, 1, rgsabound);

// Change fpidNull property

CComBSTR cbsValue = L"null";
rgIndices[0]  = long(CNumericeditbox::fpidNull);
arrDisplayFmtProps.PutElement(rgIndices, BSTR(cbsValue));

// Change fpidGrouping property

cbsValue = L"3;2;0";
rgIndices[0]  = long(CNumericeditbox::fpidGrouping);
arrDisplayFmtProps.PutElement(rgIndices, BSTR(cbsValue));

// Change fpidGroupSeparator property

cbsValue = L"'";
rgIndices[0]  = long(CNumericeditbox::fpidGroupSeparator);
arrDisplayFmtProps.PutElement(rgIndices, BSTR(cbsValue));

// Update formatter parameters

CFormatter fmt = m_nedit.get_Formatter();

fmt.Configure(CNumericeditbox::vtDouble, CNumericeditbox::ftNumeric, 
              COleVariant(arrDisplayFmtProps), COleVariant(), 
              lpcwszDisplayMask, NULL);

Conclusion

The component is free, so please try it. Hope you'll find it useful. Please let me know about bugs and other problems if you find any.

Enjoy!

History

  • 02/15/2005. Version 1.0 beta release.
  • 03/24/2005. Version 1.0 release.
    • New formatting library is used with the control.
    • Provided range checks for the values during input.
  • 08/16/2005. Version 1.2 alpha release.
    • Formatting library and ActiveX has been redesigned.
    • Some bugs have been fixed.
  • 10/31/2005. Version 1.2 release.
    • Several bugs found in alpha version have been fixed.
    • Source code has been restructured.

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