Click here to Skip to main content
15,886,689 members
Articles / Programming Languages / C#

Numbers only TextBox using Regular Expressions in C#

Rate me:
Please Sign up or sign in to vote.
4.71/5 (12 votes)
23 May 2015CPOL11 min read 66.2K   635   20   4
This article will show how to use Regular Expressions (Regex) to restrict TextBox content to numbers.

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:

C#
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

Another way:

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

Yet another way:

C#
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’:

C#
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:

C#
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:

  • 1234
  • 1234.5678
  • .1

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’.

C#
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:

C#
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.):

C#
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’.

C#
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:

C#
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;
}

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:

C#
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:

C#
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…

C#
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.
C#
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;
        }
 
    }
}
  • 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:
C#
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.");
    }
}
  • Our form code (minus the ‘Designer’ content) looks like this:

C#
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Australia Australia
My qualifications are as a road design draftsman, using AutoCAD and 12d.

Over the years I picked up LISP, .NET and 12d Programming Language enabling me to highly customise these applications.

It's certainly been a roller-coaster ride.

Comments and Discussions

 
QuestionVery helpful Pin
jrobb2297-Nov-15 19:26
jrobb2297-Nov-15 19:26 
QuestionExceptions may occur when pasting Pin
AORD25-May-15 10:30
AORD25-May-15 10:30 
AnswerRe: Exceptions may occur when pasting Pin
bjmallon1-Jun-15 0:02
bjmallon1-Jun-15 0:02 
GeneralGood Article Pin
kashif Atiq24-May-15 23:20
kashif Atiq24-May-15 23:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.