How to: Control User Input in a Numeric Text Box

You can create a custom control that is derived from TextBox that accepts only numeric input. This topic shows how to define a NumericTextBox class and how to put the custom control on a form.

This custom control resembles the one described in How to: Create a Numeric Text Box. This implementation of the control uses a more restrictive method of controlling user input. This control accepts only a limited set of values, including digits, decimals, separators, and the negative sign. When the control accepts input from the keypad, it checks the whole input string and then re-displays only the valid characters.

To derive a class from TextBox

  • Add the NumericTextBox class to your project by using the following code.

    Class NumericTextBox
        Inherits TextBox
    
        Private m_allowDecimal As Boolean = False 
        Private m_allowSeparator As Boolean = False 
        Private skipEvent As Boolean = false
    
        Private decimalCount As Integer = 0
        Private separatorCount As Integer = 0
    
        Private numberFormatInfo As NumberFormatInfo
        Private decimalSeparator As String 
        Private groupSeparator As String 
        Private negativeSign As String 
        Private groupSize As Integer()
    
        Public Sub New()
            numberFormatInfo = System.Globalization.CultureInfo.CurrentCulture.NumberFormat
            decimalSeparator = numberFormatInfo.NumberDecimalSeparator
            groupSeparator = numberFormatInfo.NumberGroupSeparator
            groupSize = numberFormatInfo.NumberGroupSizes
            negativeSign = numberFormatInfo.NegativeSign
        End Sub 
    
        ' Restricts the entry of characters to digits (including hexidecimal), the negative sign,  
        ' the decimal point, and editing keystrokes (backspace).  
        Protected Overloads Overrides Sub OnKeyPress(ByVal e As KeyPressEventArgs)
            MyBase.OnKeyPress(e)
    
            Dim keyInput As String = e.KeyChar.ToString()
    
            If [Char].IsDigit(e.KeyChar) Then
                UpdateText(e.KeyChar)
                e.Handled = True 
    
                ' Allows one decimal separator as input.  
            ElseIf keyInput.Equals(decimalSeparator) Then 
                If m_allowDecimal Then
                    UpdateText(e.KeyChar)
                End If
                e.Handled = True 
    
                ' Application should support a method to temporarily 
                ' change input modes to allow input of the decimal 
                ' character on the widest range of keyboards, especially 
                ' 12-key keyboards (no Sym button). InputMode will reset 
                ' to Numeric after next key press. 
                ' Alternative method: 
                ' else if (Char.IsPunctuation(e.KeyChar)) 
            ElseIf keyInput.Equals("*") Then 
                If (AllowDecimal _
                            AndAlso (decimalCount = 0)) Then
                    InputModeEditor.SetInputMode(Me, InputMode.AlphaABC)
                    ' Supports reset for devices  
                    ' on which KeyUp fires last.
                    skipEvent = True 
                End If
                e.Handled = True 
    
                ' Allows separator.  
            ElseIf keyInput.Equals(groupSeparator) Then 
                If m_allowSeparator Then
                    UpdateText(e.KeyChar)
                End If
                e.Handled = True 
    
                ' Allows negative sign if it is the initial character.  
            ElseIf keyInput.Equals(negativeSign) Then
                UpdateText(e.KeyChar)
                e.Handled = True 
    
                ' Allows Backspace key.  
            ElseIf e.KeyChar = ControlChars.Back Then 
    
            ElseIf e.KeyChar = ControlChars.Cr Then 
                ' Validate input when Enter key is pressed.  
                ' Take other action. 
                UpdateText(e.KeyChar)
            Else 
    
                ' Consume this invalid key and beep.  
                ' MessageBeep(); 
                e.Handled = True 
            End If 
        End Sub 
    
        Protected Overrides Sub OnKeyDown(ByVal e As KeyEventArgs)
            MyBase.OnKeyDown(e)
            ' InputModeEditor.SetInputMode(this, InputMode.Numeric); 
        End Sub 
    
        Protected Overrides Sub OnKeyUp(ByVal e As KeyEventArgs)
            MyBase.OnKeyUp(e)
            If skipEvent Then
                skipEvent = false
                Return 
            End If 
            ' Restores text box to Numeric mode if it was 
            ' changed for decimal entry.
            InputModeEditor.SetInputMode(Me, InputMode.Numeric)
        End Sub 
    
        Public Property AllowDecimal() As Boolean 
            Get 
                Return m_allowDecimal
            End Get 
            Set(ByVal value As Boolean)
                m_allowDecimal = value
            End Set 
        End Property 
    
        Public Property AllowSeparator() As Boolean 
            Get 
                Return m_allowSeparator
            End Get 
            Set(ByVal value As Boolean)
                m_allowSeparator = value
            End Set 
        End Property 
    
        Public ReadOnly Property IntValue() As Integer 
            Get 
                Try 
                    Return Int32.Parse(Me.Text, NumberStyles.AllowThousands Or NumberStyles.AllowLeadingSign)
                Catch e As FormatException
                    MessageBox.Show("invalid format: " + e.Message.ToString())
                    Return 0
                Catch e As OverflowException
                    MessageBox.Show("Exceeded min/max value!")
                    Return 0
                End Try 
            End Get 
        End Property 
    
        Public ReadOnly Property DecimalValue() As Decimal 
            Get 
                Try 
                    Return [Decimal].Parse(Me.Text, NumberStyles.AllowDecimalPoint Or NumberStyles.AllowThousands Or NumberStyles.AllowLeadingSign)
                Catch e As FormatException
                    MessageBox.Show("invalid format: " + e.Message.ToString())
                    Return 0
                End Try 
            End Get 
        End Property 
    
        ' Checks current input characters  
        ' and updates control with valid characters only.  
        Public Sub UpdateText(ByVal newKey As Char)
            decimalCount = 0
            separatorCount = 0
            Dim input As String
            input = (Me.Text + newKey.ToString)
            Dim updatedText As String = "" 
            Dim cSize As Integer = 0
    
            ' char[] tokens = new char[] { decimalSeparator.ToCharArray()[0] };  
            ' NOTE: Supports decimalSeparator with a length == 1.  
            Dim token As Char = decimalSeparator.ToCharArray()(0)
            Dim groups As String() = input.Split(token)
    
            ' Returning input to the left of the decimal.  
            Dim inputChars As Char() = groups(0).ToCharArray()
            ' Reversing input to handle separators.  
            Dim rInputChars As Char() = inputChars.Reverse().ToArray()
            Dim sb As New StringBuilder()
    
            Dim validKey As Boolean = False 
    
            For x As Integer = 0 To rInputChars.Length - 1
    
                If rInputChars(x).ToString().Equals(groupSeparator) Then 
                    Continue For 
                End If 
    
                ' Checking for decimalSeparator is not required in  
                ' current implementation. Current implementation eliminates  
                ' all digits to the right of extraneous decimal characters.  
                If rInputChars(x).ToString().Equals(decimalSeparator) Then 
                    If Not m_allowDecimal Or decimalCount > 0 Then 
                        Continue For 
                    End If 
                    ' decimalCount += 1 
                    ' validKey = True 
                End If 
    
                If rInputChars(x).ToString().Equals(negativeSign) Then 
                    ' Ignore negativeSign unless processing first character.  
                    If x < (rInputChars.Length - 1) Then 
                        Continue For 
                    End If
                    sb.Insert(0, rInputChars(x).ToString())
                    x += 1
                    Continue For 
                End If 
    
                If m_allowSeparator Then 
                    ' NOTE: Does not support multiple groupSeparator sizes.  
                    If cSize > 0 AndAlso cSize Mod groupSize(0) = 0 Then
                        sb.Insert(0, groupSeparator)
                        separatorCount += 1
                    End If 
                End If 
    
                ' Maintaining correct group size for digits.  
                If [Char].IsDigit(rInputChars(x)) Then 
                    ' Increment cSize only after processing groupSeparators. 
                    cSize += 1
                    validKey = True 
                End If 
    
                If validKey Then
                    sb.Insert(0, rInputChars(x).ToString())
                End If
    
                validKey = False 
            Next
    
            updatedText = sb.ToString()
    
            If m_allowDecimal AndAlso groups.Length > 1 Then 
                Dim rightOfDecimals As Char() = groups(1).ToCharArray()
                Dim sb2 As New StringBuilder()
    
                For Each dec As Char In rightOfDecimals
                    If [Char].IsDigit(dec) Then
                        sb2.Append(dec)
                    End If 
                Next
                updatedText += decimalSeparator + sb2.ToString()
            End If 
            ' Updates text box.  
            Me.Text = updatedText
            ' Updates cursor position.  
            Me.SelectionStart = Me.Text.Length
        End Sub 
    End Class
    
    class NumericTextBox : TextBox
    {
    
        bool allowDecimal = false;
        bool allowSeparator = false;
        bool skipEvent = false;
    
        int decimalCount = 0;
        int separatorCount = 0;
    
        NumberFormatInfo numberFormatInfo;
        string decimalSeparator;
        string groupSeparator;
        string negativeSign;
        int[] groupSize;
    
        public NumericTextBox()
        {
            numberFormatInfo = System.Globalization.CultureInfo.CurrentCulture.NumberFormat;
            decimalSeparator = numberFormatInfo.NumberDecimalSeparator;
            groupSeparator = numberFormatInfo.NumberGroupSeparator;
            groupSize = numberFormatInfo.NumberGroupSizes;
            negativeSign = numberFormatInfo.NegativeSign;
        }
    
        // Restricts the entry of characters to digits (including hexadecimal), the negative sign, 
        // the decimal point, and editing keystrokes (backspace). 
        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            base.OnKeyPress(e);
    
            string keyInput = e.KeyChar.ToString();
    
            if (Char.IsDigit(e.KeyChar))
            {
                UpdateText(e.KeyChar);
                e.Handled = true;
            }
    
            if (Char.IsSeparator(e.KeyChar))
            {
                UpdateText(e.KeyChar);
                e.Handled = true;
            }
    
            // Allows one decimal separator as input. 
            else if (keyInput.Equals(decimalSeparator))
            {
                if (allowDecimal)
                {
                    UpdateText(e.KeyChar);
                }
                e.Handled = true;
            }
    
            // Application should support a method to temporarily 
            // change input modes to allow input of decimal 
            // character on widest range of keyboards, especially 
            // 12-key keyboards (no Sym button). InputMode will reset 
            // to Numeric after next key press. 
            // Alternative method: 
            // else if (Char.IsPunctuation(e.KeyChar)) 
            else if (keyInput.Equals("*"))
            {
                if (allowDecimal && decimalCount == 0)
                {
                    InputModeEditor.SetInputMode(this, InputMode.AlphaABC);
                    // Supports reset for devices  
                    // where KeyUp fires last.
                    skipEvent = true;
                }
                e.Handled = true;
            }
    
            // Allows separator. 
            else if (keyInput.Equals(groupSeparator))
            {
                if (allowSeparator)
                {
                    UpdateText(e.KeyChar);
                }
                e.Handled = true;
            }
    
            // Allows negative sign if the negative sign is the initial character. 
            else if (keyInput.Equals(negativeSign))
            {
                UpdateText(e.KeyChar);
                e.Handled = true;
            }
    
            else if (e.KeyChar == '\b')
            {
                // Allows Backspace key.
            }
    
            else if (e.KeyChar == '\r')
            {
                UpdateText(e.KeyChar);
                // Validate input when Enter key is pressed. 
                // Take other action.
            }
    
            else
            {
                // Consume this invalid key and beep.
                e.Handled = true;
                // MessageBeep();
            }
        }
    
        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
    
            // InputModeEditor.SetInputMode(this, InputMode.Numeric);
        }
        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);
    
            if (skipEvent)
            {
                skipEvent = false;
                return;
            }
            // Restores text box to Numeric mode if it was 
            // changed for decimal entry.
            InputModeEditor.SetInputMode(this, InputMode.Numeric);
        }
    
        public bool AllowDecimal
        {
            get
            {
                return allowDecimal;
            }
            set
            {
                allowDecimal = value;
            }
        }
    
        public bool AllowSeparator
        {
            get
            {
                return allowSeparator;
            }
            set
            {
                allowSeparator = value;
            }
        }
    
        public int IntValue
        {
            get
            {
                try
                {
                    return Int32.Parse(this.Text, NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign);
                }
                catch (FormatException e)
                {
                    MessageBox.Show("invalid format: " + e.Message.ToString());
                    return 0;
                }
                catch (OverflowException e)
                {
                    MessageBox.Show("Exceeded min/max value!" + e.Message.ToString());
                    return 0;
                }
            }
        }
    
        public decimal DecimalValue
        {
            get
            {
                try
                {
                    return Decimal.Parse(this.Text, NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign);
                }
                catch (FormatException e)
                {
                    MessageBox.Show("invalid format: " + e.Message.ToString());
                    return 0;
                }
            }
        }
    
        // Checks current input characters 
        // and updates control with valid characters only. 
        public void UpdateText(char newKey)
        {
            decimalCount = 0;
            separatorCount = 0;
            string input;
    
            input = this.Text + newKey.ToString();
    
            string updatedText = "";
            int cSize = 0;
    
            // char[] tokens = new char[] { decimalSeparator.ToCharArray()[0] }; 
            // NOTE: Supports decimalSeparator with a length == 1. 
            char token = decimalSeparator.ToCharArray()[0];
            string[] groups = input.Split(token);
    
            // Returning input to left of decimal. 
            char[] inputChars = groups[0].ToCharArray();
            // Reversing input to handle separators. 
            char[] rInputChars = inputChars.Reverse().ToArray();
            StringBuilder sb = new StringBuilder();
    
            bool validKey = false;
    
            for(int x = 0; x < rInputChars.Length; x+)
            {
    
                if (rInputChars[x].ToString().Equals(groupSeparator))
                {
                    continue;
                }
    
                // Checking for decimalSeparator is not required in 
                // current implementation. Current implementation eliminates 
                // all digits to the right of extraneous decimal characters. 
                if (rInputChars[x].ToString().Equals(decimalSeparator))
                {
                    if (!allowDecimal | decimalCount > 0)
                    {
                        continue;
                    }
    
                    //validKey = true;
                }
    
                if (rInputChars[x].ToString().Equals(negativeSign))
                {
                    // Ignore negativeSign unless processing first character. 
                    if (x < (rInputChars.Length - 1))
                    {
                        continue;
                    }
                    sb.Insert(0, rInputChars[x].ToString());
                    x++;
                    continue;
                }
    
                if (allowSeparator)
                {
                    // NOTE: Does not support multiple groupSeparator sizes. 
                    if (cSize > 0 && cSize % groupSize[0] == 0)
                    {
                        sb.Insert(0, groupSeparator);
                        separatorCount++;
                    }
                }
    
                // Maintaining correct group size for digits. 
                if (Char.IsDigit(rInputChars[x]))
                {
                    // Increment cSize only after processing groupSeparators.
                    cSize++;
                    validKey = true;
                }
    
                if (validKey)
                {
                    sb.Insert(0, rInputChars[x].ToString());
                }
    
                validKey = false;
            }
    
            updatedText = sb.ToString();
    
            if (allowDecimal && groups.Length > 1)
            {
                char[] rightOfDecimals = groups[1].ToCharArray();
                StringBuilder sb2 = new StringBuilder();
    
                foreach (char dec in rightOfDecimals)
                {
                    if (Char.IsDigit(dec))
                    {
                        sb2.Append(dec);
                    }
                }
                updatedText += decimalSeparator + sb2.ToString();
            }
            // Updates text box. 
            this.Text = updatedText;
            // Updates cursor position. 
            this.SelectionStart = this.Text.Length;
        }
    }
    

To add the NumericTextBox control to the form

  1. Declare a global variable in the form, as shown in the following code.

    Private numericTextBox1 As NumericTextBox = Nothing
    
    NumericTextBox numericTextBox1 = null;
    
  2. Add the following code to the form's constructor or Load event.

    ' Create an instance of NumericTextBox. 
    numericTextBox1 = New NumericTextBox()
    numericTextBox1.Parent = Me 
    
    'Draw the bounds of the NumericTextBox. 
    numericTextBox1.Bounds = New Rectangle(5, 5, 150, 100)
    
    // Create an instance of NumericTextBox.
    numericTextBox1 = new NumericTextBox();
    numericTextBox1.Parent = this;
    
    // Draw the bounds of the NumericTextBox.
    numericTextBox1.Bounds = new Rectangle(5, 5, 150, 100);
    
  3. For a Smartphone application, specify an InputMode by using the following code.

    ' Specify an InputMode on a Smartphone.  
    ' On a Pocket PC, use an InputPanel instead. 
    InputModeEditor.SetInputMode(numericTextBox1, InputMode.Numeric)
    
    // Specify an InputMode on a Smartphone. 
    // On a Pocket PC, use an InputPanel instead.
    InputModeEditor.SetInputMode(numericTextBox1, InputMode.Numeric);
    

    Note

    For a Pocket PC application, add an InputPanel component to the form for user input into the numeric text box. For more information, see How to: Use the InputPanel Component.

  4. Use the following code to set the focus on the numeric text box.

    numericTextBox1.Focus()
    
    numericTextBox1.Focus();
    

To add controls that enable you to set properties of the numeric text box

  1. Add two check box controls to the form.

  2. Set the Text property of the first check box to allow decimal.

  3. Set the Text property of the second check box to allow separators.

  4. Add the following event handlers for the check boxes to the form.

    Private Sub checkBox1_CheckStateChanged(ByVal sender As Object, ByVal e As EventArgs)
        If checkBox1.Checked Then 
            ' checkBox2.Checked = false; 
            numericTextBox1.AllowDecimal = True 
        Else
            numericTextBox1.AllowDecimal = False 
        End If 
        Me.numericTextBox1.UpdateText(Microsoft.VisualBasic.ChrW(32))
    End Sub 
    
    Private Sub checkBox2_CheckStateChanged(ByVal sender As Object, ByVal e As EventArgs)
        If checkBox2.Checked Then 
            ' checkBox1.Checked = false; 
            numericTextBox1.AllowSeparator = True 
        Else
            numericTextBox1.AllowSeparator = False 
        End If 
        Me.numericTextBox1.UpdateText(Microsoft.VisualBasic.ChrW(32))
    End Sub
    
    private void checkBox1_CheckStateChanged(object sender, EventArgs e)
    {
        if (checkBox1.Checked)
        {
            numericTextBox1.AllowDecimal = true;
        }
        else
        {
            numericTextBox1.AllowDecimal = false;
        }
        this.numericTextBox1.UpdateText(' ');
    }
    
    private void checkBox2_CheckStateChanged(object sender, EventArgs e)
    {
        if (checkBox2.Checked)
        {
            numericTextBox1.AllowSeparator = true;
        }
        else
        {
            numericTextBox1.AllowSeparator = false;
        }
        this.numericTextBox1.UpdateText(' ');
    }
    

Compiling the Code

This example requires references to the following namespaces:

See Also

Tasks

How to: Set Smartphone Input Modes

Concepts

Custom Control Development

.NET Compact Framework How-to Topics

Change History

Date

History

Reason

October 2008

Added topic.

Information enhancement.

February 2009

Fixed bug in code

Customer feedback.