仅数字的TextBox(使用C#中的正则表达式)
本文将介绍如何使用正则表达式(Regex)将文本框的内容限制为数字。
引言
本文源于我在遇到一篇由 Jan Martin 撰写的早期版本(现已删除)的文章时产生的想法。
https://codeproject.org.cn/Articles/887085/Simple-and-Reusable-Numbers-only-Textbox-in-Csha
我认为这是一篇很棒的文章,涵盖了字符串的字符处理。所有程序员都需要这项技能。
为此,我给他的文章打了五星,并“提高了他的正则表达式水平”。我觉得有些东西可能在翻译中丢失了。我怀疑他认为我在批评他。抱歉,Jan,那不是我的本意。我只是在为讨论增加一些想法。
我的想法是,可以使用正则表达式(Regex)来通过减少原始字符处理来实现类似的结果,让 .NET 的 System.Text.RegularExpressions 来处理所有繁重的工作。还可以推导出 Regex 模式来对 TextBox 控件或任何其他文本输入控件应用其他限制。
多年来,我处理过大量格式化的文本数据文件。Regex 一直是完成这项工作的关键。
总之……我提到过我会写一篇文章,所以(终于)它在这里了。
背景
与 Jan 的文章一样,目的是确保在 TextBox 控件中输入的字符始终符合捕获数据所需的格式(例如,整数、仅正数、货币等)。我习惯称之为“条件化”数据。
一个不同之处在于,我不会尝试容忍粘贴的字符串。粘贴的字符串有点麻烦,取决于你想提取什么。并非不可能,只是有点麻烦。这完全是关于在文本框中输入。因此,值会在您输入时不断地进行条件化。
如果 Jan 的解决方案更适合您的需求,那么这才是“适合您的选择”,我不会否定您的选择。我只是提供另一种方法。
正则表达式 (Regex)
这篇 维基百科文章(我知道,我知道……维基百科,哼。大多数领域都已随着时间推移而改进,而且这种愿望是好的。即使有些贡献可能是可疑的)对正则表达式的起源进行了合理的概述,所以我不会在这里尝试涵盖这个话题。
在最强大的时候,Regex 可能会让人望而生畏。语法可能很奇怪,难以记忆。我坦诚地承认,有时会因为弄不清楚为什么某些东西不起作用而感到困惑。Regex 绝对不适合绝对的初学者。像 这个 这样的备忘单是必须的,但您总是需要弄清楚适合您所处理的 Regex 解析器的模式。 .NET 的解析器与某些其他解析器不同。
在过去的几年里,我从 Google 和 Bing 搜索中获得了许多令人惊叹的 Regex 模式,它们为我处理大型、格式化的文本文件节省了无尽的麻烦,包括嵌套的大括号( {data {more data}} )或提取被引号包围的字符串( “some string” )等。对于本文,我将使模式相对简单(但有用)并逐一解释。
有三种主要的方法可以应用或操作正则表达式。这在很大程度上取决于您需要对结果做什么。
最透明(如果代码更长)的方法是
string p = "(?<Number>[0-9])"; // Set the pattern
Regex r = new Regex(p); // Initialise a Regex object with the pattern 'p'
Match m = r.Match("123hfj76l098"); // Match the Regex pattern 'p' in the string '123hfj76l098'
string firstInteger = m.Groups["Number"].Value; // Get the found value
另一种方法
string p = "(?<Number>[0-9])"; // Set the pattern Match m = Regex.Match("123hfj76l098", p); // Initialise a Match Object passing in the string to be parsed and the pattern. string firstInteger = m.Groups["Number"].Value; // Get the found value
还有一种方法
string firstInteger = Regex.Match("123hfj76l098", "(?<Number>[0-9])").Groups["Number"].Value;
第三种选择对于从字符串中获取单个匹配值是最有效的。但是,我需要一直处理字符串,直到找到所有匹配的字符。
因此,我可以选择第一种或第二种方法,但我将选择第一种,因为它更透明,并且我可以使用 Match 对象(m)继续进行处理。
您会注意到我没有使用“var”。这并不是说我反对使用它,我只是选择在这篇文章中非常明确。
让我们开始吧。
使用代码
TextFilter 类
下面是一个“文本过滤器”类的开头,带有一个入门方法“GetInteger”。
using System.Text.RegularExpressions;
namespace Filters
{
class TextFilters
{
public string intPattern = "(?<Number>[0-9])";
public string GetInteger(string SourceString, bool PosOnly = false)
{
string newNumber = string.Empty;
if (!PosOnly)
if (SourceString.StartsWith("-"))
newNumber += "-";
Regex r = new Regex(intPattern);
Match m = r.Match(SourceString);
while (m.Success)
{
newNumber += m.Groups["Number"].Value;
m = m.NextMatch();
}
return newNumber;
}
}
}
这是它的详细(但大多数情况下简洁)的英文解释:
- 调用“System.Text.RegularExpressions”命名空间引用。
- 定义一个新的命名空间“Filters”。
- 定义一个新的类“TextFilters”。
- 为整数 Regex 模式建立一个字符串变量。
- 我在这里这样做,因为它是一个方便的地方来收集 Regex 模式字符串。
- 我将在稍后解释模式本身。
- 创建一个返回类型为“string”的公共方法“GetInteger”。
- 我们将从 TextBox 传递一个“SourceString”参数。
- 我们将传递一个“PosOnly”参数来确定我们是否接受负数。
- 默认设置为“false”,因此我们不必始终提供该参数。
- 初始化返回字符串:newNumber。
- 如果不需要“仅正数”
- 检查源字符串是否以负号开头。我们实际上不接受字符串中的其他任何“ - ”字符。这是有道理的。在 TextBox 中进行数学运算是另一个话题。
- 如果确实如此,则将“-”添加到“newNumber”。
- 检查源字符串是否以负号开头。我们实际上不接受字符串中的其他任何“ - ”字符。这是有道理的。在 TextBox 中进行数学运算是另一个话题。
- 初始化一个新的 Regex 对象(r),传入“intPattern”。
- 初始化一个 Match 对象(m),使用先前创建的 Regex 对象(r),传入“SourceString”。
- 这将获取源字符串中模式的第一个匹配项。
- 当在源字符串中成功找到模式的匹配项时
- 将名为“Number”的“捕获组”添加到“newNumber”。
- 获取下一个匹配项。
- 返回“newNumber”。
将该解释与代码进行比较,证明 C# 确实非常优雅。
那么……Regex 模式。
字符串“(?<Number>[0-9])”可以解读为:
- ( - 打开捕获组
- ?<Number> - 将捕获组命名为“Number”。
- [0-9] - 定义一个范围,查找零到九之间的任何“单个”字符。
- ) - 关闭捕获组。
替代模式
其他数字类型呢?例如十进制数。
Decimal Pattern
在整数模式下方添加另一个模式字符串。
public static string
intPattern = "(?<Number>[0-9])",
decPattern = @"(?<Number>^[0-9]*\.?[0-9]*)",
此模式的含义是:
- @ - 定义一个字面量字符串。对于转义的点“\。”很重要。点在 Regex 中具有特殊含义。
- ( - 打开捕获组。
- ?<Number> - 将捕获组命名为“Number”。
- ^ - “以下模式必须出现在源字符串的开头”。允许十进制数模式出现在其他任何地方意义不大。
- [0-9]* - 定义一个范围,查找任何数字(0-9)出现任意次数“0…n”(*)。
- \.? - 查找小数点(\.),允许它可选(?)。
- [0-9]* - 查找(可选)小数点后的任何数字。当遇到不匹配的字符时会停止。
- ) - 关闭捕获组。
因此,这应该至少允许以下内容:
- 1234
- 1234.5678
- .1
我们可以捕获一个人只输入一个点(.)的情况,但这真的不是 Regex 模式的任务。它只在值被处理时才无效,而不是在输入时。
请注意,如果您移动光标并输入无效字符,您将丢失该无效字符之后的所有字符。这是模式的工作方式。请参阅下面的“SetControlText”方法来解决此问题。
让我们稍微修改一下“GetInteger”方法。
- 将其重命名为“GetNumber”。
- 添加另一个字符串参数(RegexPattern),它将接受 Regex 模式字符串。
- 添加一行删除字符串中的“-”字符。
- 这对于十进制模式比整数模式更重要,因为十进制模式强制从字符串开头进行搜索。
public string GetNumber(string RegexPattern, string SourceString, bool PosOnly = false) { string newNumber = string.Empty; if (!PosOnly) if (SourceString.StartsWith("-")) newNumber += "-"; SourceString = SourceString.Replace("-", string.Empty); Regex r = new Regex(RegexPattern); Match m = r.Match(SourceString); while (m.Success) { newNumber += m.Groups["Number"].Value; m = m.NextMatch(); } return newNumber; }
此代码将容忍文本开头的“-”字符,并在您键入文本框时删除任何后续不符合要求的字符。
Currency Pattern
那么,您会为货币构建什么样的模式呢?
假设我们有以下限制:
- 我们希望删除字符串中的所有“$”符号。它们对计算没有用,文本框标签应清楚说明没有必要。
- 我们将允许小数点前有任意数量的数字。
- 我们只希望小数点后最多有两个数字。我们确实希望容忍整数输入(不像某些 ATM。为什么是这样?您又不能从它们那里取出硬币)。
模式将是:
@"^\$?(?<Number>[0-9]*\.?[0-9]{0,2})"
- @ - 定义一个字面量字符串。再次,因为我们有一些不寻常的转义序列,对于 Regex 是必需的。
- ^ - 以下内容必须出现在字符串的开头。
- \$? - 允许美元符号(\$),允许它可选(?)。“$”符号在 Regex 中有意义,如果您实际上要查找“$”字符,则必须进行转义。
- ( - 打开捕获组。
- ?<Number > - 将捕获组命名为“Number”。
- [0-9* - 定义一个范围,查找任何数字(0-9)出现任意次数“0…n”(*)。
- \.? - 查找小数点(\.),允许它可选(?)。
- [0-9]{0,2} - 查找(可选)小数点后的任何数字,数量在 0 到 2 之间。
- ) - 关闭捕获组。
与十进制模式一样,如果您移动光标并输入无效字符,您将丢失该无效字符之后的所有字符。同样,“SetControlText”方法可以解决这个问题。
现在我们有一个类,可以通过一个方法处理至少三种不同的数字格式。这就是我坚持将我的捕获组命名为“Number”而不是“Integer”、“Double”和“Currency”的原因。
using System.Text.RegularExpressions;
namespace Filters
{
public class TextFilters
{
public string
intPattern = "(?<Number>[0-9])",
decPattern = @"(?<Number>^[0-9]*\.?[0-9]*)",
currPattern = @"^\$?(?<Number>[0-9]*\.?[0-9]{0,2})";
public string GetNumber(string RegexPattern, string SourceString, bool PosOnly = false)
{
string newNumber = string.Empty;
if (!PosOnly)
if (SourceString.StartsWith("-"))
newNumber += "-";
SourceString = SourceString.Replace("-", string.Empty);
Regex r = new Regex(RegexPattern);
Match m = r.Match(SourceString);
while (m.Success)
{
newNumber += m.Groups["Number"].Value;
m = m.NextMatch();
}
return newNumber;
}
}
}
一旦获得数字,您就可以对其运行其他检查,以查看它是否符合您想要放置的任何其他限制。
- 它是否适合有符号 32 位或 64 位整数(Int32、Int64)的范围?
- 它是否适合无符号 32 位或 64 位整数(Uint32、UInt64)的范围?
- 它是否适合“float”或“double”的范围?
- 它是否适合财务交易限制?
TextBox
让我们看看 TextChanged 事件的代码。
最基本地,确保整数的代码是:
private void textBox1_TextChanged(object sender, EventArgs e)
{
TextFilters tF = new TextFilters();
textBox1.Text = tF.GetNumber(tF.intPattern, textBox1.Text);
textBox1.SelectionStart = textBox1.Text.Length;
}
英文
- 初始化“TextFilters”类的实例(tF)。
- 将 TextBox 的“Text”属性设置为通过“GetNumber”方法检索到的值。
- 确保光标位于 TextBox 内容的末尾。
使其“静态”
如果我们的“TextFilters”类是静态的,那会更好,这样我们就可以在不一直创建实例的情况下调用它。它更像是一个“功能”类(即,“做这个”),而不是一个“事物”类(即,“创建这个”)。我们不应该需要多个“对象”实例。
该类现在看起来像这样(注意高亮的“static”修饰符以及类中添加的“public”。)
using System.Text.RegularExpressions;
namespace Filters
{
public static class TextFilters
{
public static string
intPattern = "(?<Number>[0-9])",
decPattern = @"(?<Number>^[0-9]*\.?[0-9]*)",
currPattern = @"^\$?(?<Number>[0-9]*\.?[0-9]{0,2})";
public static string GetNumber(string RegexPattern, string SourceString, bool PosOnly = false)
{
string newNumber = string.Empty;
if (!PosOnly)
if (SourceString.StartsWith("-"))
newNumber += "-";
SourceString = SourceString.Replace("-", string.Empty);
Regex r = new Regex(RegexPattern);
Match m = r.Match(SourceString);
while (m.Success)
{
newNumber += m.Groups["Number"].Value;
m = m.NextMatch();
}
return newNumber;
}
}
}
“TextChanged”方法可以简化:
注意:
如果您像我一样是一个懒惰的打字员,可以将“using Filters;”语句更改为“using TF = Filters.TextFilters;”,用“TF”代替完整的“TextFilters”。
private void textBox1_TextChanged(object sender, EventArgs e)
{
textBox1.Text = TF.GetNumber(TF.intPattern, textBox1.Text);
textBox1.SelectionStart = textBox1.Text.Length;
}
使其更智能
这一切都很好。但我真的想创建一个方法,我可以将其应用于任何 TextBox 的“TextChanged”事件方法,并且能够处理用户将光标放在文本中间进行更改的情况。
我将把这视为我文本过滤过程的一部分,并将该方法添加到“TextFilters”类中。
这是新方法:
public static void SetControlText(System.Windows.Forms.TextBox TextBoxControl, string FilteredString)
{
int cursorPos = TextBoxControl.SelectionStart; // Get the cursor position
string textBoxContent = TextBoxControl.Text; // Extract the current content. Important for the following comparisons
if (FilteredString.Length < textBoxContent.Length) // This might indicate that an invalid character was entered part way through the string
{
cursorPos--; // Step back one character
textBoxContent = textBoxContent.Remove(cursorPos, 1); // Remove the offending character
TextBoxControl.Text = textBoxContent; // Set the Text property. This triggers a ‘recurrent’ ‘TextChanged’ event.
}
else
TextBoxControl.Text = FilteredString; // For some reason this ‘appears’ to not trigger a recurrent ‘TextChanged’ event. Go figure.
if (cursorPos >= TextBoxControl.Text.Length) // If the cursor was at the end of the text
TextBoxControl.SelectionStart = TextBoxControl.Text.Length;
Else // If the cursor was ‘inside’ the text
TextBoxControl.SelectionStart = cursorPos;
}
我使用了完全限定的“System.Windows.Forms.TextBox”,因为这是我在该类中唯一会引用“System.Windows.Forms”的地方。五五开。
“TextChanged”方法变为:
void textBox1_TextChanged(object sender, EventArgs e)
{
string filteredText = TF.GetNumber(TF.intPattern, intTextBox.Text);
TF.SetControlText((TextBox)sender, filteredText);
}
Filter Restrictions
还有哪些其他限制是我们可能想要应用的?
这是场景:
- 我们有一个名为“textBoxTransaction”的控件。
- 我们想确保交易金额在 $0.00 到 $2000.00 之间。
- 此限制可能源于:
- 另一个控件中的财务委派值,
- 基于应用程序用户的查找,
- 账户的每日交易限额,
- 可能性是无限的。
我们的“TextChanged”事件方法可以是:
private void textBox1_TextChanged(object sender, EventArgs e)
{
string filteredText = TBF.GetNumber(TBF.currPattern, textBox1.Text, true);
double valueTest = Convert.ToDouble(filteredText);
if (valueTest >= 0.00 && valueTest <= 2000.00)
TBF.SetControlText((TextBox)sender, filteredText);
Else
MessageBox.Show("$2000.00 transaction limit exceeded. Please re-enter.");
}
这将阻止输入更大的值,然后再输入小数点。'true' 参数用于阻止负数输入。
我们在执行测试时确实有一个“Text”属性可能为空(“”,“”)的问题。
所以……
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (textBox1.Text != string.Empty)
{
string filteredText = TF.GetNumber(TF.currPattern, textBox1.Text);
if (Convert.ToDouble(filteredText) <= 2000.00)
TF.SetControlText((TextBox)sender, filteredText);
else
MessageBox.Show("$2000.00 transaction limit exceeded. Please re-enter.");
}
}
整合
- 我们现在有了一个不错的 TextFilter 类,我们可以根据需要进行扩展。
namespace Filters
{
public static class TextFilters
{
public static string
intPattern = "(?<Number>[0-9])",
decPattern = @"(?<Number>^[0-9]*\.?[0-9]*)",
currPattern = @"^\$?(?<Number>[0-9]*\.?[0-9]{0,2})";
public static string GetNumber(string RegexPattern, string SourceString, bool PosOnly = false)
{
string newNumber = string.Empty;
if (!PosOnly)
if (SourceString.StartsWith("-"))
newNumber += "-";
SourceString = SourceString.Replace("-", string.Empty);
Regex r = new Regex(RegexPattern);
Match m = r.Match(SourceString);
while (m.Success)
{
newNumber += m.Groups["Number"].Value;
m = m.NextMatch();
}
return newNumber;
}
public static void SetControlText(System.Windows.Forms.TextBox TextBoxControl, string FilteredString)
{
int cursorPos = TextBoxControl.SelectionStart; // Get the cursor position
string textBoxContent = TextBoxControl.Text;
if (FilteredString.Length < textBoxContent.Length)// this might mean that an invalid character was entered part way through the string
{
cursorPos--; // Step back one character
textBoxContent = textBoxContent.Remove(cursorPos, 1); // Remove the offending character
TextBoxControl.Text = textBoxContent; // Set the string
}
else
TextBoxControl.Text = FilteredString;
if (cursorPos >= TextBoxControl.Text.Length) // If the cursor was at the end of the text
TextBoxControl.SelectionStart = TextBoxControl.Text.Length;
else
TextBoxControl.SelectionStart = cursorPos;
}
}
}
- 我们创建了一个窗体并将其放在上面放了一个文本框。
- 在这种情况下,我保留了默认名称“textBox1”。
- 我们创建了一个“TextChanged”事件方法。
- 我们告诉方法该怎么做:
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (textBox1.Text != string.Empty)
{
string filteredText = TF.GetNumber(TF.currPattern, textBox1.Text, true);
double valueTest = Convert.ToDouble(filteredText);
if (valueTest >= 0.00 && valueTest <= 2000.00)
TF.SetControlText((TextBox)sender, filteredText);
else
MessageBox.Show("$2000.00 transaction limit exceeded. Please re-enter.");
}
}
-
我们的窗体代码(不包括“Designer”内容)看起来像这样:
using System;
using System.Windows.Forms;
using System.Drawing;
using TF = Filters.TextFilters;
namespace NumberTextBox
{
public partial class Form1 : Form
{
#region Form Stuff
public Form1()
{
InitializeComponent();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
if (textBox1.Text != string.Empty)
{
string filteredText = TF.GetNumber(TF.currPattern, textBox1.Text, true);
double valueTest = Convert.ToDouble(filteredText);
if (valueTest >= 0.00 && valueTest <= 2000.00)
TF.SetControlText((TextBox)sender, filteredText);
else
MessageBox.Show("$2000.00 transaction limit exceeded. Please re-enter.");
}
}
# endregion
}
}
结论
这是一个关于正则表达式可以提供的强大功能和灵活性的简单示例,它展示了实现相当健壮的功能只需要很少的代码。
这里还有更多可以做的事情,但我需要在这里结束这篇文章。我希望它能对某处、某人有所帮助。
历史
2015 年 3 月 24 日 - 初次发布。