20

I would like to create a TextBox that only accepts numeric values, in a specific range. What is the best way to implement such TextBox?

I thought about deriving TextBox and to override the validation and coercion of the TextProperty. However, I am not sure how to do this, and I understand that deriving WPF control is generally not recommended.


Edit:
What I needed was a very basic textbox that filters out all key presses which are not digits. The easiest way to achieve it is to handle the TextBox.PreviewTextInput event:
private void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    int result;
    if (!validateStringAsNumber(e.Text,out result,false))
    {
        e.Handled = true;
    }
}

(validateStringAsNumber is my function that primarily use Int.TryParse)

Some of the suggested solutions are probably better, but for the simple functionality I needed this solution is the easiest and quickest to implement while sufficient for my needs.

akjoshi
  • 15,374
  • 13
  • 103
  • 121
Elad
  • 19,079
  • 18
  • 62
  • 71
  • 1
    Check my question on this topic here: http://stackoverflow.com/questions/5511/numeric-data-entry-in-wpf – Matt Hamilton Aug 04 '09 at 08:03
  • Or maybe something like this : private void txtNumeric_PreviewTextInput(object sender, TextCompositionEventArgs e) { if (e.Text.Any(c=>!char.IsDigit(c))) e.Handled = true; } – Amer Sawan Sep 11 '13 at 23:10

5 Answers5

8

Most implementations I have seen so far are using the PreviewTextInput event to implement the correct mask behavior. This one inherits from TextBox and this one uses attached properties. Both use .Net's MaskedTextProvider to provide the correct mask behaviour, but if you just want a simple 'numbers only' textbox you don't need this class.

Lars Truijens
  • 42,837
  • 6
  • 126
  • 143
6
private void txt_TextChanged(object sender, TextChangedEventArgs e)
{
    TextBox textBox = sender as TextBox;
    int iValue = -1;

    if (Int32.TryParse(textBox.Text, out iValue) == false)
    {
         TextChange textChange = e.Changes.ElementAt<TextChange>(0);
         int iAddedLength = textChange.AddedLength;
         int iOffset = textChange.Offset;

         textBox.Text = textBox.Text.Remove(iOffset, iAddedLength);
    }
}
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Nanda
  • 61
  • 1
  • 1
  • 1
    When I use the code above I retrieve error message: "Error 1 'System.Collections.Generic.ICollection' does not contain a definition for 'ElementAt' and no extension method 'ElementAt' accepting a first argument of type 'System.Collections.Generic.ICollection' could be found (are you missing a using directive or an assembly reference?) NewProduct.xaml.cs 104 51 MediaStore" What reference is missing? – HelloWorld1 May 30 '11 at 21:20
  • @Legato probably different .net framework – stukselbax Dec 04 '12 at 08:23
4

In my humble opinion, the best way to fulfill this requirement is using only OnTextChanged event because it can handle the digit from keystroke and also be able to handle Copy+Paste from clipboard too. I hope that my VB code shown below can throw some light on this.

Private Sub NumericBox_TextChanged(sender As Object, e As TextChangedEventArgs) Handles Me.TextChanged
  Dim Buffer As New StringBuilder
  Dim Index As Integer = Me.SelectionStart
  For Each C In Me.Text
    If Char.IsDigit(C) Then
      Buffer.Append(C)
    ElseIf Me.SelectionStart > Buffer.Length Then
      Index -= 1
    End If
  Next
  Me.Text = Buffer.ToString
  Me.SelectionStart = Index
End Sub
Ry-
  • 218,210
  • 55
  • 464
  • 476
Manta
  • 91
  • 5
0

The overall best why is to use the override method OnKeyPressed for the textbox like. Here is the code for the override using the static Char Method IsDigit.

if (!Char.IsDigit(e.KeyChar) && !Char.IsControl(e.KeyChar)) e.Handled = true;

That is all you really need to do.

sth
  • 222,467
  • 53
  • 283
  • 367
Robert
  • 25
  • 1
  • The only problem with this is that it won't allow you to use the decimal point (.) – Jason Ebersey Aug 16 '11 at 20:15
  • 5
    `TextBox` only has `KeyDown` and `PreviewKeyDown` events which send parameter as `KeyEventArgs` . and this doesn't contain `KeyChar` property only `Key` property which returns one from `Keys` enum – Javidan Sep 28 '12 at 09:52
  • 4
    Also, what if someone wanted to paste a number from the clipboard? – Matt Becker Nov 16 '12 at 20:24
-1

This would be my preferred approach:

private void yearTxt_PreviewKeyDown(object sender, KeyEventArgs e)
{
  switch (e.Key)
  {
    case Key.D0:
    case Key.D1:
    case Key.D2:
    case Key.D3:
    case Key.D4:
    case Key.D5:
    case Key.D6:
    case Key.D7:
    case Key.D8:
    case Key.D9:
    case Key.NumLock:
    case Key.NumPad0:
    case Key.NumPad1:
    case Key.NumPad2:
    case Key.NumPad3:
    case Key.NumPad4:
    case Key.NumPad5:
    case Key.NumPad6:
    case Key.NumPad7:
    case Key.NumPad8:
    case Key.NumPad9:
    case Key.Back:
      break;
    default:
      e.Handled = true;
      break;
  }
}
Bryun
  • 29
  • 3
  • If you use numeric text boxes often and don't want to repeat the code a lot, you can do this: 1. Make a method by renaming Bryuns code to 'numericTextBox' or something similar. 2. Call the method in 'PreviewKeyDown' events like this: private void textBox1_PreviewKeyDown(object sender, KeyEventArgs e) { numericTextBox(sender, e); } – matsolof Feb 04 '14 at 09:26
  • 1
    This does not handle pasted text or users modifying numeric keys and typing chars like $%^. Also the user cannot use the arrow keys to move the caret. – Robin May 15 '14 at 09:49