C# Disciples

my life in Avalon ….

Masked TextBox

I found a new control for the AvalonControlsLibrary, the MaskedTextBox. I found this control while reading one of the best WPF books around from Matthew MacDonald.

The MaskedTextBox is a normal WPF textbox (in fact it inherits from the TextBox class) that formats the text entered by the user (and also strings set from the Text property programmatically). For example if you type 1234567890 into a masked textbox that has a U.S. telephone number mask, the text will be displayed as (123) 456-7890. To check out how you can create Masks have a look at this url.

To create this control I use the MaskTextProvider which is a .Net class. The MaskedTextProvider takes care of validating the string and also to format the text entered accordingly to the mask specified.

The MaskedTextBox has a Mask property which accepts a string. This string (and the mask property) is then used to create a MaskedTextProvider that can format the user input.

In order to intercept the user input, I override the OnPreviewTextInput method and apply the mask to the text that has been entered. As you can see this is 80% of the control’s code.

/// <summary>
/// override this method to replace the characters enetered with the mask
/// </summary>
/// <param name=”e”>Arguments for event</param>

protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
        
int position = SelectionStart;
        
MaskedTextProvider provider = MaskProvider;
        
if (position < Text.Length)
        {
                position = GetNextCharacterPosition(position);
               
if (Keyboard.IsKeyToggled(Key.Insert))
                {
                        
if (provider.Replace(e.Text, position))
                               position++;
                }
               
else
               
{
                        
if (provider.InsertAt(e.Text, position))
                              position++;
                 }
                 position = GetNextCharacterPosition(position);
         }
         RefreshText(provider, position);
         e.Handled =
true;
        
base.OnPreviewTextInput(e);
}

I also override the OnPreviewKeyDown method of the TextBox in order to handle special characters such as delete and backspace.Yet we have a problem! The problem is that if the user uses cut or paste the mask will not be applied until the next keystroke. So the workaround (described in the book) shows us how we can use CommandBinding to suppress these features. I really liked the idea because this shows us how commands can give us such power in our hands. So to suppress these features we have to do the following///<summary>
/// Default constructor
///</summary>
public MaskedTextBox()
{
           //cancel the paste and cut command
          
CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, null, CancelCommand));
           CommandBindings.Add(
new CommandBinding(ApplicationCommands.Cut, null, CancelCommand));
}
//cancel the command
private static void CancelCommand(object sender, CanExecuteRoutedEventArgs e)
{
           e.CanExecute =
false;
           e.Handled =
true;
}
 As you can see all you have to do is handle the command yourself and just set the CanExecute to false and Handled to true. Nice!Another problem is how we can force the Text property to apply the Mask when set programmatically. Since we only handle the OnPreviewTextInput this is not catered for. And here comes the FrameworkMetaData. Basically we can override the default meta data for the Text property and apply a CoerceValueCallback that apply the mask to the text./// <summary>
/// Static Constructor
/// </summary>
static MaskedTextBox()
{
             //override the meta data for the Text Proeprty of the textbox
           
FrameworkPropertyMetadata metaData = new FrameworkPropertyMetadata();
            metaData.CoerceValueCallback = ForceText;
           TextProperty.OverrideMetadata(
typeof(MaskedTextBox), metaData);
}
//force the text of the control to use the mask
private static object ForceText(DependencyObject sender, object value)
{
            
MaskedTextBox textBox = (MaskedTextBox) sender;
            
if (textBox.Mask != null)
            {
                     
MaskedTextProvider provider = new MaskedTextProvider(textBox.Mask);
                      provider.Set((
string) value);
                    
return provider.ToDisplayString();
            }
           else
          
{
                  
return value;
           }
}
 This shows us how powerful Dependency properties are. I was really excited about this one since it’s my first time to play around and override property meta data explicitly  :)

And that’s basically it.Eventually I will implement a real MaskedTextBox by inheriting from the TextBoxBase but for now this will do the trick for developers that need this control.Hope you find this control useful.
Downlaod control demo and full source code

October 28, 2007 - Posted by marlongrech | .Net 3.0, WPF, WPF Custom Controls | | 9 Comments

9 Comments »

  1. Hi,

    You made a little mistake, because if space is pressed the PreviewTextInput Event will not be raised, so you have to handle that in the PreviewKeyDown Event. Space is the only character of these special keys, which could valid for a mask.

    So I wrote a little adaptation:
    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
    base.OnPreviewKeyDown(e);

    MaskedTextProvider provider = MaskProvider;
    int position = SelectionStart;

    if (e.Key == Key.Delete && position 0)
    {
    position–;
    if (provider.RemoveAt(position))
    RefreshText(provider, position);
    }
    e.Handled = true;
    }
    else if (e.Key == Key.Space)
    {
    if (provider.InsertAt(’ ‘, position))
    RefreshText(provider, position);
    e.Handled = true;
    }
    }

    Regards,
    Martin

    Comment by Martin Moser | November 9, 2007

  2. Sorry I had a problem to copy the code snipped,
    so second try:

    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
    base.OnPreviewKeyDown(e);

    MaskedTextProvider provider = MaskProvider;
    int position = SelectionStart;

    if (e.Key == Key.Delete && position 0)
    {
    position–;
    if (provider.RemoveAt(position))
    RefreshText(provider, position);
    }
    e.Handled = true;
    }
    else if (e.Key == Key.Space)
    {
    if (provider.InsertAt(’ ‘, position))
    RefreshText(provider, position);
    e.Handled = true;
    }
    }

    Comment by Martin Moser | November 9, 2007

  3. Ok I dont know why but some parts of my code are cutted out by the blog…

    Comment by Martin Moser | November 9, 2007

  4. Thanks a lot I will make sure to make this fix and upload the new code.

    Thanks a lot

    Comment by marlongrech | November 9, 2007

  5. If anyone is having problems with the IsReadonly Property of this control please have a look here
    http://www.codeplex.com/AvalonControlsLib/WorkItem/View.aspx?WorkItemId=860

    The code for this fix is committed in CodePlex… The official release will be AvalonControlsLibrary V2

    Thanks for the support

    Comment by marlongrech | February 15, 2008

  6. How to bind masked text box with today’s date.
    I have written following xaml code but it dosent work

    Kindly help

    Comment by Afzal | March 28, 2008

  7. Your MaskedTextBox behaves different than the WinForms MaskedTextBox.
    The ability to select a character and overwrite it is vital for masks like ‘L00′ that allow a letter and two digits.
    If one enters X99 and wants to change it to Y99, this can be done with WinForms by selecting the X and typing a Y.
    With your control the user probably will have to delete all three characters and type Y99 (one cannot delete the X from X99 because a digit would move into the letter position of the mask string).

    Comment by Thomas Hoevel | April 30, 2008

  8. Hi, I am not very sufficient in C# and If anyone can help me that would be the great. The question is:
    Write the mask for a masked Textbox that can produce “(AB)-(2)-(888)” as a possible output.

    Comment by Bob | June 11, 2008

  9. Hi Marlon,
    Thanks for the very useful control of yours.
    I am using the MaskedTextBox in one of our projects.
    I need to disable the Mask from appearing on the textbox by default(the mask should appear when the control gets focus)
    Second is, the textbox should be able to accept Space when the Mask is to ‘CCCCCCCC’.

    Thanks in advance,
    Prudhvi.

    Comment by Prudhvi | July 12, 2008

Leave a comment