카테고리 없음

Masking input to a WPF TextBox

마피아9 2010. 2. 24. 01:55

Masking input to a WPF TextBox February 15, 2007

Posted by Karl Hulme in .NetC#WPFXAML
trackback

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.