NumericalBox - WPF TextBox 的子类,只接受格式正确的十进制数





0/5 (0投票)
接受 [[-+]整数部分][.小数部分][{eE}[-+]指数] 格式数字的 TextBox
引言
存在大量允许只输入数字的 TextBox 的子类实现。通常,畸形数字的拒绝是在额外的验证阶段提供的,并且在输入阶段允许诸如 1+234 或 .+1e23.4 之类的怪异输入。我们将设置输入约束,以便只能输入格式正确的数字。
正则表达式方法
我们希望在输入时只允许 [[-+]整数部分][.小数部分][{eE}[-+]指数] 格式的数字。
也就是说,诸如 123、+123、123.45、12e+45 等数字允许输入,而 123.、12.34.5、1+2345e 等数字则被禁止。同时,我们不会关心数字转换为机器格式(例如,按值)的能力。我们只检查数字的语法格式。
1950 年代伟大的 Stephen Kleene 发明的经典正则表达式为我们铺平了通往目标之路。我们将使用 C# 方言的正则表达式。
我们将实现一个NumericalBox - System.Windows.Controls.TextBox 类的简易子类。实现仅包含 3 个方法重写和一个辅助方法。让我们逐步考虑这些重写。
OnKey 重写
OnKey 是一个方法,它只允许允许的按键通过。通过设置不允许按键的 KeyEventArgs.Handled=true
,我们隐藏了事件路由中OnKey 事件对监听器的可见性。允许的按键将调用监听器。
protected override void OnKeyDown(KeyEventArgs e)
{
switch ( e.Key )
{
case Key.E:
case Key.OemPlus:
case Key.OemMinus:
case Key.OemPeriod:
case Key.Subtract:
case Key.Add:
case Key.Decimal:
case Key.D0:
case Key.D1:
case Key.D2:
case Key.D3:
case Key.D4:
case Key.D5:
case Key.D6:
case Key.D7:
case Key.D8:
case Key.D9:
case Key.NumPad0:
case Key.NumPad1:
case Key.NumPad2:
case Key.NumPad3:
case Key.NumPad4:
case Key.NumPad5:
case Key.NumPad6:
case Key.NumPad7:
case Key.NumPad8:
case Key.NumPad9:
case Key.Back:
break;
default:
e.Handled = true;
break;
}
}
OnKey 将阻止无意义的按键,但不会阻止畸形的数字,例如 +..1e.e.1
等。
数字验证的正则表达式
在文本更改时(即在输入时)检查数字的语法似乎是个好主意。现在,第一个正则表达式出现了。它是
[-+]?\d*(\.?)(\d+)([eE][-+]?\d+)?
含义:
[-+]? - 可选符号部分:匹配 -
、+
或无
\d* - 整数部分:匹配零个或多个十进制数字
(\.?) - 小数点部分:匹配零个或一个小数点的组
(\d+)- 小数部分:匹配一个或多个十进制数字的组
([eE][-+]?\d+)?- 指数部分:匹配零个或一个指数基数(e 或 E)后跟可选符号,再后跟一个或多个十进制数字的组。
该正则表达式以 Regex 类型的变量形式提供;参数值 RegexOptions.ECMAScript 仅将可能的数字表示形式限制为英语。
Regex _rgxChangedValid = new Regex( @"^[-+]?\d*(\.?)(\d+)([eE][-+]?\d+)?$" );
锚点 ^ 和 $ 表示匹配从字符串的开头开始,并且必须发生在字符串的末尾。
OnTextChanged 重写 - 第一次尝试 - 项目 NumericalBoxCtrlJobHalfDone
让我们在OnTextChanged中实现以上所有内容
protected override void OnTextChanged( TextChangedEventArgs e )
{
base.OnTextChanged( e );
if ( !IsInputTextValid( Text ) )
{
Text = _lastVerifiedText;
}
_lastVerifiedText = Text;
CaretIndex = Text.Length;
}
private bool IsInputTextValid( string text )
{
return !string.IsNullOrEmpty( text == null ? null : text.Trim( ) )
? ( _rgxChangedValid.IsMatch( text ) ? true : false )
: false;
}
私有变量 _lastVerifiedText
存储最后一个通过正则表达式检查的已键入字符串,并用于恢复上一个有效版本。最终项目NumericalTextBox将展示更方便的恢复方式。
现在让我们尝试项目NumericalTextBoxCtrlJobHalfDone - 启动演示NumericalTextBoxCtrlJobHalfDone.exe 并开始在文本框中输入数字 123.45。
尝试失败!我们无法输入小数点。
失败原因
问题在于我们的 _
rgxChangedValid
描述了一个完整的、格式正确的数字;例如,它必须在小数点后至少包含一个数字。但是,在顺序输入过程中,用户无法同时输入小数点和它后面的第一个数字 - 小数点总是先出现并被拒绝。
_rgxChangedValid
期望一个完整的数字,而此时我们只有一个未完成的工作。“不要给傻瓜看未完成的工作!”
OnTextChanged 重写 - 第二次尝试 - 项目 NumericalBoxCtrl
因此,我们应该在OnTextChanged重写中使用不同的正则表达式来批准不完整的数字。就是这个
Regex _rgxChangedValid = new Regex( @"^[-+]?\d*(\.?)(\d*)([eE][-+]?\d*)?$", RegexOptions.ECMAScript );
NumericalBoxCtrlJobHalfDone的_rgxChangeValid
和NumericalBoxCtrl的_rgxChangedValid
之间只有一个区别。第二个正则表达式在第二个和第三个数字位置有\d*
。量词*
表示“匹配前一个元素零次或多次”。在我们的例子中,这意味着“零个或多个数字”,进而意味着字符串“123.”和“123e”是被允许的。现在这是新版本OnTextChanged的代码
protected override void OnTextChanged( TextChangedEventArgs e )
{
base.OnTextChanged( e );
string longestValidText;
if ( !IsTextValid( _rgxChangedValid, _rgxSaveChanged, Text, out longestValidText ) )
{
this.Text = longestValidText;
}
CaretIndex = Text.Length;
}
私有方法IsTextValid (稍后解释)使用 _rgxChangedValid
检查NumericalBox的内容,并尝试从无效内容中提取最长的有效子字符串。
OnLostFocus 重写
现在,完整的数字也应该被检查 - 当它完成时,当然。LostFocus
事件是完整性的标准:当NumericalBox失去焦点时,数字应被视为已完成。微软在为 TextBox 的绑定选择 Mode=LostFocus
时并非偶然!
我们第一个“数字验证的正则表达式”应该在这里使用。我们现在称之为 _rgxLostFocusValid
。
Regex _rgxLostFocusValid = new Regex( @"^[-+]?\d*(\.?)(\d+)([eE][-+]?\d+)?$", RegexOptions.ECMAScript );
这是OnLostFocus
protected override void OnLostFocus( RoutedEventArgs e )
{
base.OnLostFocus( e );
string longestValidText;
if(!IsTextValid( _rgxLostFocusValid, _rgxSaveLost, Text, out longestValidText ))
{
this.Text = longestValidText;
}
}
它几乎与OnTextChanged相似,除了它使用 _rgxLostFocusValid
正则表达式,并且不将光标放置在字符串的末尾,因为光标被焦点丢失取消了。
顺便说一句,关于焦点丢失。它是由框外的鼠标单击执行的,我很抱歉它是在代码隐藏中实现的。对于如此短的项目,LostFocus
事件的 MVVM 实现看起来有点笨拙。
IsTextValid 方法和有效子字符串的提取
IsTextValid 方法提取最长的有效子字符串。在不完整数字的情况下,它使用 rgxSave=_rgxSaveChanged
Regex _rgxSaveChanged = new Regex( @"[-+]?\d*(\.?)(\d*)([eE][-+]?\d*)?", RegexOptions.ECMAScript );
正则表达式,在完整数字的情况下,它使用 rgxSave=_rgxSaveLost
Regex _rgxSaveLost = new Regex( @"[-+]?\d*(\.?)(\d+)([eE][-+]?\d+)?", RegexOptions.ECMAScript );
正则表达式
longestValidSubstr = rgxSave.Match( text ).Value;
_rgxSaveChanged
和 _rgxLostChanged
都没有 ^ 和 $ 锚点,用于从文本中间提取有效子字符串。
IsTextValid 方法还通过 Regex.Match
方法验证文本,其中 rgxValid
在“已更改”验证和“焦点丢失”验证中分别等于 _rgxChangedValid
和 _rgxLostValid
。
return !string.IsNullOrEmpty( text == null ? null : text.Trim( ) ) ?
rgxValid.IsMatch( text ) :
false;
使用代码
尝试演示NumericalBoxCtrl.exe。
现在NumericalBox允许格式正确的数字
并在 LostFocus 事件后尝试提取畸形数字的有效部分
NumericalBox.cs 可以在任何需要的地方使用,代替 TextBox。
历史
2015-09-09 修复了指向图片的错误引用
2015-09-13 修正了语法和拼写错误;修复了代码中的一些错误