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.
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
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.
We’ve all done that!
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);
}
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.
Yes that makes the best sense, plus then it could be done declaritively in xaml 🙂 Thanks for the tip!
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?
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.
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
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.
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?
Thank you very much for this article!!! It help me with my issue.
Thanks for the code snippet.SImple and Straight!
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
[…] of incorrect data hook up the DataObject.Pasting event DataObject.Pasting="TextBoxPasting" as shown here (code […]
[…] of incorrect data hook up the DataObject.Pasting event DataObject.Pasting="TextBoxPasting" as shown here (code […]
[…] of incorrect data hook up the DataObject.Pasting event DataObject.Pasting="TextBoxPasting" as shown here (code […]
(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.
[…] 다음 과 같이 DataObject.Pasting이벤트 DataObject.Pasting="TextBoxPasting"를 연결 하십시오 (코드 […]
[…] 다음 과 같이 DataObject.Pasting이벤트 DataObject.Pasting="TextBoxPasting"를 연결 하십시오 (코드 […]