65.9K
CodeProject 正在变化。 阅读更多。
Home

SP 数字编辑控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (10投票s)

2005年2月15日

11分钟阅读

viewsIcon

77049

downloadIcon

3655

带掩码的数字编辑 ActiveX 控件。

引言

在编程 GUI 时,有时需要提供一个编辑框来输入数字值。通常使用标准的编辑框;它有一个 ES_NUMBER 样式,可以限制用户输入,只允许输入数字。这是一个有用的选项,但它并不适用于所有实际情况。例如,当需要输入指数形式的浮点数时,您应该允许用户输入不仅是数字,还要包括小数点和指数符号。此外,浮点数的格式比简单的数字序列更复杂,因此您必须解析输入的文本以确保它可以转换为数字,并可能让用户知道检测到的错误。因此,我基于标准编辑框创建了一个特殊的 ActiveX 控件,它扩展了其功能并提供了额外的数字处理选项。

特点

首先,我想指出该控件直接处理数字数据类型,而不是文本字符串。在内部,它执行数字值到其文本表示形式的转换以及反向转换。转换根据由 *掩码* 和一组附加参数(*格式属性*)定义的特定格式执行。掩码是一个文本字符串,定义了一个类似正则表达式的表达式,用于匹配特定的语法。您可以指定自己的掩码,或者使用默认掩码,该掩码是根据系统/区域设置自动生成的。格式属性在格式化、扫描和生成默认掩码时使用。

图 1

控件有两种模式:显示模式和编辑模式。当控件获得键盘输入焦点时,将切换到编辑模式;否则,它将保持在显示模式。每种模式都有自己的一组格式配置(掩码和格式属性)。因此,用户可以看到同一数字值的两种不同的文本表示形式(参见图 1)。在显示模式下,值仅转换为文本,但在编辑模式下,执行双向转换。通常,对于编辑模式,您应该使用简化的格式,而在显示模式下,您可以启用全套功能。考虑您要处理货币值。用户方便地看到像 $4,499.98 这样的数字,但同时,在编辑时,用户不应被迫输入货币符号和千位分隔符;他/她只需输入基本数据:4499.98。这就是提供两种模式的原因。

掩码

掩码由模式组成,模式之间用分号分隔。每种模式对应于特定的值范围或状态。掩码中的模式数量及其目的取决于控件处理的数据类型。有关更多信息,请参见表 1。

表 1:与数据类型对应的掩码模式。

数据类型 模式1 模式2 模式3 模式4 模式5 模式6 模式7 模式8 模式9
vtInt8 (VT_I1) 正数 负数 null - - - - -
vtInt16 (VT_I2) 正数 负数 null - - - - -
vtInt32 (VT_I4) 正数 负数 null - - - - -
vtInt64 (VT_I8) 正数 负数 null - - - - -
vtUInt8 (VT_UI1) 非零数 null - - - - - -
vtUInt16 (VT_UI2) 非零数 null - - - - - -
vtUInt32 (VT_UI4) 非零数 null - - - - - -
vtUInt64 (VT_UI8) 非零数 null - - - - - -
vtFloat (VT_R4) 正数 负数 正零 负零 正无穷大 负无穷大 安静 NaN 信号 NaN null
vtDouble (VT_R8) 正数 负数 正零 负零 正无穷大 负无穷大 安静 NaN 信号 NaN null

任何数字值都根据特定的模式进行格式化。例如,双精度值(vtDouble)的掩码包含 9 种模式;负值将使用模式 2 进行格式化,正无穷大将使用模式 5 进行格式化,依此类推。

有两种模式:*值模式*和*字面量模式*。值模式用于格式化确定的数字值,例如正数、负数和零。其余为字面量模式。字面量模式用于表示值的特殊状态;例如,当值为 NULL 或浮点值为负无穷大或正无穷大时,将使用相应的字面量模式进行格式化。

反过来,一个模式由*段*组成。所有字面量模式只有一个段,但值模式至少有一个段对应于数字的*整数部分*,并且可以有两个附加段:*前缀*和*后缀*。浮点模式此外还有*小数部分*和*指数部分*的段。指数部分仅在 E 表示法(指数)模式中存在。整数、小数和指数段包含在模式的*数字部分*中。

  • 字面量模式架构

    { 字面量 }

  • 整数值模式架构

    { 前缀 } | { 整数 } | { 后缀 }

  • 浮点值 F 格式模式架构

    { 前缀 } | { 整数 } { . 小数 } | { 后缀 }

  • 浮点值 E 格式模式架构

    { 前缀 } | { 整数 } { . 小数 } { e 指数 } | { 后缀 }

段始终按上述顺序排列。前缀和后缀与*值部分*用“|”符号分隔。它们是可选的,但如果使用,则两者都必须存在;但是,您可以指定一个空的前缀或后缀。如果存在“|”前缀分隔符,则整数段紧跟其后,否则从模式的开头开始。通常,小数从“.”符号开始,指数从“e”符号开始。但是,当段完全包含在*可选块*(下文讨论)中时,它从打开该块的令牌开始。整数、小数和指数段对于相应的模式是必需的。

在格式化和扫描过程中,数字的处理顺序是确定的,具体取决于正在处理的段。整数和指数部分从右到左处理,而小数部分从左到右处理。

段由令牌组成。每个令牌为格式化过程指定一个明确的指令,或者用作掩码不同部分的分隔符。段内只能使用三种令牌:控制令牌、占位符和字面量。

表 2:令牌

分隔符
; 模式结束
| 前缀/后缀分隔符
控制令牌
( 打开可重复块
) 关闭可重复块
[ 打开可选块
] 关闭可选块
占位符
0 带有默认零值的数字占位符
_ 带有默认空格值的数字占位符
# 数字占位符
- 负号
+ 正号
$ 货币符号
% 百分号
千分号
, 千位(分组)分隔符
. 小数点
e 指数
保留
{, }, <, > 保留用于未来扩展。
字面量
\ 转义符号。
任意字符

任何字符都可以用作字面量。对应于保留符号的字符字面量应以反斜杠“\”开头。

此外,还有三个特殊转义符号

  • \r - 生成回车符 (CR)
  • \n - 生成换行符 (LF)
  • \t - 生成制表符

在这里,您可以看到一些掩码的示例

  1. 浮点数 F 格式(非指数表示法)
    (###,)##0.00(#);-(###,)##0.00(#);0.00;0.00;\+INF;\-INF;QNaN;SNaN;NULL
  2. 浮点数 E 格式(指数表示法)
    (###,)##0.00(#)e\+(#)0;-(###,)##0.00(#)e\-(#)0;
                   0.00e\+0;0.00e\-0;\+INF;\-INF;QNaN;SNaN;NULL
  3. 浮点数简化 E 格式(指数表示法)
    (#)0[.0(#)][e\+(#)0];-(#)0[.0(#)][e\-(#)0];0[.0][e\+0];
                             0[.0][e\-0];\+INF;\-INF;QNaN;SNaN
  4. 美元格式的浮点货币
    $(###,)###.00(#);($|(###,)###.00(#)|);$.00;($|.00|);\+INF;\-INF;QNaN;SNaN

通过使用*控制令牌*,您可以定义*可重复块*和*可选块*。格式化过程继续使用*可重复块*,直到所有数字都被处理。*可选块*仅在有未处理的数字时使用一次。由控制令牌定义的块不能部分重叠,但一个块可以嵌套在另一个块内。任何块都必须完全位于单个段内。每个打开的块都必须用相应的令牌关闭。*可重复块*仅适用于值部分段,如整数、小数和指数部分。

占位符将被替换为它们相关的数字或符号。例如,“+”将被替换为正号,“$”将被替换为货币符号,依此类推。“0”、“_”和“#”是数字占位符;它们不仅用于输出,还用于编辑模式下的扫描输入。它们之间存在差异;在格式化过程中,当没有要处理的数字时,“0”和“_”分别替换为零和空格符号,但“#”将不会生成任何内容。数字占位符仅在值部分段中使用。

字面量按原样编写。

格式属性

通过使用格式属性,您可以获得格式化和扫描操作配置以及默认掩码生成自定义的附加选项。事实上,fpidNegativeInfinityfpidPositiveInfinityfpidQuietNaNfpidSignalingNaNfpidNullfpidLeadingZerofpidDecimalDigitsNumberfpidExponentDigitsNumberfpidGroupingfpidNegativePatternFpidPositivePattern 仅用于生成默认掩码,因此在使用自定义掩码时不会产生任何影响。其他属性也参与格式化和扫描。有关更多信息,请参见表 3。

表 3:格式属性

ID 描述
fpidWhiteSpace 用作空格令牌(“_”)默认替换的符号。
fpidZero 用作零令牌(“0”)默认替换的符号。
fpidNegativeSign 负号的字符串值。
fpidPositiveSign 正号的字符串值。如果数字不带任何符号,则应将其解释为正数,请为此属性值使用空字符串。
fpidNegativeInfinity 负无穷大的表示。
fpidPositiveInfinity 正无穷大的表示。
fpidQuietNaN “安静非数字”值的表示。
fpidSignalingNaN “信号非数字”值的表示。
fpidNull Null 值的表示。
fpidCurrency 用作货币符号的字符串。
fpidPercent 用作百分号的字符串。
fpidPermille 用作千分号的字符串。
fpidExponent 用作指数符号的字符串。
fpidDecimalSeparator 用作小数点分隔符的字符(串)。
fpidGroupSeparator 用作小数点左侧数字分组的分隔符的字符(串)。
fpidLeadingZero 默认生成的掩码中小数域前导零的说明符。如果设置为 True,则添加前导零;否则,小数点前不会有前导零。
fpidDecimalDigitsNumber 要打印的小数位数最小值。
fpidExponentDigitsNumber 要打印的指数位数最小值。
fpidGrouping 小数点左侧每组数字的大小。每组需要一个显式大小,大小之间用分号分隔。如果最后一个值为零,则重复前一个值。
fpidNegativePattern 负数模式,即负数的格式。
fpidPositivePattern 正数模式,即正数的格式。

控件架构

该控件由多个类实现(参见图 2)。

图 2

控件的主类名为 NumericEditBox。它实现了 INumericEditBox 接口,该接口允许您更改与控件的可视化和行为相关的各种属性;通过其 Value 属性,您可以访问控件处理的数字值。

NumericEditBox 包含另一个名为 Formatter 的对象。运行时可以通过 Formatter 属性访问它,或者在控件的设计器中通过 FormatterParams 访问。

Formatter 维护显示和编辑模式的值类型、格式类型、掩码和格式属性。它实际管理格式化过程,并提供进行配置的必要设施。

反过来,Formatter 包含两个集合对象(FormatProperties),它们代表显示和编辑模式的格式属性。

如何使用

首先,请确保 SpNumericEdit.dll 已在您的 PC 上注册。如果未注册,请使用以下命令注册 COM 控件。

regsvr32 SpNumericEdit.dll

如果您使用 Visual Studio 构建了控件,它应该会自动注册。

在设计时,您可以使用 FormatterParams 属性(参见图 3)打开一个特殊的属性页(参见图 4),从而更轻松地配置格式化程序。

图 3

图 4

此外,还可以在运行时更改格式参数。您可以使用 IFormatter::Configure 方法来完成此操作。

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

该方法接受六个参数

enValueType 值的类型(vtInt8vtInt16 等)。
enFormatType 格式类型(ftNumericftCurrency 等)。
vDisplayFmtProps, vEditingFmtProps 分别是显示模式和编辑模式的格式属性。此参数是一个 VARIANT,可以包含 SAFEARRAYIFormatProperties。如果传递了 SAFEARRAY,则其每个元素对应属性值,而元素索引对应属性 ID。如果元素值为 NULL,则相应属性将设置为系统默认值。要为所有属性分配默认值,只需传递 VT_NULLVT_EMPTY 类型的 VARIANT
bsDisplatMask, bsEditingMask 分别是显示模式和编辑模式的掩码表达式。如果传递了 NULL 值,则默认掩码将根据系统/区域设置生成。

以下 C++/MFC 代码片段演示了如何在运行时配置格式化程序

// 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);

结论

该组件是免费的,请随意试用。希望您觉得它有用。如果您发现任何错误或其他问题,请告知我。

尽情享用!

历史

  • 2005/02/15。1.0 版本 Beta 发布。
  • 2005/03/24。1.0 版本发布。
    • 控件使用了新的格式化库。
    • 在输入期间提供了值的范围检查。
  • 2005/08/16。1.2 版本 Alpha 发布。
    • 格式化库和 ActiveX 已重新设计。
    • 已修复一些错误。
  • 2005/10/31。1.2 版本发布。
    • Alpha 版本中发现的几个错误已得到修复。
    • 源代码已重构。
© . All rights reserved.