Numeric Data Entry in WPF

C#Wpf

C# Problem Overview


How are you handling the entry of numeric values in WPF applications?

Without a NumericUpDown control, I've been using a TextBox and handling its PreviewKeyDown event with the code below, but it's pretty ugly.

Has anyone found a more graceful way to get numeric data from the user without relying on a third-party control?

private void NumericEditPreviewKeyDown(object sender, KeyEventArgs e)
{
    bool isNumPadNumeric = (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9) || e.Key == Key.Decimal;
    bool isNumeric = (e.Key >= Key.D0 && e.Key <= Key.D9) || e.Key == Key.OemPeriod;

    if ((isNumeric || isNumPadNumeric) && Keyboard.Modifiers != ModifierKeys.None)
    {
        e.Handled = true;
        return;
    }

    bool isControl = ((Keyboard.Modifiers != ModifierKeys.None && Keyboard.Modifiers != ModifierKeys.Shift)
        || e.Key == Key.Back || e.Key == Key.Delete || e.Key == Key.Insert
        || e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Up
        || e.Key == Key.Tab
        || e.Key == Key.PageDown || e.Key == Key.PageUp
        || e.Key == Key.Enter || e.Key == Key.Return || e.Key == Key.Escape
        || e.Key == Key.Home || e.Key == Key.End);

    e.Handled = !isControl && !isNumeric && !isNumPadNumeric;
}

C# Solutions


Solution 1 - C#

How about:

protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
{
	e.Handled = !AreAllValidNumericChars(e.Text);
	base.OnPreviewTextInput(e);
}

private bool AreAllValidNumericChars(string str)
{
	foreach(char c in str)
	{
		if(!Char.IsNumber(c)) return false;
	}

	return true;
}

Solution 2 - C#

This is how I do it. It uses a regular expression to check if the text that will be in the box is numeric or not.

Regex NumEx = new Regex(@"^-?\d*\.?\d*$");

private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    if (sender is TextBox)
    {
        string text = (sender as TextBox).Text + e.Text;
        e.Handled = !NumEx.IsMatch(text);
    }
    else
        throw new NotImplementedException("TextBox_PreviewTextInput Can only Handle TextBoxes");
}

There is now a much better way to do this in WPF and Silverlight. If your control is bound to a property, all you have to do is change your binding statement a bit. Use the following for your binding:

<TextBox Text="{Binding Number, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}"/>

Note that you can use this on custom properties too, all you have to do is throw an exception if the value in the box is invalid and the control will get highlighted with a red border. If you click on the upper right of the red border then the exception message will pop up.

Solution 3 - C#

I've been using an attached property to allow the user to use the up and down keys to change the values in the text box. To use it, you just use

<TextBox local:TextBoxNumbers.SingleDelta="1">100</TextBox>

This doesn't actually address the validation issues that are referred to in this question, but it addresses what I do about not having a numeric up/down control. Using it for a little bit, I think I might actually like it better than the old numeric up/down control.

The code isn't perfect, but it handles the cases I needed it to handle:

  • Up arrow, Down arrow
  • Shift + Up arrow, Shift + Down arrow
  • Page Up, Page Down
  • Binding Converter on the text property

Code behind

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;

namespace Helpers
{
    public class TextBoxNumbers
    {    
        public static Decimal GetSingleDelta(DependencyObject obj)
        {
            return (Decimal)obj.GetValue(SingleDeltaProperty);
        }

        public static void SetSingleDelta(DependencyObject obj, Decimal value)
        {
            obj.SetValue(SingleDeltaProperty, value);
        }

        // Using a DependencyProperty as the backing store for SingleValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SingleDeltaProperty =
            DependencyProperty.RegisterAttached("SingleDelta", typeof(Decimal), typeof(TextBoxNumbers), new UIPropertyMetadata(0.0m, new PropertyChangedCallback(f)));

        public static void f(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            TextBox t = o as TextBox;

            if (t == null)
                return;

            t.PreviewKeyDown += new System.Windows.Input.KeyEventHandler(t_PreviewKeyDown);
        }

        private static Decimal GetSingleValue(DependencyObject obj)
        {
            return GetSingleDelta(obj);
        }

        private static Decimal GetDoubleValue(DependencyObject obj)
        {
            return GetSingleValue(obj) * 10;
        }

        private static Decimal GetTripleValue(DependencyObject obj)
        {
            return GetSingleValue(obj) * 100;
        }

        static void t_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            TextBox t = sender as TextBox;
            Decimal i;

            if (t == null)
                return;

            if (!Decimal.TryParse(t.Text, out i))
                return;

            switch (e.Key)
            {
                case System.Windows.Input.Key.Up:
                    if (Keyboard.Modifiers == ModifierKeys.Shift)
                        i += GetDoubleValue(t);
                    else
                        i += GetSingleValue(t);
                    break;

                case System.Windows.Input.Key.Down:
                    if (Keyboard.Modifiers == ModifierKeys.Shift)
                        i -= GetDoubleValue(t);
                    else
                        i -= GetSingleValue(t);
                    break;

                case System.Windows.Input.Key.PageUp:
                    i += GetTripleValue(t);
                    break;

                case System.Windows.Input.Key.PageDown:
                    i -= GetTripleValue(t);
                    break;

                default:
                    return;
            }

            if (BindingOperations.IsDataBound(t, TextBox.TextProperty))
            {
                try
                {
                    Binding binding = BindingOperations.GetBinding(t, TextBox.TextProperty);
                    t.Text = (string)binding.Converter.Convert(i, null, binding.ConverterParameter, binding.ConverterCulture);
                }
                catch
                {
                    t.Text = i.ToString();
                }
            }
            else
                t.Text = i.ToString();
        }
    }
}

Solution 4 - C#

I decided to simplify the reply marked as the answer on here to basically 2 lines using a LINQ expression.

e.Handled = !e.Text.All(Char.IsNumber);
base.OnPreviewTextInput(e);

Solution 5 - C#

Why don't you just try using the KeyDown event rather than the PreviewKeyDown Event. You can stop the invalid characters there, but all the control characters are accepted. This seems to work for me:

private void NumericKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
    bool isNumPadNumeric = (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9);
    bool isNumeric =((e.Key >= Key.D0 && e.Key <= Key.D9) && (e.KeyboardDevice.Modifiers == ModifierKeys.None));
    bool isDecimal = ((e.Key == Key.OemPeriod || e.Key == Key.Decimal) && (((TextBox)sender).Text.IndexOf('.') < 0));
    e.Handled = !(isNumPadNumeric || isNumeric || isDecimal);
}

Solution 6 - C#

I use a custom ValidationRule to check if text is numeric.

public class DoubleValidation : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        if (value is string)
        {
            double number;
            if (!Double.TryParse((value as string), out number))
                return new ValidationResult(false, "Please enter a valid number");
        }

        return ValidationResult.ValidResult;
    }

Then when I bind a TextBox to a numeric property, I add the new custom class to the Binding.ValidationRules collection. In the example below the validation rule is checked everytime the TextBox.Text changes.

<TextBox>
    <TextBox.Text>
        <Binding Path="MyNumericProperty" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:DoubleValidation/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Solution 7 - C#

public class NumericTextBox : TextBox
{
    public NumericTextBox()
        : base()
    {
        DataObject.AddPastingHandler(this, new DataObjectPastingEventHandler(CheckPasteFormat));
    }

    private Boolean CheckFormat(string text)
    {
        short val;
        return Int16.TryParse(text, out val);
    }

    private void CheckPasteFormat(object sender, DataObjectPastingEventArgs e)
    {
        var isText = e.SourceDataObject.GetDataPresent(System.Windows.DataFormats.Text, true);

        if (isText)
        {
            var text = e.SourceDataObject.GetData(DataFormats.Text) as string;
            if (CheckFormat(text))
            {
                return;
            }
        }

        e.CancelCommand();
    }

    protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
    {
        if (!CheckFormat(e.Text))
        {
            e.Handled = true;
        }
        else
        {
            base.OnPreviewTextInput(e);
        }
    }
}

Additionally you may customize the parsing behavior by providing appropriate dependency properties.

Solution 8 - C#

Combining the ideas from a few of these answers, I have created a NumericTextBox that

  • Handles decimals
  • Does some basic validation to ensure any entered '-' or '.' is valid
  • Handles pasted values

Please feel free to update if you can think of any other logic that should be included.

public class NumericTextBox : TextBox
{
	public NumericTextBox()
	{
		DataObject.AddPastingHandler(this, OnPaste);
	}

	private void OnPaste(object sender, DataObjectPastingEventArgs dataObjectPastingEventArgs)
	{
		var isText = dataObjectPastingEventArgs.SourceDataObject.GetDataPresent(System.Windows.DataFormats.Text, true);

		if (isText)
		{
			var text = dataObjectPastingEventArgs.SourceDataObject.GetData(DataFormats.Text) as string;
			if (IsTextValid(text))
			{
				return;
			}
		}

		dataObjectPastingEventArgs.CancelCommand();
	}

	private bool IsTextValid(string enteredText)
	{
		if (!enteredText.All(c => Char.IsNumber(c) || c == '.' || c == '-'))
		{
			return false;
		}

		//We only validation against unselected text since the selected text will be replaced by the entered text
        var unselectedText = this.Text.Remove(SelectionStart, SelectionLength);

        if (enteredText == "." && unselectedText.Contains("."))
        {
            return false;
        }

        if (enteredText == "-" && unselectedText.Length > 0)
        {
            return false;
        }

		return true;
	}

	protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
	{
		e.Handled = !IsTextValid(e.Text);
		base.OnPreviewTextInput(e);
	}
}

Solution 9 - C#

You can also try using data validation if users commit data before you use it. Doing that I found was fairly simple and cleaner than fiddling about with keys.

Otherwise, you could always disable Paste too!

Solution 10 - C#

Add this to the main solution to make sure the the binding is updated to zero when the textbox is cleared.

protected override void OnPreviewKeyUp(System.Windows.Input.KeyEventArgs e)
{
    base.OnPreviewKeyUp(e);

    if (BindingOperations.IsDataBound(this, TextBox.TextProperty))
    {
        if (this.Text.Length == 0)
        {
            this.SetValue(TextBox.TextProperty, "0");
            this.SelectAll();
        }
    }
}

Solution 11 - C#

My Version of Arcturus answer, can change the convert method used to work with int / uint / decimal / byte (for colours) or any other numeric format you care to use, also works with copy / paste

protected override void OnPreviewTextInput( System.Windows.Input.TextCompositionEventArgs e )
{
	try
	{
		if ( String.IsNullOrEmpty( SelectedText ) )
		{
			Convert.ToDecimal( this.Text.Insert( this.CaretIndex, e.Text ) );
		}
		else
		{
			Convert.ToDecimal( this.Text.Remove( this.SelectionStart, this.SelectionLength ).Insert( this.SelectionStart, e.Text ) );
		}
	}
	catch
	{
		// mark as handled if cannot convert string to decimal
		e.Handled = true;
	}

	base.OnPreviewTextInput( e );
}

N.B. Untested code.

Solution 12 - C#

Call me crazy, but why not put plus and minus buttons at either side of the TextBox control and simply prevent the TextBox from receiving cursor focus, thereby creating your own cheap NumericUpDown control?

Solution 13 - C#

Can you not just use something like the following?

int numericValue = 0;

if (false == int.TryParse(yourInput, out numericValue))
{
    // handle non-numeric input
}

Solution 14 - C#

private void txtNumericValue_PreviewKeyDown(object sender, KeyEventArgs e)
{
	KeyConverter converter = new KeyConverter();
	
	string key = converter.ConvertToString(e.Key);

	if (key != null && key.Length == 1)
	{
		e.Handled = Char.IsDigit(key[0]) == false;
	}
}

This is the easiest technique I've found to accomplish this. The down side is that the context menu of the TextBox still allows non-numerics via Paste. To resolve this quickly I simply added the attribute/property: ContextMenu="{x:Null}" to the TextBox thereby disabling it. Not ideal but for my scenario it will suffice.

Obviously you could add a few more keys/chars in the test to include additional acceptable values (e.g. '.', '$' etc...)

Solution 15 - C#

Private Sub Value1TextBox_PreviewTextInput(ByVal sender As Object, ByVal e As TextCompositionEventArgs) Handles Value1TextBox.PreviewTextInput
    Try
        If Not IsNumeric(e.Text) Then
            e.Handled = True
        End If
    Catch ex As Exception
    End Try
End Sub

Worked for me.

Solution 16 - C#

void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
{
    string sVal = e.Text;
    int val = 0;

    if (sVal != null && sVal.Length > 0)
    {
        if (int.TryParse(sVal, out val))
        {
            e.Handled = false;
        }
        else
        {
            e.Handled = true;
        }
    }
}

Solution 17 - C#

Can also use a converter like:

public class IntegerFormatConverter : IValueConverter
{
    public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int result;
        int.TryParse(value.ToString(), out result);
        return result;
    }

    public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int result;
        int.TryParse(value.ToString(), out result);
        return result;
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatt HamiltonView Question on Stackoverflow
Solution 1 - C#ArcturusView Answer on Stackoverflow
Solution 2 - C#EricView Answer on Stackoverflow
Solution 3 - C#user3035View Answer on Stackoverflow
Solution 4 - C#erodewaldView Answer on Stackoverflow
Solution 5 - C#JroonkView Answer on Stackoverflow
Solution 6 - C#Jonny OtsView Answer on Stackoverflow
Solution 7 - C#juanaguiView Answer on Stackoverflow
Solution 8 - C#Brian HincheyView Answer on Stackoverflow
Solution 9 - C#NidonocuView Answer on Stackoverflow
Solution 10 - C#BrownBotView Answer on Stackoverflow
Solution 11 - C#Dead.RabitView Answer on Stackoverflow
Solution 12 - C#tags2kView Answer on Stackoverflow
Solution 13 - C#Mark AllenView Answer on Stackoverflow
Solution 14 - C#AudaxisView Answer on Stackoverflow
Solution 15 - C#RitzView Answer on Stackoverflow
Solution 16 - C#bcdView Answer on Stackoverflow
Solution 17 - C#VarunView Answer on Stackoverflow