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
Declare a global variable in the form, as shown in the following code.
Private numericTextBox1 As NumericTextBox = Nothing
NumericTextBox numericTextBox1 = null;
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);
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.
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
Add two check box controls to the form.
Set the Text property of the first check box to allow decimal.
Set the Text property of the second check box to allow separators.
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
.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. |