SP Numeric Edit Control

Introduction

When programming a GUI, quite often you use edit boxes to enter numeric values. A standard edit box has the ES_NUMBER style that lets you restrict a 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, you should let the user enter not only digits but also a decimal separator and exponent symbol. Moreover, the floating-point format is more complex than a simple sequence of digits, so entered text has to be parsed to make sure 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 number values to their textual representation and back. The conversion is performed according to the certain format defined by a mask and an additional set of parameters (format properties). The mask is a text string that defines a format expression that matches certain syntaxes. You can specify your own mask or use the default one that is automatically generated according to system/locale settings. Format properties are used during formatting, scanning, and generation of the default masks.

The control operates in two modes: display and editing. Editing mode is turned on when the control gets keyboard input focus; otherwise, it stays in display mode. Each mode has its own format parameters, such as mask and format properties. Therefore, the user can see two different textual representations of the same numeric value. In display mode, the value is only converted in text, but in editing mode 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 number like $4,499.98, but at the same time, during editing, the user should not be forced to enter monetary symbol and separate groups with commas; he or she just has to enter essential data: 4499.98. This is the reason why two modes are provided.

Mask

A mask consists of the patterns, separated with semicolons. Every pattern corresponds to a certain value range or state. The number of the mask's patterns 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 certain pattern. For example, a mask for double values (vtDouble) consists of nine patterns; a 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. These are "number" and "zero" patterns (patterns related to positive number, negative number, non-zero number, positive zero, negative zero and zero). The rest are literal patterns. Literal patterns are used to represent special state of the value; for example when value is NULL or floating point value is negative or positive infinity.

In 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-format (exponential) patterns.

Segments are always placed in the order 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. There are three types of the tokens that 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. Characters 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

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

Placeholders are replaced with the digits or symbols they are bound 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. 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 "#". They can be used as placeholders only inside value segments.

Literals are just written as they are.

Format properties

By using format properties, you can specify additional parameters for the formatting and scanning operations and customize generation of the default mask. 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 they have no effect when a custom mask is used. Other properties are involved in formatting and scanning. See Table 3 for more information.

Table 3: Format properties

ID

Description

fpidWhiteSpace

Symbol used as a default substitution of white space token ("_").

fpidZero

Symbol used as a default substitution of zero token ("0").

fpidNegativeSign

String value for the negative sign.

fpidPositiveSign

String value for the positive sign. If numbers without any sign should be interpreted as positive, use an empty string for this parameter.

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 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 Figure 1).

Figure 1

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 through the FormatterParams property in the control's designer.

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

How to Use It

First of all, make sure that 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 built the control with Visual Studio, it should be registered automatically.

During design time, you can use the FormatterParams property (see Figure 2) to open the special property page (see Figure 3) that makes formatter configuration easy.

Figure 2

Figure 3

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

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 element index corresponds to property ID. If the element value is NULL, the corresponding property will be set to the system default value.
To assign default values to all properties, just pass the VARIANT of the 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 system settings.

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

Top White Papers and Webcasts

Live Event Date: March 19, 2015 @ 1:00 p.m. ET / 10:00 a.m. PT
The 2015 Enterprise Mobile Application Survey asked 250 mobility professionals what their biggest mobile challenges are, how many employees they are equipping with mobile apps, and their methods for driving value with mobility.
Join Dan Woods, Editor and CTO of CITO Research, and Alan Murray, SVP of Products at Apperian, as they break down the results of this survey and discuss how enterprises are using mobile application management and private …

On-demand Event
Event Date: February 12, 2015
The evolution of systems engineering with the SysML modeling language has resulted in improved requirements specification, better architectural definition, and better hand-off to downstream engineering. Agile methods have proven successful in the software domain, but how can these methods be applied to systems engineering? Check out this webcast and join Bruce Powel Douglass, author of Real-Time Agility, as he discusses how agile methods have had a tremendous …