Introduction
This article comes from an idea that occurred to me when I encountered an earlier version (since removed) of this article by Jan Martin:
http://www.codeproject.com/Articles/887085/Simple-and-Reusable-Numbers-only-Textbox-in-Csha
I thought it was a great article for covering character processing of strings. All programmers need this in their tool kit.
To this end I gave him five stars for his article and ‘raised him a Regex’. I think something might have been lost in translation. I have a suspicion he thought I was being critical. Sorry Jan, that wasn’t my intention. I was simply adding ideas to the conversation.
The thought I had was that Regular Expressions (Regex) could be used to achieve a similar result with less raw character processing, by letting .NET’s System.Text.RegularExpressions do all the heavy lifting. Regex patterns could also be derived to apply other restrictions to a TextBox control, or any other textual input control.
Over the years I'vehad to process a lot of formatted text data files. Regex has been key to doing this.
Anyway… I indicated I’d write an article, so here (finally) it is.
Background
As with Jan’s article, the purpose is to ensure that the characters entered into a TextBox control are always in the form required for the data being captured (e.g. integer, positive only double, currency, etc). I’m used to thinking of it as ‘conditioning’ the data.
One difference is that I’m not attempting to tolerate pasted in strings. Pasted strings are a little bit troublesome depending on what you’re trying to extract. Not impossible, just troublesome. This will be all about typing into the text box. Therefore the values will be constantly conditioned as you type.
If Jan’s solution suits your needs better, then that’s the ‘horse for your course’ and I won’t gainsay your choice. I’m simply offering another approach.
Regular Expressions (Regex)
This Wikipedia article (I know, I know.. Wikipedia, bah humbug. Most areas have improved over time, and the aspiration is a good one. Even if some contributions can be dodgy.) gives a reasonable run down on the origins of Regular Expressions, so I won’t attempt to cover that topic here.
At its most powerful, Regex can be intimidating. The syntax can be weird and hard to remember. I’ll freely admit being bamboozled at times trying to work out why something wasn’t working. Regex is categorically not for the absolute beginner. Cheat sheets like this one are a must, but you will always have to work out patterns that suit the Regex parser you’re dealing with. .NET’s parser is different to some others.
I’ve cribbed some astounding Regex patterns over the last few years, courtesy of Google and Bing searches, and they have saved me endless heartache processing large, formatted text files, including things like nested curly brackets ( {data {more data}} ), or extracting strings surrounded by quotes ( “some string” ). For this article, I’ll keep the patterns fairly simple (yet useful) and explain each one.
Here are three main ways to apply, or action, a Regular Expression. It depends a lot on what you need to do with the result.
The most transparent (if longer) method is:
string p = "(?<Number>[0-9])"; Regex r = new Regex(p); Match m = r.Match("123hfj76l098"); string firstInteger = m.Groups["Number"].Value;
Another way:
string p = "(?<Number>[0-9])"; Match m = Regex.Match("123hfj76l098", p); string firstInteger = m.Groups["Number"].Value;
Yet another way:
string firstInteger = Regex.Match("123hfj76l098", "(?<Number>[0-9])").Groups["Number"].Value;
The third option is most valid for getting a single matching value from a string. However I need to keep processing the string until all matching characters are found.
I could therefore go with either the first or second options, but I’m going to go with the first, for its transparency and the Match object (m), that I can keep working with.
You’ll notice I’m not making use of ‘var’. It’s not that I’m against its use, I’m just choosing to be very explicit for this article.
Let’s get to it.
Using the code
The TextFilter Class
Below is the beginning of a class of ‘Text Filters’, with a starter method ‘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;
}
}
}
Here’s what it’s doing in long winded (yet mostly concise) English:
- Call in the ‘System.Text.RegularExpressions’ namespace reference.
- Define a new namespace ‘Filters’.
- Define a new class ‘’TextFilters".
- Establish a string variable for an integer Regex pattern.
- I’m doing this here, because it’s a convenient place to gather the Regex pattern strings together.
- I’ll explain the pattern itself in a moment.
- Create a public method ‘GetInteger’ with a return type of ‘string’
- We’re going to pass in a ‘SourceString’ parameter from the TextBox,
- We’ll pass in a ‘PosOnly’ parameter to determine whether we’ll accept negative numbers.
- It’s set to ‘false’ by default, so we don’t always have to provide that parameter.
- Initialise the return string : newNumber.
- If ‘positive only’ numbers are not require:
- Check whether the source string starts with a negative.Effectively we’re not accepting a ‘-‘ character anywhere else in the string.Makes sense.Doing maths in the TextBox is another topic.
- If it does, add a ‘-‘ to ‘newNumber’
- Initialise a new Regex object (r), passing in ‘intPattern’.
- Initialise a Match object (m), using the previously created Regex object (r), passing in ‘SourceString’.
- This will acquire the first match of the pattern in the source string.
- While a match for the pattern is successfully found in the source string
- Add the ‘capture group’ called “Number” to ‘newNumber’.
- Get the next match.
- Return ‘newNumber’.
Comparing that explanation with the code just proves that C# is really quite elegant.
So… The Regex pattern.
The string "(?<Number>[0-9])" can be read as:
- ( - Open a capture group
- ?<Number> - Name the capture group ‘Number’
- [0-9] - Define a range looking for any ‘single’ character between zero and nine.
- ) - Close the capture group
Alternative Patterns
What about other number types? Decimal numbers for one.
Decimal Pattern
Add another pattern string below the integer pattern:
public static string
intPattern = "(?<Number>[0-9])",
decPattern = @"(?<Number>^[0-9]*\.?[0-9]*)",
This pattern is saying:
- @ - Define a literal string. Important for the escaped decimal point ‘\.’ Dots have special meaning in Regex.
- ( - Open a capture group
- ?<Number> - Call the capture group ‘Number”
- ^ - “The following pattern must appear at the start of the source string”. It makes little sense to allow a decimal number pattern to appear anywhere else.
- [0-9]* - Define a range looking for any number (0-9) any number of times ‘0…n’ (*).
- \.? - Look for a decimal point (\.),allowing it to be optional (?).
- [0-9]* - Look for any numbers following the (optional) decimal point. This will stop when it hits a non-matching character.
- ) - Close the capture group.
This should therefore allow at least the following:
We could trap the case where someone only enters a dot (.), but that’s really not the Regex pattern’s task. It’s only invalid when the value is being processed. Not while it’s being entered.
Be warned that if you move the cursor and type in an invalid character, you will lose every character after the invalid one. It’s the way the pattern works. See the ‘SetControlText’ method below for working around this.
Lets slightly modify our ‘GetInteger’ method.
- Rename it to ‘GetNumber’
- Add another string parameter (RegexPattern) that will accept the Regex pattern string.
- Add a line that removes ‘-‘ characters from the string.
- This is more important for the decimal pattern than it was for the integer pattern as the decimal pattern forces a search from the start of the string.
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;
}
This code will tolerate a ‘-‘ character at the start of the text, and will remove any subsequent values that do not comply as you type into the text box.
Currency Pattern
So what sort of pattern would you construct for currency?
Let’s say we want the following restrictions:
- We want any ‘$’ signs removed from the string.They aren’t useful to calculations, and the TextBox label should make it clear that it’s not necessary.
- We will allow any number of digits prior to the decimal
- We only want a maximum of two digits after the decimal.We do want to tolerate whole number entries (Unlike some ATMs.Why is that?It’s not like you can get coins out of them).
The pattern would be:
@"^\$?(?<Number>[0-9]*\.?[0-9]{0,2})"
- @ - Define a literal string. Again because we’ve got some unusual escape sequences, required for Regex.
- ^ - The following must appear at the start of the string.
- \$? - Allow for a dollar character (\$), allowing it to be optional (?). ‘$’ symbols have meaning in Regex and must be escaped if you’re actually looking for the ‘$’ character.
- ( - Open a capture group.
- ?<Number > - Call the capture group “Number”.
- [0-9* - Define a range looking for any number (0-9) any number of times ‘0…n’ (*).
- \.? - Look for a decimal point (\.), allowing it to be optional (?).
- [0-9]{0,2} - Look for any numbers following the (optional) decimal point., between 0 and 2 of them
- ) - Close the capture group.
As with the decimal pattern, if you move the cursor and type in an invalid character, you will lose every character after the invalid one. Again the ‘SetControlText’ method below sorts this out.
Now we have a class that can handle at least three different number formats through the one method. This is why I’ve persisted with calling my capture group ‘Number’ instead of ‘Integer’, ‘Double’ and ‘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;
}
}
}
Once you’ve got the number, you can run other checks on it to see whether it fits any other restrictions you want to place on it:
- Does it fit the range for a signed 32 or 64 bit integer (Int32, Int64)?
- Does it fit the range for an unsigned 32 or 64 bit integer (Uint32, UInt64)?
- Does it fit the range for a ‘float’ or ‘double’?
- Does it fit a financial transaction limitation?
The TextBox
Let’s look at the code for the TextChanged event.
At its most basic, the code for ensuring an integer would be:
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;
}
In English:
- Initialise an instance of the ‘TextFilters’ class (tF)
- Set the ‘Text’ property of the TextBox to the value retrieved by the ‘GetNumber’ method
- Ensure that the cursor is at the end of the TextBox content.
Make It ‘Static’
It would be nicer if our ‘TextFilters’ class was static, so that we could call it without creating an instance all the time. It is more of a ‘functional’ class (i.e. “do this”), rather than a ‘thing’ class (i.e. “make this”). We shouldn’t have a need for multiple instances of the ‘object’.
The class now looks like this (Note the highlighted ‘static’ modifier and addition of ‘public’ to the class.):
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;
}
}
}
The ‘TextChanged’ method can be simplified:
Note:
If you’re a lazy typist (like me), you could change the ‘using Filters;‘ statement to ‘using TF = Filters.TextFilters;’ instead, substituting ‘TF’ for the full ‘TextFilters’.
private void textBox1_TextChanged(object sender, EventArgs e)
{
textBox1.Text = TF.GetNumber(TF.intPattern, textBox1.Text);
textBox1.SelectionStart = textBox1.Text.Length;
}
Make It Smarter
That’s all good. But I really want to create a method I can apply to any TextBox ‘TextChanged’ event method, and can cope when a user places their cursor in the middle of the text to change it.
I’m going to consider this as part of my text filtering process, and add the method to the ‘TextFilters’ class.
Here’s the new method:
public static void SetControlText(System.Windows.Forms.TextBox TextBoxControl, string FilteredString)
{
int cursorPos = TextBoxControl.SelectionStart; string textBoxContent = TextBoxControl.Text;
if (FilteredString.Length < textBoxContent.Length) {
cursorPos--; textBoxContent = textBoxContent.Remove(cursorPos, 1); TextBoxControl.Text = textBoxContent; }
else
TextBoxControl.Text = FilteredString;
if (cursorPos >= TextBoxControl.Text.Length) TextBoxControl.SelectionStart = TextBoxControl.Text.Length;
Else TextBoxControl.SelectionStart = cursorPos;
}
I’ve used the fully qualified ‘System.Windows.Forms.TextBox’ as it’s the only reference I’ll make to ‘System.Windows.Forms’ in this class. Six of one, half a dozen of the other.
The ‘TextChanged’ method becomes:
void textBox1_TextChanged(object sender, EventArgs e)
{
string filteredText = TF.GetNumber(TF.intPattern, intTextBox.Text);
TF.SetControlText((TextBox)sender, filteredText);
}
Filter Restrictions
How about those other restrictions we might want to apply?
Here’s the scenario:
- We have a control called ‘textBoxTransaction’
- We want to make sure that the transaction is between $0.00 and $2000.00
- This limitation may result from:
- A financial delegation value in another control,
- A lookup based on the user of the application,
- A daily transaction limit on an account,
- The possibilities are endless.
Our ‘TextChanged’ event method could be:
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.");
}
This will trap larger values before any decimals are entered. The 'true' parameter is trapping negative entries.
We do have an issue where the ‘Text’ property can be empty (“”) when the test is performed.
So…
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.");
}
}
Bringing it all together
- We’ve now got the beginnings of a nice little TextFilter class, that we can expand as needed.
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; string textBoxContent = TextBoxControl.Text;
if (FilteredString.Length < textBoxContent.Length) {
cursorPos--; textBoxContent = textBoxContent.Remove(cursorPos, 1); TextBoxControl.Text = textBoxContent; }
else
TextBoxControl.Text = FilteredString;
if (cursorPos >= TextBoxControl.Text.Length) TextBoxControl.SelectionStart = TextBoxControl.Text.Length;
else
TextBoxControl.SelectionStart = cursorPos;
}
}
}
- We’ve created a form and placed a textbox on it.
- In this case, I’ve retained the default name ‘textBox1’.
- We’ve created a ‘TextChanged’ event method.
- We’ve told the method what to do:
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.");
}
}
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
}
}
Conclusion
This was a simple example of the power and flexibility that regular expressions can provide and shows how little code you have to implement to get something fairly robust.
There is more that can be done here, but I needed to end this article somewhere. I hope it proves useful to somebody, somewhere.
History
24th March 2015 - Initial publication.