jump to navigation

Masking input to a WPF TextBox February 15, 2007

Posted by Karl Hulme in .Net, C#, WPF, XAML.
trackback

This blog is no longer actively maintained. The content is well over 5 years old – which is like 50 coder years. Use at your own risk!

How do you go about masking the input to a textbox.  Say you wanted to allow numbers but not letters or any punctuation, how would you do it?  Well the simplest way is to trap the TextChanged event…

        private void TextChangedEventHandler(Object sender, EventArgs e) 
        { 
            TextBox textBox = sender as TextBox; 
            Int32 selectionStart = textBox.SelectionStart; 
            Int32 selectionLength = textBox.SelectionLength; 

            String newText = String.Empty; 
            foreach (Char c in textBox.Text.ToCharArray()) 
            { 
                if(Char.IsDigit(c) || Char.IsControl(c)) newText += c; 
            } 

            textBox.Text = newText; 

            textBox.SelectionStart = selectionStart <= textBox.Text.Length ? 
                selectionStart : textBox.Text.Length; 
        }

This method is simple to understand (always a positive) but there are a few problems with this as I see it, namely..

  • An additional TextChanged event is raised when input is rejected
  • You can’t tell what the change was, you can only inspect the end result
  • You have to re-implement the code that determines the correct caret position

This is not a great way to solve the original problem, because we’re basically hacking at the input after it’s already been accepted.  Leaving us trying to fix the text and compensate for the changes in caret position.  It’s easier to reject invalid characters as they’re presented.  Let’s start by defining a very simple method for stripping out the input we don’t want..  

        private Boolean IsTextAllowed(String text) 
        { 
            foreach (Char c in text.ToCharArray()) 
            { 
                if (Char.IsDigit(c) || Char.IsControl(c)) continue; 
                    else return false; 
            } 
            return true; 
        }

This could be defined slightly more elegantly using a predicate..

        private Boolean IsTextAllowed(String text) 
        { 
            return Array.TrueForAll<Char>(text.ToCharArray(), 
                delegate(Char c) { return Char.IsDigit(c) || Char.IsControl(c); }); 
        }

We then need to identify the entry points to the control and for each one, identify how we will be notified about it’s use.  For typical a TextBox these entry points might be:

  • Typing into the TextBox
    • Hook up to the PreviewTextInput event
  • Copy/Pasting into the TextBox
    • Hook up to the DataObject.Pasting event

It’s easy to implement and hook up the handlers for these events..

      <TextBox PreviewTextInput="PreviewTextInputHandler" 
               DataObject.Pasting="PastingHandler" />

        // Use the PreviewTextInputHandler to respond to key presses 
        private void PreviewTextInputHandler(Object sender, TextCompositionEventArgs e) 
        { 
            e.Handled = !IsTextAllowed(e.Text); 
        } 

        // Use the DataObject.Pasting Handler  
        private void PastingHandler(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(); 
        }

In some cases, you may need to support other scenarios (such as allowing users to Drag-and-Drop text onto it) and these can be dealt with in a similar fashion – identify an appropriate event, check the input, and if it’s invalid then prevent any further processing.

I hope this information serves as a starting point for implementing a masked textbox.  That is, display an actual mask (using underlines) and rigidly control the input of the user.  One immediate change is that you’d look to subclass TextBox (or base class) and override the protected handlers (e.g. OnPreviewTextInput) rather than hook up directly to the events.  The underlines are also important because without them the user has little clue what is expected of them.  Infact, in a basic validation scenario I think it better to accept dodgy input and then provide UI cues as to why it isn’t acceptable.  I’m holding off doing this myself just in case it turns up in the standard library, perhaps when Orcas ships?  If it doesn’t, I’ll post animplementation because my project needs one. 

Comments»

1. Simon Middlemiss - February 15, 2007

Have you looked at the WPF ‘Fun Controls Bag’ on codeplex? There’s a control called ‘FilteredTextBox’ which allows you to do simple masking.

Check it out

2. Karl Hulme - February 18, 2007

Hi Simon. I’ll definately take a look at that, really helpful, thanks. I’m not anticipating too much difficulty having done something similar in Windows Forms which is harder because little of the functionality of a textbox comes out in subclassing and you don’t have a TextInput style event. Hey, what do you know? WPF makes this kind of stuff A LOT easier! But I think we knew that…

I actually put this post together to get my frustration out in a positive way. I was trying to help someone on the WPF forum and after providing the code to do it properly, they opted for some nasty hack, which they then had to ‘edit post’ several times as they started to find the bugs/problems with it.

3. Simon Middlemiss - March 6, 2007

We’ve all done that!

4. james peckham - May 23, 2007

i was using this to control length of a string, noticed a behavior when highlighted text wouldn’t overwrite … here’s a fix for that. Hope it’s not too ‘hack’ for you.
protected override void OnTextInput(System.Windows.Input.TextCompositionEventArgs e)
{
e.Handled = !(IsTextAllowed(e.Text) && (this.Text.Length + e.Text.Length – this.SelectedText.Length) <= 10);
base.OnTextInput(e);
}

5. Karl Hulme - May 24, 2007

Hi James,

I editted your 3 comments down to a single comment – I hope that’s OK. You’re right, WordPress doesn’t seem to cope with a < sign! I entered this as &-l-t-; to get it to appear properly.

I see that you have a requirement that I didn’t anticipate, and yes the code in it’s current state doesn’t satisfy it. However I think that the IsTextAllowed method should be wholly responsible for determining if the text is allowed. My feeling is that your version spreads this logic across the IsTextAllowed method and also puts it in the TextCompositionEventArgs – do you agree? My suggestion (and I reserve the right to be horribly wrong!) is to extend the IsTextAllowed method so that it gets passed enough information to make the decision. In this case that appears to be the TextBox itself, or perhaps a new class that encapsulates the current text, selected text, etc.

private Boolean IsTextAllowed(TextBox textBox, inputText string) or better
private Boolean IsTextAllowed(TextInputData tid, String inputText)

TextInputData would have to be defined. We could also consider using the Strategy pattern. So refactor the IsTextAllowed method into an ITextAllowed interface, and pass an object that implements that interface to the TextBox sometime after creation. You could then implement that interface in a TenDigitsOrLess class. It would save us endlessly subclassing TextBox.

I hope that all makes sense. Good luck anyway, and thanks for your feedback.

6. james - May 27, 2007

Yes that makes the best sense, plus then it could be done declaritively in xaml 🙂 Thanks for the tip!

7. R2 - June 20, 2007

Gents,

I’m looking for a way to implement the old input mask concept to corale the user into inputting a certain format string. ( nnn-nnn-nnnn for example). Any ideas?

8. Peter Voutov - September 12, 2007

The FilteredTextbox in WPF ‘Fun Controls Bag’ is a little more than an extension of this post, you could put something better together in 5 minutes.. It’s worthless. Someone needs to implement regex based validation and the ability to display an input mask. FilteredTextBox is not it.

9. traskjd - April 10, 2008

Mindscape just released WPF Elements which includes 12 controls for building line of business applications, one of them is a full Masked TextBox control.

http://www.mindscape.co.nz/products/WpfElements/

As a disclaimer, I work for Mindscape, but like many of the controls that we develop as products, we created them out of necessity and I hope that other developers can save time by using this suite.

Thanks,

John-Daniel Trask

10. Skurmedel - August 28, 2008

Hi.

I’m working on a masked text box in WPF myself, for my company. The System.ComponentModel.MaskedTextProvider helped me considerably, but I still feel parts of the TextBox-class itself could be more subclassing friendly than it is.

Looking in the TextBox-class with Reflector reveals alot of internal classes that handles tricky stuff like selections and the like. While I understand that much of this is hidden from us for good reasons I also think that there should be more hooks available. A big hurdle for me was trying to figure out when the CaretIndex was manipulated by mouse input, something you more or less cannot know from subclass perspective. I ended up trapping OnMouseDown, calling the base method and made my code always assume the CaretIndex had been changed.

11. biosys - December 3, 2008

I have the same problem… I need mask to input hour and have no idea how to do it :/ Any one can help me, please?

12. Ian - February 12, 2009

Thank you very much for this article!!! It help me with my issue.

13. Ady - September 15, 2011

Thanks for the code snippet.SImple and Straight!

14. Johnc169 - August 8, 2014

I truly appreciate this post. I’ve been looking everywhere for this! Thank goodness I found it on Bing. You’ve made my day! Thank you again! cdeddecgedbb

15. How to: How do I get a TextBox to only accept numeric input in WPF? | SevenNet - December 19, 2014

[…] of incorrect data hook up the DataObject.Pasting event DataObject.Pasting="TextBoxPasting" as shown here (code […]

16. How do I get a TextBox to only accept numeric input in WPF? | ASK AND ANSWER - January 17, 2016

[…] of incorrect data hook up the DataObject.Pasting event DataObject.Pasting="TextBoxPasting" as shown here (code […]

17. How do I get a TextBox to only accept numeric input in WPF? – Knowleage Exchange - April 24, 2018

[…] of incorrect data hook up the DataObject.Pasting event DataObject.Pasting="TextBoxPasting" as shown here (code […]

18. Latashia Service - November 8, 2019

(I wish to show thanks to you just for bailing me out of this particular trouble.As a result of checking through the net and meeting techniques that were not productive, I thought my life was done.

19. [C#] WPF에서 TextBox에 숫자 입력 만 허용하려면 어떻게해야합니까? - 리뷰나라 - June 8, 2021

[…] 다음 과 같이 DataObject.Pasting이벤트 DataObject.Pasting="TextBoxPasting"를 연결 하십시오 (코드 […]

20. WPF에서 TextBox에 숫자 입력 만 허용하려면 어떻게해야합니까? - 질문답변 - June 17, 2021

[…] 다음 과 같이 DataObject.Pasting이벤트 DataObject.Pasting="TextBoxPasting"를 연결 하십시오 (코드 […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: