How do I get a TextBox to only accept numeric input in WPF?

Learn how do i get a textbox to only accept numeric input in wpf? with practical examples, diagrams, and best practices. Covers c#, wpf, xaml development techniques with visual explanations.

How to Restrict WPF TextBox Input to Numeric Values Only

Hero image for How do I get a TextBox to only accept numeric input in WPF?

Learn various techniques to ensure a WPF TextBox accepts only numeric input, from basic event handling to advanced attached behaviors and custom controls.

In Windows Presentation Foundation (WPF) applications, it's a common requirement to restrict user input in a TextBox to specific data types, such as numbers. This prevents invalid data from being entered, improving data integrity and user experience. This article explores several robust methods to achieve numeric-only input, ranging from simple event-based validation to more sophisticated attached behaviors and custom controls.

Method 1: Event-Based Validation (PreviewTextInput)

The PreviewTextInput event is a good starting point for basic input validation. This event fires before the text is actually entered into the TextBox. By handling this event, you can inspect the incoming text and mark the event as handled if the input is not numeric, effectively preventing it from appearing in the TextBox.

<TextBox PreviewTextInput="TextBox_PreviewTextInput" />

XAML for a TextBox using PreviewTextInput

using System.Text.RegularExpressions;
using System.Windows.Input;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        // Use a regular expression to check if the input is a digit
        // This allows for integers only. For decimals, see the next example.
        Regex regex = new Regex("[^0-9]+"); // Match non-digits
        e.Handled = regex.IsMatch(e.Text);
    }
}

C# code for PreviewTextInput event handler (integer only)

private void TextBox_PreviewTextInput_Decimal(object sender, TextCompositionEventArgs e)
{
    TextBox textBox = sender as TextBox;
    // Allow digits and one decimal point
    if (!char.IsDigit(e.Text, 0) && e.Text != ".")
    {
        e.Handled = true; // Reject non-digit and non-decimal point characters
    }
    else if (e.Text == "." && textBox.Text.Contains("."))
    {
        e.Handled = true; // Reject if a decimal point already exists
    }
}

C# code for PreviewTextInput event handler (decimal numbers)

Method 2: Attached Behavior for Reusability

Attached behaviors provide a more elegant and reusable solution compared to direct event handling in the code-behind. They allow you to encapsulate the numeric input logic into a separate class that can be easily attached to any TextBox in XAML, promoting cleaner code and better separation of concerns.

flowchart TD
    A[TextBox] --> B{Attached Behavior: NumericOnly}
    B --> C{PreviewTextInput Event}
    C --> D{Validate Input (Regex)}
    D -- Invalid --> E[e.Handled = true]
    D -- Valid --> F[Allow Input]
    E --> G[Input Blocked]
    F --> H[Input Accepted]

Flowchart of Attached Behavior for Numeric Input

using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

public static class NumericOnlyBehavior
{
    public static readonly DependencyProperty IsNumericOnlyProperty =
        DependencyProperty.RegisterAttached(
            "IsNumericOnly",
            typeof(bool),
            typeof(NumericOnlyBehavior),
            new PropertyMetadata(false, OnIsNumericOnlyChanged));

    public static bool GetIsNumericOnly(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsNumericOnlyProperty);
    }

    public static void SetIsNumericOnly(DependencyObject obj, bool value)
    {
        obj.SetValue(IsNumericOnlyProperty, value);
    }

    private static void OnIsNumericOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is TextBox textBox)
        {
            if ((bool)e.NewValue)
            {
                textBox.PreviewTextInput += TextBox_PreviewTextInput;
                // Optional: Handle pasting to prevent non-numeric text
                textBox.AddHandler(DataObject.PastingEvent, new DataObjectPastingEventHandler(TextBox_Pasting));
            }
            else
            {
                textBox.PreviewTextInput -= TextBox_PreviewTextInput;
                textBox.RemoveHandler(DataObject.PastingEvent, new DataObjectPastingEventHandler(TextBox_Pasting));
            }
        }
    }

    private static void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        Regex regex = new Regex("[^0-9.-]+"); // Allow digits, decimal point, and negative sign
        TextBox textBox = sender as TextBox;

        // Handle decimal point
        if (e.Text == ".")
        {
            if (textBox.Text.Contains(".") || textBox.SelectionStart == 0 && textBox.Text.Length > 0 && !char.IsDigit(textBox.Text[0]))
            {
                e.Handled = true; // Prevent multiple decimal points or decimal at start if not followed by digit
            }
        }
        // Handle negative sign
        else if (e.Text == "-")
        {
            if (textBox.Text.Contains("-") || textBox.SelectionStart != 0)
            {
                e.Handled = true; // Prevent multiple negative signs or negative sign not at start
            }
        }
        // Handle other non-numeric characters
        else if (regex.IsMatch(e.Text))
        {
            e.Handled = true;
        }
    }

    private static void TextBox_Pasting(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(typeof(string)))
        {
            string text = (string)e.DataObject.GetData(typeof(string));
            Regex regex = new Regex("[^0-9.-]+");
            if (regex.IsMatch(text))
            {
                e.CancelCommand();
            }
        }
        else
        {
            e.CancelCommand();
        }
    }
}

C# code for NumericOnlyBehavior attached property

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApp" <!-- Adjust namespace -->
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <TextBox local:NumericOnlyBehavior.IsNumericOnly="True" Width="200" Height="30" VerticalAlignment="Center" HorizontalAlignment="Center" />
    </Grid>
</Window>

XAML using the NumericOnlyBehavior attached property

Method 3: Custom Control (NumericUpDown)

For a more feature-rich numeric input experience, consider creating a custom NumericUpDown control or using a third-party library. A custom control can encapsulate not only the input validation but also provide increment/decrement buttons, min/max value enforcement, and formatting options. This is ideal when you need consistent numeric input across your application with additional UI elements.

Considerations and Best Practices

When implementing numeric input, keep the following in mind:

  • Culture-Specific Decimals: Different cultures use different decimal separators (e.g., '.' vs. ','). Ensure your validation accounts for the current culture if your application needs to be globalized.
  • Pasting: Users can paste non-numeric text. The DataObject.PastingEvent handler in the attached behavior example addresses this.
  • Negative Numbers: If negative numbers are allowed, ensure the '-' sign is permitted only at the beginning of the input.
  • Min/Max Values: For more robust validation, consider adding logic to enforce minimum and maximum allowed values, typically on the TextChanged or LostFocus events, or when the data is bound.
  • Input Method Editors (IMEs): For languages that use IMEs, PreviewTextInput might not always fire as expected. For most Western numeric input, it's sufficient.
  • Data Binding: If you're using data binding, ensure your ViewModel properties are of a numeric type (e.g., int, double) and implement INotifyPropertyChanged. WPF's binding engine can also perform type conversion and validation.