410

I'm looking to accept digits and the decimal point, but no sign.

I've looked at samples using the NumericUpDown control for Windows Forms, and this sample of a NumericUpDown custom control from Microsoft. But so far it seems like NumericUpDown (supported by WPF or not) is not going to provide the functionality that I want. The way my application is designed, nobody in their right mind is going to want to mess with the arrows. They don't make any practical sense, in the context of my application.

So I'm looking for a simple way to make a standard WPF TextBox accept only the characters that I want. Is this possible? Is it practical?

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
Giffyguy
  • 20,378
  • 34
  • 97
  • 168

33 Answers33

500

Add a preview text input event. Like so: <TextBox PreviewTextInput="PreviewTextInput" />.

Then inside that set the e.Handled if the text isn't allowed. e.Handled = !IsTextAllowed(e.Text);

I use a simple regex in IsTextAllowed method to see if I should allow what they've typed. In my case I only want to allow numbers, dots and dashes.

private static readonly Regex _regex = new Regex("[^0-9.-]+"); //regex that matches disallowed text
private static bool IsTextAllowed(string text)
{
    return !_regex.IsMatch(text);
}

If you want to prevent pasting of incorrect data hook up the DataObject.Pasting event DataObject.Pasting="TextBoxPasting" as shown here (code excerpted):

// Use the DataObject.Pasting Handler 
private void TextBoxPasting(object sender, DataObjectPastingEventArgs e)
{
    if (e.DataObject.GetDataPresent(typeof(String)))
    {
        String text = (String)e.DataObject.GetData(typeof(String));
        if (!IsTextAllowed(text))
        {
            e.CancelCommand();
        }
    }
    else
    {
        e.CancelCommand();
    }
}
Dai
  • 141,631
  • 28
  • 261
  • 374
Ray
  • 45,695
  • 27
  • 126
  • 169
  • 6
    Your regex doesn't allow scientific notation (1e5) if that is important. – Ron Warholic Aug 12 '11 at 17:32
  • 19
    Note that this answer only checks what you type, so you could enter 3-.3 – David Sykes Mar 15 '13 at 11:56
  • 1
    Specifying a dot hard coded as decimal separator is not right as well. if i select a different number format in the windows settings i expect that it works in your application as well. – quadroid Feb 11 '14 at 09:15
  • 182
    The point of the answer wasn't to specify the perfect Regex, it was to show how to use WPF to filter what someone types. – Ray Feb 14 '14 at 03:49
  • 1
    This approach isn't reusable. Shouldn't we use behaviors? – SepehrM Oct 09 '14 at 08:09
  • 37
    [Space] doesn't fire PreviewTextInput event. – peterG Oct 31 '14 at 18:31
  • 2
    `e.Handled = !IsTextAllowed(e.Text);` doesn't work well with Chinese. – dmigo May 26 '15 at 13:55
  • any method of preventing a negative sign after the number? (e.g. 3.2-) or a second dot (e.g. 3.34.4) – Mehrad May 29 '15 at 06:24
  • 4
    @Mehrad: Use Double.TryParse and filter based on whether it succeeds or not – Pyritie Sep 10 '15 at 16:53
  • probably reacting on OnTextChanged event is better idea. – GeniusIsme Feb 09 '16 at 14:38
  • why the double negative?.. To read it it's "e.Handled equals the opposite of the opposite of regex.IsMatch" – Murphybro2 Apr 19 '16 at 13:36
  • 1
    @Murphybro2 - Just preference to keep function names positive (e.g. IsAllowed instead of IsDisallowed) – Ray Aug 25 '16 at 21:56
  • It does not accept commas. In Germany it's `,`, not `.`. Please remove that Regex. It's not helpful. Leave the implementation open. – Thomas Weller Oct 25 '17 at 12:49
  • 15
    Something like `double.TryParse()` would probably be implemented with the same number of lines and be more flexible. – Thomas Weller Oct 25 '17 at 12:54
  • 4
    @ThomasWeller double.TryParse won't accept dot `.`, since `e.Text` only returns the last input character and a dot cannot be parsed to a double. – Anthony Dec 28 '17 at 22:28
  • 1
    no matter what you define in PreviewTextInput event method, it won't properly handle the negative sign, unless the textbox does not allow the user to move the input cursor and use `e.Handled = !double.TryParse(((TextBox) sender).Text + e.Text, out d);` – Anthony Dec 29 '17 at 11:32
  • Has anyone encountered a crash using this code? I am using it in a tool, and I am getting reports from users that the tool occasionally crashes while they are typing into the textbox. Crash only occurs when typing into textboxes that have the PreviewTextInput option enabled. They are typing valid characters. – Flopdong Jan 24 '19 at 19:53
  • it is ok but I can insert multi '.' like '11.2.3.6.256' – david2020 Feb 16 '20 at 19:12
  • Powershell Regex is slightly different: `^[0-9]+$` – Patrick Nov 05 '21 at 11:26
  • For WinUI it doesn't look like there is a `PreviewTextInput` event. [BeforeTextChanging](https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.textbox.beforetextchanging?view=winrt-22000) seems to work. – Felix Dec 12 '21 at 20:39
  • PreviewTextInput event handled for TextBox WPF but user can able to add space in between the entered numbers in textbox and PreviewTextInput event also not firing. – Sandeep Jadhav Dec 27 '21 at 13:18
254

The event handler is previewing text input. Here a regular expression matches the text input only if it is not a number, and then it is not made to entry textbox.

If you want only letters then replace the regular expression as [^a-zA-Z].

XAML

<TextBox Name="NumberTextBox" PreviewTextInput="NumberValidationTextBox"/>

XAML.CS FILE

using System.Text.RegularExpressions;
private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
{
    Regex regex = new Regex("[^0-9]+");
    e.Handled = regex.IsMatch(e.Text);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kishor
  • 4,006
  • 7
  • 30
  • 47
  • 1
    The Event handler is preview text input. Here regular expression matches the text input only if it is not a number, then it is not made to entry textbox. If you want only alphabets then replace the regular expression as [^a-zA-Z]. – Kishor Oct 19 '12 at 05:29
  • What about numbers, decimals and operators? – Jason Ebersey May 21 '13 at 19:19
  • Please let me know how to use it when declares in some other STATIC class and apply to textbox? – SHEKHAR SHETE Jun 28 '13 at 07:41
  • 8
    I like this one more than the actual answer, short and simple. The actual answer needs two methods to get the same result. – Revils Dec 16 '15 at 08:34
  • @Sliver2009 - True, but the accepted answer is a better choice if you have more than one textbox that needs validating (assuming that your textboxes are validating against the same criteria). – Jagd Feb 22 '17 at 22:59
  • 2
    @Jagd The suggested answer is better seperation of concerns. However you can set as many textboxes on this validation as well. just add the PreviewTextInput="NumberValidationTextBox". (just like the other answer!) – Revils Feb 24 '17 at 06:58
  • A way to set the event programmatically is: (textBox.PreviewTextInput += NumberValidationTextBox;) – Kreshnik Mar 12 '17 at 00:30
  • 2
    For handling +- and decimal point, use this regexp http://stackoverflow.com/a/13686481/4146066 – Hans Mar 28 '17 at 09:13
  • It still allows to copy and paste into a non-numeric value. – Serg Dec 09 '19 at 16:35
  • 1
    PreviewTextInput event handled for TextBox WPF but user can able to add space in between the entered numbers in textbox and PreviewTextInput event also not firing. – Sandeep Jadhav Dec 27 '21 at 13:20
  • 1
    For spaces between the text entered, just use trim to remove spaces. – Diego Montania Oct 05 '22 at 13:25
95

I used some of what was already here and put my own twist on it using a behavior so I don't have to propagate this code throughout a ton of Views...

public class AllowableCharactersTextBoxBehavior : Behavior<TextBox>
{
    public static readonly DependencyProperty RegularExpressionProperty =
         DependencyProperty.Register("RegularExpression", typeof(string), typeof(AllowableCharactersTextBoxBehavior),
         new FrameworkPropertyMetadata(".*"));
    public string RegularExpression
    {
        get
        {
            return (string)base.GetValue(RegularExpressionProperty);
        }
        set
        {
            base.SetValue(RegularExpressionProperty, value);
        }
    }

    public static readonly DependencyProperty MaxLengthProperty =
        DependencyProperty.Register("MaxLength", typeof(int), typeof(AllowableCharactersTextBoxBehavior),
        new FrameworkPropertyMetadata(int.MinValue));
    public int MaxLength
    {
        get
        {
            return (int)base.GetValue(MaxLengthProperty);
        }
        set
        {
            base.SetValue(MaxLengthProperty, value);
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.PreviewTextInput += OnPreviewTextInput;
        DataObject.AddPastingHandler(AssociatedObject, OnPaste);
    }

    private void OnPaste(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(DataFormats.Text))
        {
            string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));

            if (!IsValid(text, true))
            {
                e.CancelCommand();
            }
        }
        else
        {
            e.CancelCommand();
        }
    }

    void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
    {
        e.Handled = !IsValid(e.Text, false);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
        DataObject.RemovePastingHandler(AssociatedObject, OnPaste);
    }

    private bool IsValid(string newText, bool paste)
    {
        return !ExceedsMaxLength(newText, paste) && Regex.IsMatch(newText, RegularExpression);
    }

    private bool ExceedsMaxLength(string newText, bool paste)
    {
        if (MaxLength == 0) return false;

        return LengthOfModifiedText(newText, paste) > MaxLength;
    }

    private int LengthOfModifiedText(string newText, bool paste)
    {
        var countOfSelectedChars = this.AssociatedObject.SelectedText.Length;
        var caretIndex = this.AssociatedObject.CaretIndex;
        string text = this.AssociatedObject.Text;

        if (countOfSelectedChars > 0 || paste)
        {
            text = text.Remove(caretIndex, countOfSelectedChars);
            return text.Length + newText.Length;
        }
        else
        {
            var insert = Keyboard.IsKeyToggled(Key.Insert);

            return insert && caretIndex < text.Length ? text.Length : text.Length + newText.Length;
        }
    }
}

Here is the relevant view code:

<TextBox MaxLength="50" TextWrapping="Wrap" MaxWidth="150" Margin="4"
 Text="{Binding Path=FileNameToPublish}" >
     <interactivity:Interaction.Behaviors>
         <v:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9.\-]+$" MaxLength="50" />
     </interactivity:Interaction.Behaviors>
</TextBox>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Wil P
  • 3,341
  • 1
  • 20
  • 20
  • 1
    Inspired by this awesome solution, I implemented some improvements. Please check it out below in the thread. – Alex Klaus Oct 10 '13 at 04:04
  • 2
    Hi. I know this is a bit late but I'm trying to implement this but I keep getting errors. I'm guessing I'm missing some references. Are there any that are meant to be typed in other than the defaults after you created a class? – Offer May 29 '14 at 12:56
  • 1
    @Offer Yes, make sure to include xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity" at the top of your xaml window. – Ebsan Oct 27 '14 at 04:36
  • Expression is now obsolete. While this approach is clean,it's using code that is no longer being maintained. – Robert Baker Jan 27 '15 at 07:31
  • I like the idea here, but it failed when evaluating the entire string (e.g. 100.00.000 would work. Using a more complex regex (e.g. ^\$?(\d{1,3},?(\d{3},?)*\d{3}(.\d{0,3})?|\d{1,3}(.\d{0})|\d{1,3}(.\d{1})|\d{1,3}(.\d{2})?)$$) would fail when typing a period. Still working on editing the code to work. – Rogala Jan 28 '15 at 23:38
  • 1
    So if you edit the IsValid function to return !ExceedsMaxLength(newText, paste) && Regex.IsMatch(String.Concat(this.AssociatedObject.Text, newText), RegularExpression); then this will evaluate the entire string. Btw - Love this option with the behaviors!! – Rogala Jan 28 '15 at 23:44
61

This is an improved solution of WilPs answer. My improvements are:

  • Improved behaviour on Del and Backspace buttons
  • Added EmptyValue property, if empty string is inappropriate
  • Fixed some minor typos
/// <summary>
///     Regular expression for Textbox with properties: 
///         <see cref="RegularExpression"/>, 
///         <see cref="MaxLength"/>,
///         <see cref="EmptyValue"/>.
/// </summary>
public class TextBoxInputRegExBehaviour : Behavior<TextBox>
{
    #region DependencyProperties
    public static readonly DependencyProperty RegularExpressionProperty =
        DependencyProperty.Register("RegularExpression", typeof(string), typeof(TextBoxInputRegExBehaviour), new FrameworkPropertyMetadata(".*"));

    public string RegularExpression
    {
        get { return (string)GetValue(RegularExpressionProperty); }
        set { SetValue(RegularExpressionProperty, value); }
    }

    public static readonly DependencyProperty MaxLengthProperty =
        DependencyProperty.Register("MaxLength", typeof(int), typeof(TextBoxInputRegExBehaviour),
                                        new FrameworkPropertyMetadata(int.MinValue));

    public int MaxLength
    {
        get { return (int)GetValue(MaxLengthProperty); }
        set { SetValue(MaxLengthProperty, value); }
    }

    public static readonly DependencyProperty EmptyValueProperty =
        DependencyProperty.Register("EmptyValue", typeof(string), typeof(TextBoxInputRegExBehaviour), null);

    public string EmptyValue
    {
        get { return (string)GetValue(EmptyValueProperty); }
        set { SetValue(EmptyValueProperty, value); }
    }
    #endregion

    /// <summary>
    ///     Attach our behaviour. Add event handlers
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.PreviewTextInput += PreviewTextInputHandler;
        AssociatedObject.PreviewKeyDown += PreviewKeyDownHandler;
        DataObject.AddPastingHandler(AssociatedObject, PastingHandler);
    }

    /// <summary>
    ///     Deattach our behaviour. remove event handlers
    /// </summary>
    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.PreviewTextInput -= PreviewTextInputHandler;
        AssociatedObject.PreviewKeyDown -= PreviewKeyDownHandler;
        DataObject.RemovePastingHandler(AssociatedObject, PastingHandler);
    }

    #region Event handlers [PRIVATE] --------------------------------------

    void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
    {
        string text;
        if (this.AssociatedObject.Text.Length < this.AssociatedObject.CaretIndex)
            text = this.AssociatedObject.Text;
        else
        {
            //  Remaining text after removing selected text.
            string remainingTextAfterRemoveSelection;

            text = TreatSelectedText(out remainingTextAfterRemoveSelection)
                ? remainingTextAfterRemoveSelection.Insert(AssociatedObject.SelectionStart, e.Text)
                : AssociatedObject.Text.Insert(this.AssociatedObject.CaretIndex, e.Text);
        }

        e.Handled = !ValidateText(text);
    }

    /// <summary>
    ///     PreviewKeyDown event handler
    /// </summary>
    void PreviewKeyDownHandler(object sender, KeyEventArgs e)
    {
        if (string.IsNullOrEmpty(this.EmptyValue))
            return;

        string text = null;

        // Handle the Backspace key
        if (e.Key == Key.Back)
        {
            if (!this.TreatSelectedText(out text))
            {
                if (AssociatedObject.SelectionStart > 0)
                    text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart - 1, 1);
            }
        }
        // Handle the Delete key
        else if (e.Key == Key.Delete)
        {
            // If text was selected, delete it
            if (!this.TreatSelectedText(out text) && this.AssociatedObject.Text.Length > AssociatedObject.SelectionStart)
            {
                // Otherwise delete next symbol
                text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, 1);
            }
        }

        if (text == string.Empty)
        {
            this.AssociatedObject.Text = this.EmptyValue;
            if (e.Key == Key.Back)
                AssociatedObject.SelectionStart++;
            e.Handled = true;
        }
    }

    private void PastingHandler(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(DataFormats.Text))
        {
            string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));

            if (!ValidateText(text))
                e.CancelCommand();
        }
        else
            e.CancelCommand();
    }
    #endregion Event handlers [PRIVATE] -----------------------------------

    #region Auxiliary methods [PRIVATE] -----------------------------------

    /// <summary>
    ///     Validate certain text by our regular expression and text length conditions
    /// </summary>
    /// <param name="text"> Text for validation </param>
    /// <returns> True - valid, False - invalid </returns>
    private bool ValidateText(string text)
    {
        return (new Regex(this.RegularExpression, RegexOptions.IgnoreCase)).IsMatch(text) && (MaxLength == int.MinValue || text.Length <= MaxLength);
    }

    /// <summary>
    ///     Handle text selection
    /// </summary>
    /// <returns>true if the character was successfully removed; otherwise, false. </returns>
    private bool TreatSelectedText(out string text)
    {
        text = null;
        if (AssociatedObject.SelectionLength <= 0) 
            return false;

        var length = this.AssociatedObject.Text.Length;
        if (AssociatedObject.SelectionStart >= length)
            return true;

        if (AssociatedObject.SelectionStart + AssociatedObject.SelectionLength >= length)
            AssociatedObject.SelectionLength = length - AssociatedObject.SelectionStart;

        text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, AssociatedObject.SelectionLength);
        return true;
    }
    #endregion Auxiliary methods [PRIVATE] --------------------------------
}

Usage is pretty straightforward:

<i:Interaction.Behaviors>
    <behaviours:TextBoxInputRegExBehaviour RegularExpression="^\d+$" MaxLength="9" EmptyValue="0" />
</i:Interaction.Behaviors>
Community
  • 1
  • 1
Alex Klaus
  • 8,168
  • 8
  • 71
  • 87
  • 2
    This solution is pretty better. But you've made a small mistake: When not setting the `MaxLength` your conditon `(this.MaxLength == 0 || text.Length <= this.MaxLength)` returns always `false` when testing the new text. This should better be `(this.MaxLength == int.MinValue || text.Length <= this.MaxLength)` since you set `int.MinValue` as default value for `MaxLength`. – Christoph Meißner Feb 24 '17 at 09:15
  • 1
    Thank you @Christoph, yes, you're right. I've modified my answer. – Alex Klaus Feb 25 '17 at 05:36
  • @AlexKlaus this works great until I add `UpdateSourceTrigger=PropertyChanged` to the binding. Any idea how to get this code to work when changing the `UpdateSourceTrigger` is set to `PropertyChanged`? Thank you for sharing this code. – Junior Apr 24 '18 at 21:23
42

Here is a very simple and easy way to do this using MVVM.

Bind your textBox with an integer property in the view model, and this will work like a gem ... it will even show validation when a non-integer is entered in the textbox.

XAML code:

<TextBox x:Name="contactNoTxtBox"  Text="{Binding contactNo}" />

View model code:

private long _contactNo;
public long contactNo
{
    get { return _contactNo; }
    set
    {
        if (value == _contactNo)
            return;
        _contactNo = value;
        OnPropertyChanged();
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Snziv Gupta
  • 1,016
  • 1
  • 10
  • 21
  • But the question contained *"I'm looking to accept digits and the decimal point"*. Is the decimal point accepted for this answer? – Peter Mortensen May 18 '18 at 10:23
  • I tried changing `long` to `float`, but it didn't work quite right with immediate validation. I added `UpdateSourceTrigger="PropertyChanged"` to the binding, so it would perform the validation as each character was typed, and could no longer type a '.' in the TextBox unless there was an illegal character present (had to type "1x.234" then delete the 'x'). It also feels a bit sluggish in this mode. This appears to use `System.Number.ParseSingle()` to do the work, so it accepts a variety of notations. – fadden Jun 28 '19 at 20:53
  • @wolle probably not getting up-voted because it does not explain how the validation works. – Paul McCarthy Dec 16 '19 at 10:21
  • I had a nullable property for my backing property. When the user deletes all of the text in the text box, it shows the textbox in red, but the backing property was not changed. So I need a different or modified solution. – John Foll Aug 31 '21 at 19:06
  • @JohnFoll this shouldn't be an issue if define a `TargetNullValue` e.g.: `` Don't forget the system namespace `xmlns:sys="clr-namespace:System;assembly=System.Runtime` – kalenderdose Apr 20 '22 at 15:18
32

Here I have a simple solution inspired by Ray's answer. This should be sufficient to identify any form of number.

This solution can also be easily modified if you want only positive numbers, integer values or values accurate to a maximum number of decimal places, etc.


As suggested in Ray's answer, you need to first add a PreviewTextInput event:

<TextBox PreviewTextInput="TextBox_OnPreviewTextInput"/>

Then put the following in the code behind:

private void TextBox_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
    var textBox = sender as TextBox;
    // Use SelectionStart property to find the caret position.
    // Insert the previewed text into the existing text in the textbox.
    var fullText = textBox.Text.Insert(textBox.SelectionStart, e.Text);

    double val;
    // If parsing is successful, set Handled to false
    e.Handled = !double.TryParse(fullText, out val);
}

To invalid whitespace, we can add NumberStyles:

using System.Globalization;

private void TextBox_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
    var textBox = sender as TextBox;
    // Use SelectionStart property to find the caret position.
    // Insert the previewed text into the existing text in the textbox.
    var fullText = textBox.Text.Insert(textBox.SelectionStart, e.Text);

    double val;
    // If parsing is successful, set Handled to false
    e.Handled = !double.TryParse(fullText, 
                                 NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, 
                                 CultureInfo.InvariantCulture,
                                 out val);
}
Anthony
  • 3,595
  • 2
  • 29
  • 38
  • 6
    i like this answer alot, simple and effective + – Pulle Apr 17 '18 at 08:51
  • god and simple but its ugly that it allows spaces – Momo Feb 14 '19 at 10:26
  • 4
    This still allows someone to just paste a string into the textbox – FCin Feb 21 '19 at 15:08
  • 1
    I managed to work around the whitespace problem by getting rid of them at the start of my `TextChanged` event's handler method. Probably not the most elegant solution, but works. – z33k Nov 09 '20 at 11:53
  • 1
    @z33k Updated the answer to catch whitespace. It might be simpler than yours. – Anthony Nov 10 '20 at 12:31
  • Nice. Is missing a coma after `CultureInfo.InvariantCulture` though. – z33k Nov 10 '20 at 12:34
  • @z33k nice catch. fixed it – Anthony Nov 10 '20 at 12:34
  • It still accepts whitespaces.... – PeterPazmandi Sep 16 '21 at 09:54
  • @inspire_coding Hi, I don't have C# setup right now to retest the code, but to allow whitespace when using NumberStyles, you have to specify `NumberStyles.AllowLeadingWhite` and `NumberStylesAllowTrailingWhite`. Whitespace in between digits will cause `double.TryParse` to fail immediately. Could you elaborate on your comment and show me an example please? – Anthony Sep 17 '21 at 10:35
  • When I pushed the space key, then it was visible in the TextBox. I have binded it to a property, and when the code reached the double.TryParse, then it could parse because it wasn't there. But if I entered a new digit/letter after the spacec, then it showed me the whitesapces as well. – PeterPazmandi Sep 21 '21 at 12:55
29

Add in a VALIDATION RULE so that when the text changes, check to determine if the data is numeric, and if it is, allows processing to continue, and if it is not, prompts the user that only numeric data is accepted in that field.

Read more in Validation in Windows Presentation Foundation

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Stephen Wrighton
  • 36,783
  • 6
  • 67
  • 86
  • 6
    This is not really an answer for SO standards. – Robert Baker Jan 27 '15 at 07:37
  • 1
    This seems to be the .net way of doing it. – Telemat Oct 15 '15 at 02:28
  • 1
    The **is** the *correct* answer: validation should be at viene model or model level. Moreover you can simply bind to a numeric type like `double` and that's already providing you with a standard validation. –  Feb 27 '17 at 08:45
26

The Extented WPF Toolkit has one: NumericUpDown enter image description here

  • I've tried this control but it gives problems when using the spinner with UpdateSourceTrigger=PropertyChanged and is in general difficult for the user to enter scientific notation. – Menno Deij - van Rijswijk Feb 13 '13 at 12:28
  • 5
    Please notice that `NumericUpDown` is now obsolete. you can use `DecimalUpDown` from the updated toolkit [Extended WPF Toolkit™ Community Edition](http://wpftoolkit.codeplex.com/wikipage?title=NumericUpDown&referringTitle=Home) – itsho Feb 05 '15 at 19:58
24

Could also simply implement a validation rule and apply it to the TextBox:

  <TextBox>
    <TextBox.Text>
      <Binding Path="OnyDigitInput" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
        <Binding.ValidationRules>
          <conv:OnlyDigitsValidationRule />
        </Binding.ValidationRules>
      </Binding>
    </TextBox.Text>

With the implementation of the rule as follow (using the same Regex as proposed in other answers):

public class OnlyDigitsValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        var validationResult = new ValidationResult(true, null);

        if(value != null)
        {
            if (!string.IsNullOrEmpty(value.ToString()))
            {
                var regex = new Regex("[^0-9.-]+"); //regex that matches disallowed text
                var parsingOk = !regex.IsMatch(value.ToString());
                if (!parsingOk)
                {
                    validationResult = new ValidationResult(false, "Illegal Characters, Please Enter Numeric Value");
                }
            }
        }

        return validationResult;
    }
}
goul
  • 813
  • 1
  • 13
  • 32
  • 1
    If you wanna input decimal digits, do not return "valid" when the Text ends in "." Please refer to http://stackoverflow.com/a/27838893/417939 – YantingChen Sep 14 '16 at 01:57
12

Another approach will be using an attached behavior, I implemented my custom TextBoxHelper class, which can be used on textboxes all over my project. Because I figured that subscribing to the events for every textboxes and in every individual XAML file for this purpose can be time consuming.

The TextBoxHelper class I implemented has these features:

  • Filtering and accepting only numbers in Double, Int, Uint and Natural format
  • Filtering and accepting only Even or Odd numbers
  • Handling paste event handler to prevent pasting invalid text into our numeric textboxes
  • Can set a Default Value which will be used to prevent invalid data as the last shot by subscribing to the textboxes TextChanged event

Here is the implementation of TextBoxHelper class:

public static class TextBoxHelper
{
    #region Enum Declarations

    public enum NumericFormat
    {
        Double,
        Int,
        Uint,
        Natural
    }

    public enum EvenOddConstraint
    {
        All,
        OnlyEven,
        OnlyOdd
    }

    #endregion

    #region Dependency Properties & CLR Wrappers

    public static readonly DependencyProperty OnlyNumericProperty =
        DependencyProperty.RegisterAttached("OnlyNumeric", typeof(NumericFormat?), typeof(TextBoxHelper),
            new PropertyMetadata(null, DependencyPropertiesChanged));
    public static void SetOnlyNumeric(TextBox element, NumericFormat value) =>
        element.SetValue(OnlyNumericProperty, value);
    public static NumericFormat GetOnlyNumeric(TextBox element) =>
        (NumericFormat) element.GetValue(OnlyNumericProperty);


    public static readonly DependencyProperty DefaultValueProperty =
        DependencyProperty.RegisterAttached("DefaultValue", typeof(string), typeof(TextBoxHelper),
            new PropertyMetadata(null, DependencyPropertiesChanged));
    public static void SetDefaultValue(TextBox element, string value) =>
        element.SetValue(DefaultValueProperty, value);
    public static string GetDefaultValue(TextBox element) => (string) element.GetValue(DefaultValueProperty);


    public static readonly DependencyProperty EvenOddConstraintProperty =
        DependencyProperty.RegisterAttached("EvenOddConstraint", typeof(EvenOddConstraint), typeof(TextBoxHelper),
            new PropertyMetadata(EvenOddConstraint.All, DependencyPropertiesChanged));
    public static void SetEvenOddConstraint(TextBox element, EvenOddConstraint value) =>
        element.SetValue(EvenOddConstraintProperty, value);
    public static EvenOddConstraint GetEvenOddConstraint(TextBox element) =>
        (EvenOddConstraint)element.GetValue(EvenOddConstraintProperty);

    #endregion

    #region Dependency Properties Methods

    private static void DependencyPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is TextBox textBox))
            throw new Exception("Attached property must be used with TextBox.");

        switch (e.Property.Name)
        {
            case "OnlyNumeric":
            {
                var castedValue = (NumericFormat?) e.NewValue;

                if (castedValue.HasValue)
                {
                    textBox.PreviewTextInput += TextBox_PreviewTextInput;
                    DataObject.AddPastingHandler(textBox, TextBox_PasteEventHandler);
                }
                else
                {
                    textBox.PreviewTextInput -= TextBox_PreviewTextInput;
                    DataObject.RemovePastingHandler(textBox, TextBox_PasteEventHandler);
                }

                break;
            }

            case "DefaultValue":
            {
                var castedValue = (string) e.NewValue;

                if (castedValue != null)
                {
                    textBox.TextChanged += TextBox_TextChanged;
                }
                else
                {
                    textBox.TextChanged -= TextBox_TextChanged;
                }

                break;
            }
        }
    }

    #endregion

    private static void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        var textBox = (TextBox)sender;

        string newText;

        if (textBox.SelectionLength == 0)
        {
            newText = textBox.Text.Insert(textBox.SelectionStart, e.Text);
        }
        else
        {
            var textAfterDelete = textBox.Text.Remove(textBox.SelectionStart, textBox.SelectionLength);

            newText = textAfterDelete.Insert(textBox.SelectionStart, e.Text);
        }

        var evenOddConstraint = GetEvenOddConstraint(textBox);

        switch (GetOnlyNumeric(textBox))
        {
            case NumericFormat.Double:
            {
                if (double.TryParse(newText, out double number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;
                    }
                }
                else
                    e.Handled = true;

                break;
            }

            case NumericFormat.Int:
            {
                if (int.TryParse(newText, out int number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;
                    }
                }
                else
                    e.Handled = true;

                break;
            }

            case NumericFormat.Uint:
            {
                if (uint.TryParse(newText, out uint number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;
                    }
                }
                else
                    e.Handled = true;

                break;
            }

            case NumericFormat.Natural:
            {
                if (uint.TryParse(newText, out uint number))
                {
                    if (number == 0)
                        e.Handled = true;
                    else
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.Handled = true;
                                else
                                    e.Handled = false;

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.Handled = true;
                                else
                                    e.Handled = false;

                                break;
                        }
                    }
                }
                else
                    e.Handled = true;

                break;
            }
        }
    }
    
    private static void TextBox_PasteEventHandler(object sender, DataObjectPastingEventArgs e)
    {
        var textBox = (TextBox)sender;

        if (e.DataObject.GetDataPresent(typeof(string)))
        {
            var clipboardText = (string) e.DataObject.GetData(typeof(string));

            var newText = textBox.Text.Insert(textBox.SelectionStart, clipboardText);

            var evenOddConstraint = GetEvenOddConstraint(textBox);

            switch (GetOnlyNumeric(textBox))
            {
                case NumericFormat.Double:
                {
                    if (double.TryParse(newText, out double number))
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.CancelCommand();

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.CancelCommand();
                                
                                break;
                        }
                    }
                    else
                        e.CancelCommand();

                    break;
                }

                case NumericFormat.Int:
                {
                    if (int.TryParse(newText, out int number))
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.CancelCommand();

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.CancelCommand();


                                break;
                        }
                    }
                    else
                        e.CancelCommand();

                    break;
                }

                case NumericFormat.Uint:
                {
                    if (uint.TryParse(newText, out uint number))
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.CancelCommand();

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.CancelCommand();


                                break;
                        }
                    }
                    else
                        e.CancelCommand();

                    break;
                }
                    
                case NumericFormat.Natural:
                {
                    if (uint.TryParse(newText, out uint number))
                    {
                        if (number == 0)
                            e.CancelCommand();
                        else
                        {
                            switch (evenOddConstraint)
                            {
                                case EvenOddConstraint.OnlyEven:

                                    if (number % 2 != 0)
                                        e.CancelCommand();

                                    break;

                                case EvenOddConstraint.OnlyOdd:

                                    if (number % 2 == 0)
                                        e.CancelCommand();

                                    break;
                            }
                        }
                    }
                    else
                    {
                        e.CancelCommand();
                    }
                    
                    break;
                }
            }
        }
        else
        {
            e.CancelCommand();
        }
    }
    
    private static void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        var textBox = (TextBox)sender;
        
        var defaultValue = GetDefaultValue(textBox);

        var evenOddConstraint = GetEvenOddConstraint(textBox);

        switch (GetOnlyNumeric(textBox))
        {
            case NumericFormat.Double:
            {
                if (double.TryParse(textBox.Text, out double number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                textBox.Text = defaultValue;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                textBox.Text = defaultValue;

                            break;
                    }
                }
                else
                    textBox.Text = defaultValue;

                break;
            }

            case NumericFormat.Int:
            {
                if (int.TryParse(textBox.Text, out int number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                textBox.Text = defaultValue;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                textBox.Text = defaultValue;

                            break;
                    }
                }
                else
                    textBox.Text = defaultValue;

                break;
            }

            case NumericFormat.Uint:
            {
                if (uint.TryParse(textBox.Text, out uint number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                textBox.Text = defaultValue;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                textBox.Text = defaultValue;

                            break;
                    }
                }
                else
                    textBox.Text = defaultValue;

                break;
            }

            case NumericFormat.Natural:
            {
                if (uint.TryParse(textBox.Text, out uint number))
                {
                    if(number == 0)
                        textBox.Text = defaultValue;
                    else
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    textBox.Text = defaultValue;

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    textBox.Text = defaultValue;

                                break;
                        }
                    }
                }
                else
                {
                    textBox.Text = defaultValue;
                }

                break;
            }
        }
    }
}

And here is some example of its easy usage:

<TextBox viewHelpers:TextBoxHelper.OnlyNumeric="Double"
         viewHelpers:TextBoxHelper.DefaultValue="1"/>

Or

<TextBox viewHelpers:TextBoxHelper.OnlyNumeric="Natural"
         viewHelpers:TextBoxHelper.DefaultValue="3"
         viewHelpers:TextBoxHelper.EvenOddConstraint="OnlyOdd"/>

Note that my TextBoxHelper resides in the viewHelpers xmlns alias.

I hope that this implementation eases someone else's work :)

Amir Mahdi Nassiri
  • 1,190
  • 13
  • 21
  • 1
    This is great when using a textbox inside a DataTemplate, thanks! – NucS Oct 03 '18 at 11:00
  • 4
    Great answer but I find your methods difficult to read. Probably you should break them down into smaller ones. See [What is the ideal length of a method for you?](https://softwareengineering.stackexchange.com/questions/133404/what-is-the-ideal-length-of-a-method-for-you) – Anthony Jan 23 '19 at 22:01
  • Thank you for the constructive feedback @Anthony – Amir Mahdi Nassiri Dec 09 '19 at 15:51
  • 1
    This is excellent. I also agree with @Anthony that your long method should be broken down to multiple methods. Even better, multiple classes for the types of textboxes. – gabnaim Jul 01 '20 at 01:23
8

I allowed numeric keypad numbers and backspace:

    private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        int key = (int)e.Key;

        e.Handled = !(key >= 34 && key <= 43 || 
                      key >= 74 && key <= 83 || 
                      key == 2);
    }
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Hamzeh Soboh
  • 7,572
  • 5
  • 43
  • 54
  • 10
    I'd recommend using the enum values instead of **Magic Numbers**: `var keyEnum = (System.Windows.Input.Key) e.Key; e.Handled = !(keyEnum >= System.Windows.Input.Key.D0 && keyEnum <= System.Windows.Input.Key.D9 || keyEnum >= System.Windows.Input.Key.NumPad0 && keyEnum <= System.Windows.Input.Key.NumPad9 || keyEnum == System.Windows.Input.Key.Back);` – itsho Feb 04 '16 at 04:54
6

This is the only code needed:

void MyTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    e.Handled = new Regex("[^0-9]+").IsMatch(e.Text);
}

This only allows numbers to be inputted into the text box.

To allow a decimal point or minus sign, you can change the regular expression to [^0-9.-]+.

Danny Beckett
  • 20,529
  • 24
  • 107
  • 134
  • 2
    Very good solution except for one minor nitpick: It will not stop you from entering whitespaces, since those do not trigger the PreviewTextInput event. – Tim Pohlmann Feb 04 '16 at 07:51
  • 1
    Backspace does not fire it as well. – wingerse Apr 28 '16 at 19:23
  • Or paste. I don't think these are minor nitpicks btw. The OP is quite simply wrong to say this is "all" that is required. You need to do more than this. – PeteH May 05 '22 at 15:25
6

In Windows Forms it was easy; you can add an event for KeyPress and everything works easily. However, in WPF that event isn't there. But there is a much easier way for it.

The WPF TextBox has the TextChanged event which is general for everything. It includes pasting, typing and whatever that can come up to your mind.

So you can do something like this:

XAML:

<TextBox name="txtBox1" ... TextChanged="TextBox_TextChanged"/>

CODE BEHIND:

private void TextBox_TextChanged(object sender, TextChangedEventArgs e) {
    string s = Regex.Replace(((TextBox)sender).Text, @"[^\d.]", "");
    ((TextBox)sender).Text = s;
}

This also accepts . , if you don't want it, just remove it from the regex statement to be @[^\d].

Note: This event can be used on many TextBox elements as it uses the sender object's Text. You only write the event once and can use it for multiple TextBox elements.

Everyone
  • 1,751
  • 13
  • 36
6

If you do not want to write a lot of code to do a basic function (I don't know why people make long methods) you can just do this:

  1. Add namespace:

    using System.Text.RegularExpressions;
    
  2. In XAML, set a TextChanged property:

    <TextBox x:Name="txt1" TextChanged="txt1_TextChanged"/>
    
  3. In WPF under txt1_TextChanged method, add Regex.Replace:

    private void txt1_TextChanged(object sender, TextChangedEventArgs e)
    {
        txt1.Text = Regex.Replace(txt1.Text, "[^0-9]+", "");
    }
    
iato
  • 356
  • 5
  • 16
  • 2
    It is much cleaner to use a Behavior or an AttachedProperty. No code-behind fiddeling in every view/for every textbox – Sir Rufo May 31 '17 at 12:07
  • May work but is ugly as the carrot will jump in front position of the textbox – Momo Feb 14 '19 at 10:26
6

I will assume that:

  1. Your TextBox for which you want to allow numeric input only has its Text property initially set to some valid number value (for example, 2.7172).

  2. Your Textbox is a child of your main window

  3. Your main window is of class Window1

  4. Your TextBox name is numericTB

Basic idea:

  1. Add: private string previousText; to your main window class (Window1)

  2. Add: previousText = numericTB.Text; to your main window constructor

  3. Create a handler for the numericTB.TextChanged event to be something like this:

    private void numericTB_TextChanged(object sender, TextChangedEventArgs e)
    {
        double num = 0;
        bool success = double.TryParse(((TextBox)sender).Text, out num);
        if (success & num >= 0)
            previousText = ((TextBox)sender).Text;
        else
            ((TextBox)sender).Text = previousText;
    }
    

This will keep setting previousText to numericTB.Text as long as it is valid, and set numericTB.Text to its last valid value if the user writes something that you don't like. Of course, this is just basic idea, and it is just "idiot resistant", not "idiot proof". It doesn't handle the case in which the user messes with spaces, for example. So here is a complete solution which I think is "idiot proof", and if I'm wrong please tell me:

  1. Content of your Window1.xaml file:

    <Window x:Class="IdiotProofNumericTextBox.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <TextBox Height="30" Width="100" Name="numericTB" TextChanged="numericTB_TextChanged"/>
        </Grid>
    </Window>
    
  2. Content of your Window.xaml.cs file:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace IdiotProofNumericTextBox
    {
        public partial class Window1 : Window
        {
            private string previousText;
    
            public Window1()
            {
                InitializeComponent();
                previousText = numericTB.Text;
            }
    
            private void numericTB_TextChanged(object sender, TextChangedEventArgs e)
            {
                if (string.IsNullOrEmpty(((TextBox)sender).Text))
                    previousText = "";
                else
                {
                    double num = 0;
                    bool success = double.TryParse(((TextBox)sender).Text, out num);
                    if (success & num >= 0)
                    {
                        ((TextBox)sender).Text.Trim();
                        previousText = ((TextBox)sender).Text;
                    }
                    else
                    {
                        ((TextBox)sender).Text = previousText;
                        ((TextBox)sender).SelectionStart = ((TextBox)sender).Text.Length;
                    }
                }
            }
        }
    }
    

And that's it. If you have many TextBoxes then I recommend creating a CustomControl that inherits from TextBox, so you can wrap previousText and numericTB_TextChanged up in a separate file.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user666535
  • 69
  • 1
  • 1
5

For developers who want their text fields to accept unsigned numbers only such as socket ports and so on:

WPF

<TextBox PreviewTextInput="Port_PreviewTextInput" MaxLines="1"/>

C#

private void Port_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    e.Handled = !int.TryParse(e.Text, out int x);
}
Beyondo
  • 2,952
  • 1
  • 17
  • 42
  • 3
    Note that if you really wanna use this method with a socket port field; You'd need to check if the integer is less than or equal to `65535`. If it is greater then it is not a valid port. Also, setting the `TextBox.MaxLength` to `5` would help *either programmatically or in XAML*. – Beyondo Jul 26 '19 at 09:35
4
PreviewTextInput += (s, e) =>
{
    e.Handled = !e.Text.All(char.IsDigit);
};
Unamata Sanatarai
  • 6,475
  • 3
  • 29
  • 51
Julian Kowalczuk
  • 145
  • 1
  • 2
  • 10
  • 2
    this also won't accept dot `.`, since `e.Text` only returns the last input character and a dot will fail the `IsDigit` check. – Anthony Dec 28 '17 at 22:30
4

For those looking for a quick and very simple implementation for this type of problem using only integers and decimal, in your XAML file, add a PreviewTextInput property to your TextBoxand then in your xaml.cs file use:

private void Text_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    e.Handled = !char.IsDigit(e.Text.Last()) && !e.Text.Last() == '.';
}

It's kind of redundant to keep checking the entire string every time unless, as others have mentioned, you're doing something with scientific notation (albeit, if you're adding certain characters like 'e', a simple regex adding symbols/characters is really simple and illustrated in other answers). But for simple floating point values, this solution will suffice.

Written as a one-liner with a lambda expression:

private void Text_PreviewTextInput(object sender, TextCompositionEventArgs e) => e.Handled = !char.IsDigit(e.Text.Last() && !e.Text.Last() == '.');
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Chris
  • 2,254
  • 8
  • 22
3

We can do validation on text box changed event. The following implementation prevents keypress input other than numeric and one decimal point.

private void textBoxNumeric_TextChanged(object sender, TextChangedEventArgs e) 
{         
      TextBox textBox = sender as TextBox;         
      Int32 selectionStart = textBox.SelectionStart;         
      Int32 selectionLength = textBox.SelectionLength;         
      String newText = String.Empty;         
      int count = 0;         
      foreach (Char c in textBox.Text.ToCharArray())         
      {             
         if (Char.IsDigit(c) || Char.IsControl(c) || (c == '.' && count == 0))             
         {                 
            newText += c;                 
            if (c == '.')                     
              count += 1;             
         }         
     }         
     textBox.Text = newText;         
     textBox.SelectionStart = selectionStart <= textBox.Text.Length ? selectionStart :        textBox.Text.Length;     
} 
kumar Gouraw
  • 121
  • 1
  • 2
3
e.Handled = (int)e.Key >= 43 || (int)e.Key <= 34;

in preview keydown event of textbox.

Novice
  • 2,447
  • 3
  • 27
  • 33
3

How about this? Works well for me. Hope I didn't miss any edge cases...

MyTextBox.PreviewTextInput += (sender, args) =>
{
    if (!int.TryParse(args.Text, out _))
    {
        args.Handled = true;
    }
};

DataObject.AddPastingHandler(MyTextBox, (sender, args) =>
{
    var isUnicodeText = args.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true);
    if (!isUnicodeText)
    {
        args.CancelCommand();
    }

    var data = args.SourceDataObject.GetData(DataFormats.UnicodeText) as string;
    if (!int.TryParse(data, out _))
    {
        args.CancelCommand();
    }
});
Shahin Dohan
  • 6,149
  • 3
  • 41
  • 58
3

The best and most elegant solution to only allow integer numbers in textbox (even in some range) is:

XAML:

<TextBox PreviewTextInput="ValidationTextBox" TextChanged="TextBox_TextChanged"/>

C#:

private void ValidationTextBox(object sender, TextCompositionEventArgs e)
{
    int max = 100;

    //do not allow futher incorrect typing
    e.Handled = !(int.TryParse(((TextBox)sender).Text + e.Text, out int i) && i >= 1 && i <= max);
}

private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    int max = 100;

    if (!int.TryParse(((TextBox)sender).Text, out int j) || j < 1 || j > max)
    {
        //delete incoret input
        ((TextBox)sender).Text = "";
    }
    else
    {
        //delete leading zeros
        ((TextBox)sender).Text = j.ToString();
    }
}

You can adjust the minimal and maximal acceptable number with max (min) by switch on ((TextBox)sender).Name.

This solution does not allow leading zeros or copy pasting the input. In every scenario you will have a correct number in the textbox.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
juraj
  • 47
  • 6
  • ValidationTextBox function will not work properly in this scenario: you enter 100 and after you select whole text and type 1. In reality this should remove 100 and show 1 in the textbox but it will not allow you to do this, as your condition is checking if 1001 is greater than 100. – Vahagn Nahapetyan Apr 13 '22 at 20:21
2

Now I know this question has an accepted answer, but personally, I find it a bit confusing and I believe it should be easier than that. So I'll try to demonstrate how I got it to work as best as I can:

In Windows Forms, there's an event called KeyPress which is perfectly good for this kind of task. But that doesn't exist in WPF, so instead, we'll be using the PreviewTextInput event. Also, for the validation, I believe one can use a foreach to loop through the textbox.Text and check if it matches ;) the condition, but honestly, this is what regular expressions are for.

One more thing before we dive into the holy code. For the event to be fired, one can do two things:

  1. Use XAML to tell the program which function to call: <PreviewTextInput="textBox_PreviewTextInput/>
  2. Do it in the Loaded event of the form (which the textBox is in): textBox.PreviewTextInput += onlyNumeric;

I think the second method is better because in situations like this, you'll mostly be required to apply the same condition (regex) to more than one TextBox and you don't want to repeat yourself!.

Finally, here's how you'd do it:

private void onlyNumeric(object sender, TextCompositionEventArgs e)
{
    string onlyNumeric = @"^([0-9]+(.[0-9]+)?)$";
    Regex regex = new Regex(onlyNumeric);
    e.Handled = !regex.IsMatch(e.Text);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Amir Shabani
  • 3,857
  • 6
  • 30
  • 67
2

Here is my version of it. It's based on a base ValidatingTextBox class that just undoes what has been done if it's not "valid". It supports paste, cut, delete, backspace, +, - etc.

For 32-bit integer, there is a Int32TextBox class that just compares with an int. I have also added floating point validation classes.

public class ValidatingTextBox : TextBox
{
    private bool _inEvents;
    private string _textBefore;
    private int _selectionStart;
    private int _selectionLength;

    public event EventHandler<ValidateTextEventArgs> ValidateText;

    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
        if (_inEvents)
            return;

        _selectionStart = SelectionStart;
        _selectionLength = SelectionLength;
        _textBefore = Text;
    }

    protected override void OnTextChanged(TextChangedEventArgs e)
    {
        if (_inEvents)
            return;

        _inEvents = true;
        var ev = new ValidateTextEventArgs(Text);
        OnValidateText(this, ev);
        if (ev.Cancel)
        {
            Text = _textBefore;
            SelectionStart = _selectionStart;
            SelectionLength = _selectionLength;
        }
        _inEvents = false;
    }

    protected virtual void OnValidateText(object sender, ValidateTextEventArgs e) => ValidateText?.Invoke(this, e);
}

public class ValidateTextEventArgs : CancelEventArgs
{
    public ValidateTextEventArgs(string text) => Text = text;

    public string Text { get; }
}

public class Int32TextBox : ValidatingTextBox
{
    protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !int.TryParse(e.Text, out var value);
}

public class Int64TextBox : ValidatingTextBox
{
    protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !long.TryParse(e.Text, out var value);
}

public class DoubleTextBox : ValidatingTextBox
{
    protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !double.TryParse(e.Text, out var value);
}

public class SingleTextBox : ValidatingTextBox
{
    protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !float.TryParse(e.Text, out var value);
}

public class DecimalTextBox : ValidatingTextBox
{
    protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !decimal.TryParse(e.Text, out var value);
}

Note 1: When using WPF binding, you must make sure you use the class that fits the bound property type otherwise, it may lead to strange results.

Note 2: When using floating point classes with WPF binding, make sure the binding uses the current culture to match the TryParse method I've used.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
2

I modified Rays answer to handle the highlighted text prior to checking the regular expression. I also adjusted the regular expression to only allow for two decimal places (currency).

private static readonly Regex _regex = new Regex(@"^[0-9]\d*(\.\d{0,2})?$");
private static bool IsTextAllowed(string text)
{
    return _regex.IsMatch(text);
}

private bool IsAllowed(TextBox tb, string text)
{
    bool isAllowed = true;
    if (tb != null)
    {
        string currentText = tb.Text;
        if (!string.IsNullOrEmpty(tb.SelectedText))
            currentText = currentText.Remove(tb.CaretIndex, tb.SelectedText.Length);
        isAllowed = IsTextAllowed(currentText.Insert(tb.CaretIndex, text));
    }
    return isAllowed;
}

private void Txt_PreviewCurrencyTextInput(object sender, TextCompositionEventArgs e)
{
    e.Handled = !IsAllowed(sender as TextBox, e.Text);            
}


private void TextBoxPasting(object sender, DataObjectPastingEventArgs e)
{
    if (e.DataObject.GetDataPresent(typeof(String)))
    {
        String text = (String)e.DataObject.GetData(typeof(String));
        if (!IsAllowed(sender as TextBox, text))
            e.CancelCommand();
    }
    else
        e.CancelCommand();
}

And the xaml

<TextBox Name="Txt_Textbox" PreviewTextInput="Txt_PreviewCurrencyTextInput"  DataObject.Pasting="TextBoxPasting" />
clamchoda
  • 4,411
  • 2
  • 36
  • 74
1

I was working with an unbound box for a simple project I was working on, so I couldn't use the standard binding approach. Consequently I created a simple hack that others might find quite handy by simply extending the existing TextBox control:

namespace MyApplication.InterfaceSupport
{
    public class NumericTextBox : TextBox
    {


        public NumericTextBox() : base()
        {
            TextChanged += OnTextChanged;
        }


        public void OnTextChanged(object sender, TextChangedEventArgs changed)
        {
            if (!String.IsNullOrWhiteSpace(Text))
            {
                try
                {
                    int value = Convert.ToInt32(Text);
                }
                catch (Exception e)
                {
                    MessageBox.Show(String.Format("{0} only accepts numeric input.", Name));
                    Text = "";
                }
            }
        }


        public int? Value
        {
            set
            {
                if (value != null)
                {
                    this.Text = value.ToString();
                }
                else 
                    Text = "";
            }
            get
            {
                try
                {
                    return Convert.ToInt32(this.Text);
                }
                catch (Exception ef)
                {
                    // Not numeric.
                }
                return null;
            }
        }
    }
}

Obviously, for a floating type, you would want to parse it as a float and so on. The same principles apply.

Then in the XAML file you need to include the relevant namespace:

<UserControl x:Class="MyApplication.UserControls.UnParameterisedControl"
             [ Snip ]
             xmlns:interfaceSupport="clr-namespace:MyApplication.InterfaceSupport"
             >

After that you can use it as a regular control:

<interfaceSupport:NumericTextBox Height="23" HorizontalAlignment="Left" Margin="168,51,0,0" x:Name="NumericBox" VerticalAlignment="Top" Width="120" >
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
glenatron
  • 11,018
  • 13
  • 64
  • 112
1

After using some of the solutions here for some time, I developed my own that works well for my MVVM setup. Note that it's not as dynamic as some of the other ones in a sense of still allowing users to enter erroneous characters, but it blocks them from pressing the button and thus doing anything. This goes well with my theme of graying out buttons when actions cannot be performed.

I have a TextBox that a user must enter a number of document pages to be printed:

<TextBox Text="{Binding NumberPagesToPrint, UpdateSourceTrigger=PropertyChanged}"/>

...with this binding property:

private string _numberPagesToPrint;
public string NumberPagesToPrint
{
    get { return _numberPagesToPrint; }
    set
    {
        if (_numberPagesToPrint == value)
        {
            return;
        }

        _numberPagesToPrint = value;
        OnPropertyChanged("NumberPagesToPrint");
    }
}

I also have a button:

<Button Template="{DynamicResource CustomButton_Flat}" Content="Set"
        Command="{Binding SetNumberPagesCommand}"/>

...with this command binding:

private RelayCommand _setNumberPagesCommand;
public ICommand SetNumberPagesCommand
{
    get
    {
        if (_setNumberPagesCommand == null)
        {
            int num;
            _setNumberPagesCommand = new RelayCommand(param => SetNumberOfPages(),
                () => Int32.TryParse(NumberPagesToPrint, out num));
        }

        return _setNumberPagesCommand;
    }
}

And then there's the method of SetNumberOfPages(), but it's unimportant for this topic. It works well in my case because I don't have to add any code into the View's code-behind file and it allows me to control behavior using the Command property.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
B.K.
  • 9,982
  • 10
  • 73
  • 105
1

When checking a number value you can use the VisualBasic.IsNumeric function.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ahmed Fwela
  • 955
  • 1
  • 13
  • 32
1

In the WPF application, you can handle this by handling TextChanged event:

void arsDigitTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
    Regex regex = new Regex("[^0-9]+");
    bool handle = regex.IsMatch(this.Text);
    if (handle)
    {
        StringBuilder dd = new StringBuilder();
        int i = -1;
        int cursor = -1;
        foreach (char item in this.Text)
        {
            i++;
            if (char.IsDigit(item))
                dd.Append(item);
            else if(cursor == -1)
                cursor = i;
        }
        this.Text = dd.ToString();

        if (i == -1)
            this.SelectionStart = this.Text.Length;
        else
            this.SelectionStart = cursor;
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mehdi Khademloo
  • 2,754
  • 2
  • 20
  • 40
1

Here is a library for numeric input in WPF

It has properties like NumberStyles and RegexPatternfor validation.

Subclasses WPF TextBox

NuGet

Johan Larsson
  • 17,112
  • 9
  • 74
  • 88
1

The following code creates a control which you will be able to use like a normal TextBox however it will only take a positive double as an input:

In the XAML you'll be able to use this control like so:

<local:UnsignedDoubleBox/>

In the C# code add the following inside the current namespace:

public class UnsignedDoubleBox : TextBox
    {
        public UnsignedDoubleBox()
        {
            this.PreviewTextInput += defaultPreviewTextInput;
            DataObject.AddPastingHandler(this, defaultTextBoxPasting);
        }

        private bool IsTextAllowed(TextBox textBox, String text)
        {
            //source: https://stackoverflow.com/questions/23397195/in-wpf-does-previewtextinput-always-give-a-single-character-only#comment89374810_23406386
            String newText = textBox.Text.Insert(textBox.CaretIndex, text);
            double res;
            return double.TryParse(newText, out res) && res >= 0;
        }
        //source: https://stackoverflow.com/a/1268648/13093413
        private void defaultTextBoxPasting(object sender, DataObjectPastingEventArgs e)
        {
            if (e.DataObject.GetDataPresent(typeof(String)))
            {
                String text = (String)e.DataObject.GetData(typeof(String));

                if (!IsTextAllowed((TextBox)sender, text))
                {
                    e.CancelCommand();
                }
            }
            else
            {
                e.CancelCommand();
            }
        }

        private void defaultPreviewTextInput(object sender, TextCompositionEventArgs e)
        {

            if (IsTextAllowed((TextBox)sender, e.Text))
            {
                e.Handled = false;
            }
            else
            {
                e.Handled = true;
            }
        }

    }
Trake Vital
  • 1,019
  • 10
  • 18
1

Use:

Private Sub DetailTextBox_PreviewTextInput( _
  ByVal sender As Object, _
  ByVal e As System.Windows.Input.TextCompositionEventArgs) _
  Handles DetailTextBox.PreviewTextInput

    If _IsANumber Then
        If Not Char.IsNumber(e.Text) Then
            e.Handled = True
        End If
    End If
End Sub
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Johnny
  • 27
  • 1
0

This is what I would use to get a WPF textbox that accept digits and the decimal point:

class numericTextBox : TextBox
{
    protected override void OnKeyDown(KeyEventArgs e)
    {
        bool b = false;
        switch (e.Key)
        {
            case Key.Back: b = true; break;
            case Key.D0: b = true; break;
            case Key.D1: b = true; break;
            case Key.D2: b = true; break;
            case Key.D3: b = true; break;
            case Key.D4: b = true; break;
            case Key.D5: b = true; break;
            case Key.D6: b = true; break;
            case Key.D7: b = true; break;
            case Key.D8: b = true; break;
            case Key.D9: b = true; break;
            case Key.OemPeriod: b = true; break;
        }
        if (b == false)
        {
            e.Handled = true;
        }
        base.OnKeyDown(e);
    }
}

Put the code in a new class file, add

using System.Windows.Controls;
using System.Windows.Input;

at the top of the file and build the solution. The numericTextBox control will then appear at the top of the toolbox.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
matsolof
  • 245
  • 2
  • 6
  • 1
    See the earlier MUCH easier solution using NumberValidationTextBox and regular expressions. This is ridiculous. – Scott Shaw-Smith Mar 10 '16 at 18:59
  • @ScottShaw-Smith Maybe the accepted solution is less code, but it isn't faster than this. There are always some projects that require a lot of processing power rather than using regex. – Beyondo Jul 26 '19 at 02:57