我正在使用 PreviewTextInput 来验证十进制数。这是我的代码:
private void AmountTextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
TextBox t = sender as TextBox;
string newText = t.Text.Insert(t.SelectionStart,e.Text);
Regex regex = new Regex(@"^[0-9]{1,12}(?:\.[0-9]{0,2})?$");
e.Handled = !regex.IsMatch(newText);
}
这正确地只接受数字和一位小数点,但只有用户在数字之间插入小数点时才接受小数点。如果在字符串末尾输入小数点,则即使正则表达式返回匹配项(即 e.Handled = false),它也不会添加到文本框中。当然,在输入值时,用户会输入小数点左侧,然后输入小数点,然后输入小数点右侧。为什么小数点会被去掉?它是 PreviewTextInput 处理的产物吗?我只是错过了一些明显的东西吗?
请注意,您应该覆盖
TextInput
或 PreviewTextInput
,而不是注册事件处理程序。一般来说,您应该始终覆盖事件处理程序,而不是注册新的处理程序。
WPF 绑定引擎执行隐式类型转换。当您将
"1."
转换为 double
时,解析器会删除小数点分隔符。如果将 TextBox.Text
属性绑定到 string
属性,则由于避免了数字转换,该问题就会消失。
正则表达式对于这个简单的任务来说太昂贵了(检查文本是否是数字)。您应该使用例如
double.TryParse
。
您不应该对小数点分隔符进行硬编码。相反,使用
CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator
值可以让您的控件适应不同的文化(考虑到世界上 50% 的国家使用“,”作为小数点分隔符)。
另请注意,将文本粘贴到
TextBox
中不会引发 TextInput
事件,因此会绕过您的输入验证或填充。为了解决这种情况,您应该通过调用 DataObject.AddPastingHandler
方法来注册数据粘贴事件处理程序: DataObject.AddPastingHandler(OnInputPasted)
解决转换问题的一种解决方案是检测 trailing 小数点分隔符,然后附加
0
以使其成为有效的十进制数。然后,您可以使用下一个用户输入自动覆盖生成的 0
:
private bool isContentAutoCompleted;
protected override void OnTextInput(TextCompositionEventArgs e)
{
string numberDecimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
bool isInputDecimalSeparator = e.Text.Equals(numberDecimalSeparator, StringComparison.OrdinalIgnoreCase);
// Input is a decimal separator: append a "0" (if this is the only separator)
if (isInputDecimalSeparator)
{
e.Handled = true;
int existinDecimalSeparatorIndex = this.Text.LastIndexOf(numberDecimalSeparator, StringComparison.OrdinalIgnoreCase);
bool isDecimalSeparatorPresent = existinDecimalSeparatorIndex != -1;
if (isDecimalSeparatorPresent)
{
// The next character is already a decimal separator. Just advance the caret.
// Otherwise ignore the input.
if (existinDecimalSeparatorIndex == this.CaretIndex)
{
++this.CaretIndex;
}
return;
}
// Only auto complete input if decimal separator is trailing the cotent
this.isContentAutoCompleted = this.CaretIndex == this.Text.Length;
string newContent = this.isContentAutoCompleted
? this.Text + e.Text + "0"
: this.Text.Insert(this.CaretIndex, e.Text);
int currentCaretIndex = this.CaretIndex;
SetCurrentValue(TextBox.TextProperty, newContent);
this.CaretIndex = currentCaretIndex + 1;
return;
}
bool isInputNumeric = double.TryParse(e.Text, out _);
if (!isInputNumeric)
{
e.Handled = true;
return;
}
// Overwrite the auto appended "0" with the new input
if (this.isContentAutoCompleted)
{
this.isContentAutoCompleted = false;
e.Handled = true;
string newContent = this.Text[..^1] + e.Text;
// Setting the Text property will reset the caret position.
// Therefore we need to restore it.
int currentCaretIndex = this.CaretIndex;
SetCurrentValue(TextBox.TextProperty, newContent);
this.CaretIndex = currentCaretIndex + 1;
return;
}
base.OnTextInput(e);
}