Introduction
After trying several different approaches, I finally settled on creating a custom class derived from TextBox
. Then very simply added a custom public enum
property "Mask
" to my custom class for selecting the masked type. Next, I subscribe to the TextChanged
event in the constructor of my class and this is where I'll apply any logic needed to filter input, then apply our need mask.
Setting the Mask Type
Our custom TextBox
which I've called "MaskedTextBox
" will make use of a custom enum
called "TextBoxMask
". So if you are using VS 2010 or greater, you can begin by creating a new WPF project and calling it what you wish, in my case "WpfApplication6
". Then right click on the project in project explorer and select Add>New>Class and add the enum
listed below to the class file.
public enum TextBoxMask
{
Phone7Digit,
Phone7DigitWithExt,
Phone10Digit,
Phone10DigitWithExt,
Phone11Digit,
Phone11DigitWithExt,
SSN
}
These are the masks options I've used so far. You can remove or alter masks to suit your needs.
You can add your custom TextBox
class to the same class file as below:
public class MaskedTextBox : TextBox
{
public TextBoxMask Mask { get; set; }
public MaskedTextBox()
{
this.TextChanged += new TextChangedEventHandler(MaskedTextBox_TextChanged);
}
void MaskedTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
this.CaretIndex = this.Text.Length;
var tbEntry = sender as MaskedTextBox;
if (tbEntry != null && tbEntry.Text.Length > 0)
{
tbEntry.Text = formatNumber(tbEntry.Text, tbEntry.Mask);
}
}
public static string formatNumber(string MaskedNum, TextBoxMask phoneFormat)
{
int x;
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
if (MaskedNum != null)
{
for (int i = 0; i < MaskedNum.Length; i++)
{
if (int.TryParse(MaskedNum.Substring(i, 1), out x))
{
sb.Append(x.ToString());
}
}
switch (phoneFormat)
{
case TextBoxMask.Phone7Digit:
return FormatFor7DigitPhone(sb.ToString());
case TextBoxMask.Phone7DigitWithExt:
return FormatFor7DigitPhoneWithExt(sb.ToString());
case TextBoxMask.Phone10Digit:
return FormatFor10DigitPhone(sb.ToString());
case TextBoxMask.Phone10DigitWithExt:
return FormatFor10DigitPhoneWithExt(sb.ToString());
case TextBoxMask.Phone11Digit:
return FormatFor11DigitPhone(sb.ToString());
case TextBoxMask.Phone11DigitWithExt:
return FormatFor11DigitPhoneWithExt(sb.ToString());
case TextBoxMask.SSN:
return FormatForSSN(sb.ToString());
default:
break;
}
}
return sb.ToString();
}
}
public enum TextBoxMask
{
Phone7Digit,
Phone7DigitWithExt,
Phone10Digit,
Phone10DigitWithExt,
Phone11Digit,
Phone11DigitWithExt,
SSN
}
In the Text Changed Event, first we move the Caret to the end of the text. Then, if the string
is not null
or 0
length, we pass the unformatted text and mask to the formatNumber
method. Inside of formatNumber
, we then remove all non numeric characters and pass both StringBuilders
to the appropriate method based on which mask type is selected.
To save a little time and space, I've included the methods to format a 10 Digit phonenumber and SSN only. You can expand on those with your own formatting options.
public static StringBuilder FormatFor10DigitPhone(string sb)
{
StringBuilder sb2 = new StringBuilder();
if (sb.Length > 0) sb2.Append("(");
if (sb.Length > 0) sb2.Append(sb.Substring(0, 1));
if (sb.Length > 1) sb2.Append(sb.Substring(1, 1));
if (sb.Length > 2) sb2.Append(sb.Substring(2, 1));
if (sb.Length > 3) sb2.Append(") ");
if (sb.Length > 3) sb2.Append(sb.Substring(3, 1));
if (sb.Length > 4) sb2.Append(sb.Substring(4, 1));
if (sb.Length > 5) sb2.Append(sb.Substring(5, 1));
if (sb.Length > 6) sb2.Append("-");
if (sb.Length > 6) sb2.Append(sb.Substring(6, 1));
if (sb.Length > 7) sb2.Append(sb.Substring(7, 1));
if (sb.Length > 8) sb2.Append(sb.Substring(8, 1));
if (sb.Length > 9) sb2.Append(sb.Substring(9, 1));
return sb2;
}
and for SSN:
public static StringBuilder FormatForSSN(String sb)
{
StringBuilder sb2 = new StringBuilder();
if (sb.Length > 0) sb2.Append(sb.Substring(0, 1));
if (sb.Length > 1) sb2.Append(sb.Substring(1, 1));
if (sb.Length > 2) sb2.Append(sb.Substring(2, 1));
if (sb.Length > 3) sb2.Append("-");
if (sb.Length > 3) sb2.Append(sb.Substring(3, 1));
if (sb.Length > 4) sb2.Append(sb.Substring(4, 1));
if (sb.Length > 5) sb2.Append("-");
if (sb.Length > 5) sb2.Append(sb.Substring(5, 1));
if (sb.Length > 6) sb2.Append(sb.Substring(6, 1));
if (sb.Length > 7) sb2.Append(sb.Substring(7, 1));
if (sb.Length > 8) sb2.Append(sb.Substring(8, 1));
return sb2;
}
XAML
The following code is from my MainWindow
XAML:
<Window
x:Class="WpfApplication6.MainWindow"
xmlns:dc="clr-namespace:WpfApplication6"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication6"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<dc:MaskedTextBox Mask="SSN"
Height="23" Width="150"></dc:MaskedTextBox>
</Grid>
</Window>
I've added only two lines:
xmlns:dc="clr-namespace:WpfApplication6"
in the Window declaration and for our masked text box:
<Grid>
<dc:MaskedTextBox Mask="SSN"
Height="23" Width="150"></dc:MaskedTextBox>
</Grid>
Conclusion
There is plenty of room to reduce, reuse, and refactor, but this should give you a jumping off point to experiment with your own mask types.