Introduction

This article describes how to enhance WPF TextBox and make it accept numeric(integer and floating point) values. The second goal is make the TextBox smart enough to have it easier to input numerics. The easy means to provide the TextBox with some kind of intelligence not just rejecting non-numeric symbols. Provided extension also allows setting minimum and/or maximum values.

If you search in the net, you will probably find some solutions for this problem where developers create their own versions of TextBox either by inheriting from it or creating a Custom/User Controls that include standard WPF TextBox. Previous solutions have one major drawback - you would need to replace your TextBox definitions with your new MaskTextBox. Sometimes it is not painful, sometimes it is. The reason I chose another solutions is that in my case such kind of change would be painful.

The approach I’m proposing here is a usage of WPF Attached Properties, which basically are similar to Dependency Properties. The major difference among these to is that Dependency Properties are defined inside the Control, but Attached Properties - outside. For instance, TextBox.Text is Dependency Property, but Grid.Column is an Attached Property.

Background(Extending Functionality of TextBox)

First of all I will define an enumeration, which tells us whether the TextBox accepts integer, decimal or any kind of values:

public enum MaskType
{
    Any,
    Integer,
    Decimal
}

Next thing would be definition of the attached property.

public class TextBoxMaskBehavior
{
    public static MaskType GetMask(DependencyObject obj)
    {
        return (MaskType)obj.GetValue(MaskProperty);
    }

    public static void SetMask(DependencyObject obj, MaskType value)
    {
        obj.SetValue(MaskProperty, value);
    }

    public static readonly DependencyProperty MaskProperty =
        DependencyProperty.RegisterAttached(
        "Mask",
        typeof(MaskType),
        typeof(TextBoxMaskBehavior),
        new FrameworkPropertyMetadata(MaskChangedCallback)
        );

    private static void MaskChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // ...
    }
}

Now, we may specify the Mask for WPF TextBox like this:

<TextBox local:TextBoxMaskBehavior.Mask="Integer" />

or

<TextBox local:TextBoxMaskBehavior.Mask="Decimal" />

and our assumption would be that at the particular TextBox would accept integer and decimal values correspondingly. Whenever TestBoxMaskBehavior.Mask is set, the MaskChangedCallback is being called. We will add corresponding handling there.

Background(Subscribing to TextBox Changes)

WPF TextBox comes with an event: PreviewTextInput, which allows to listen for text input. It also allows to cancel particular text input by setting the Handled property of the TextCompositionEventArgs to true. Below is an illustrative example:

...
private static void TextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
{
    try
    {
        Convert.ToInt32(e.Text);
    }
    catch
    {
        e.Handled = true;
    }
}

Please note, that this code is just an example of PreviewTextInput event usage. My solution much more useful featurer rather then checking the input text is number or not :).

There is another event we will need to subscribe. PreviewTextInput does not receive a notification when text is being pasted from the clipboard. For this purpose we will recourse to DataObject class. Check out this example:

DataObject.AddPastingHandler(myTextBox, TextBoxPastingEventHandler);
...
private void TextBoxPastingEventHandler(object sender, DataObjectPastingEventArgs e)
{
    string clipboard = e.DataObject.GetData(typeof(string)) as string;
    try
    {
        Convert.ToInt32(clipboard);
    }
    catch
    {
        e.CancelCommand();
        e.Handled = true;
    }
}

I think it was self-explanatory :).

Maskable TextBox

We have covered techniques that I used in the solution. Full source code and demo binary is available from links below.

Here is list of features available for my Maskable TextBox:

  • Rejects symbols other than digits, negative sign and decimal separator.
  • Clamps to minimum/maximum values if any specified.
  • When typing negative sign regardless from the caret position, adds it in the beginning if not exits, or removes existing one.
  • When typing decimal separator removes the existing one (if exists), and places the new one in the correct place.
  • And more useful stuff. Just try to use it. I’m sure you will not regret.

Downloads