By Michael J. Bullard
We programmed a masked textbox control with C# that will convert the mask into a regular
expression for each character in the field. It is designed to accept a user defined mask
or a regular expression that defines each character in the field. If you pass it the mask
as a string using the characters listed below, it will build the regular expression for you.
It then loads the regular expression into an array which is used to validate and control the
behavior of the field. There is an error provider message and a tooltip associated with the
control.
The m_mask property is used to set the mask variable. Masks are set up for each character.
Examples of valid mask strings and the regular expression they generate are detailed below:
# = [0-9]
9 = [\s0-9]
A = [A-Z]
a = [a-z]
B = [A-Za-z]
C = [A-Za-z0-9]
M
D
Y
/ * , . - : ( ) { } = +
Examples:
If you set YourTextBox.m_mask = "9,999,999,999.99" then the field is set to accept only numeric
characters and the 2 digit numbers will be less than 10 Trillion in length. Negative signs are
displayed to the left of the number. Commas will be inserted into the number when you leave
the field. Entering the field removes the commas and selects the entire number. Inserting
and backspacing work to help you edit existing numbers.
If you set YourTextBox.m_mask = "9,999,999,99#.##" then the behavior of the field will be the same
as above except that 0.00 is diplayed when you enter the field and the digits will be filled with
zeros, if blank, when you leave the field. The "9" characters will be a space if not filled in.
If you set YourTextBox.m_mask = "(###)###-####" then the literals appear in the textbox when you
enter a blank field. The textbox will accept numeric characters in the non literal positions.
Inserting and backspacing work to help you edit existing numbers.
If you set YourTextBox.m_mask = "AaBC" then the first position will accept all upper case letters,
the second position will accept all lower case letters, the third position will accept all upper
and lower case letters and the fourth position will accept all letters and all numbers.
If you set YourTextBox.m_mask = "[A-JL-Z][012]-[6-9]/[jklm]" the first position will accept all
upper case letters except K, the second position will accept only 0, 1 or 2, the third position will
be equal to a literal dash, the fourth position will accept numbers of 6 through 9, the fifth position
will be a literal forward slash and the last position will accept only lower case j, k, l or m.
The M, D and Y are designed to provide masks of "MM/DD/YYYY" or "MM/DD/YY". You could also rearrange
the date to show the year first. The first M position will accept only 0 or 1. The first D position
will accept only 0, 1, 2 or 3. The first Y position will accept only 0, 1 or 2 if there are four Y
characters. The second Y position will accept 0 through 5 and 9 if there are only two Y characters.
The second Y position will accept only 9 or 0 if there are four Y characters.
Additionally, an Error-Provider displays when the character entered does not match the regular
expression. The mouse-hover event can be used on the form to display the string YourTextBox.stip
as a tool-tip.
private void MaskText_Enter(object sender, System.EventArgs e)
{
isleaving = false;
EditRegx sd = (EditRegx) sender;
string s = this.Text;
int iSLength = s.Length;
//**public class ETMFein : ETMString
this.m_mask = "[A0-9][A0-9]-[0-9][0-9][0-9][0-9][0-9][0-9][0-9]";
alphas = literals = true;
sTip = "Enter a Federal ID number in ##-####### format";
//**public class ETMMedSupID : ETMString
this.m_mask = "[10][20][30][40][50][60][70]";
alphas = true;
sTip = "Enter a Med Sup ID";
//**public class ETMZipCode : ETMString
this.m_mask = "[A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][A-Z0-9][\s-][\s0-9][\s0-9][\s0-9][\s0-9]";
alphas = literals = true;
sTip = "Enter a Zip Code in #####-#### format";
//**public class ETMPhoneNumber : ETMString
this.m_mask = "[0-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]-[\s0-9][\s0-9][\s0-9][\s0-9][\s0-9][\s0-9][\s0-9]";
alphas = literals = true;
sTip = "Enter a Phone Number in ###-###-####-####### format";
//**public class ETMDecimal : ETMNumber
this.m_mask = "999,999,999,999";
isdigit = true;
sTip = "Enter a Number with No Decimals";
//**public class ETMPercent : ETMDecimal
this.m_mask = "999,999,999,999.###%";
isdigit = true;
sTip = "Enter a Percent with Three Decimals";
//**public class ETMDollar : ETMDecimal
this.m_mask = "9,999,999,999,99#.##";
isdecimal = true;
sTip = "Enter a Dollar Value with Two Decimals";
//**public class ETMNumber_1Dec : ETMDecimal
this.m_mask = "99,999,999,999,99#.#";
isdecimal = true;
sTip = "Enter a Number with One Decimal";
//**public class ETMNumber_2Dec : ETMDecimal
this.m_mask = "9,999,999,999,99#.##";
isdecimal = true;
sTip = "Enter a Number with Two Decimals";
*/
//*********************************************************************
int iMaskLength = sd.mask.Length;
if((isdecimal)||((isdigit)&&!(alphas)&&!(literals)))
{
string sdecdig = Trimit(s);
if(negpressed)
sdecdig = "-" + sdecdig;
this.Text = sdecdig;
}
this.SelectAll();
string strmask = this.mask;
int numLiteral = setMaskArray(strmask);
if((isdecimal)||((isdigit)&&!(alphas)&&!(literals)))
{
this.etmlength = this.regexArray.Count - numLiteral;
}
else
{
this.etmlength = this.regexArray.Count;
}
this.MaxLength = etmlength;
//set the cursor in the blank mask and set alphas and isdigit flags
//new alphas should position cursor after first literal.
//digits and decimals should position cursor before dot
//if digits and not alphas then right align
int inumdec = 0;
int cursorpos = 0;
int firstalpha = 0;
int literalpos = 0;
if((iSLength <1)&&(iMaskLength >=1)) //entering a blank cell, display mask
{
for(int i = 0; i=1)) //entering a blank cell, display mask
{
string sMaskText = setBlankMaskText();
this.Text = sMaskText;
if((isdecimal)&&(!alphas)&&(!literals))
{
cursorpos = this.Text.IndexOf(".");
}
sd.setCursor = cursorpos;
this.Select(sd.setCursor, 0);
}
if((iSLength >=1)&&(iMaskLength >=1)&&(alphas||literals)) //entering a non blank cell, display mask
{
string sMaskText = setMaskText(s);
this.Text = sMaskText;
this.SelectAll();
}
}// end of MaskText_Enter
// *****************************************************************
private void MaskText_MouseHover(object sender, System.EventArgs e)
{
EditRegx sd = (EditRegx) sender;
ToolTip toolTip1 = new ToolTip();
toolTip1.AutoPopDelay = 3000;
toolTip1.InitialDelay = 1000;
toolTip1.ReshowDelay = 2000;
toolTip1.ShowAlways = true;
toolTip1.SetToolTip(sd, this.sTip);
}
// ************************************************************
private string setBlankMaskText()
{
string sBlankMask = "";
string sWork1 = "";
string sWork2 = "";
int iArrayLen = this.regexArray.Count;
int i = 0;
int iFirstTime = 999;
if((alphas)||(literals))
{
for(i = 0; i < iArrayLen; i++)
{
sWork1 = (string)regexArray[i];
if((sWork1.StartsWith("L"))&&(sWork1.Substring(1,1)!= ","))
{
sWork2 = sWork2 + sWork1.Substring(1, sWork1.Length - 1);
}
else
{
sWork2 = sWork2 + " ";
if(iFirstTime == 999)
{
iFirstTime = i;
}
}
sBlankMask = sWork2;
if(iFirstTime != 999)
{
this.setCursor = iFirstTime;
}
else
{
this.setCursor = 0;
}
}//end of for loop
}//end of alphas
if((isdigit)&&!(isdecimal)&&!(alphas)&&!(literals))
{
sBlankMask = "";
setCursor = 0;
}
if((isdigit)&&(isdecimal)&&!(alphas)&&!(literals))
{
sBlankMask = ".";// + sdecimal;
setCursor = 0;
}
return sBlankMask;
}
//*****************************************
private string setMaskText(string x)
{
string sMask = "";
string sWork1 = "";
string sWork2 = "";
string s = x;
string sTrimmed = Trimit(s);
int iTrimLen = sTrimmed.Length;
int iArrayLen = this.regexArray.Count;
int ia = 0;
int it = 0;
if((alphas)||(literals))
{
for(ia = 0; ia < iArrayLen; ia++)
{
sWork1 = (string)regexArray[ia];
if(sWork1.StartsWith("L"))
{
sWork2 = sWork2 + sWork1.Substring(1, sWork1.Length - 1);
}
else if(it < iTrimLen)
{
sWork2 = sWork2 + sTrimmed.Substring(it,1);
it = it + 1;
}
else
{
sWork2 = sWork2 + " ";
}
}//end of for loop
sMask = sWork2;
}//end of alphas
if((isdigit)&&!(isdecimal)&&!(alphas)&&!(literals))
{
sMask = sTrimmed;
}
if((isdecimal)&&!(alphas)&&!(literals))
{
s = sTrimmed;
int whereisdot = sTrimmed.IndexOf(".");
if(whereisdot == -1)
{
sTrimmed = sTrimmed + ".";
whereisdot = sTrimmed.IndexOf(".");
}
//if(!dotpressed)
//{
// setCursor = whereisdot;
//}
//else
//{
// setCursor = sTrimmed.Length;
//}
sMask = sTrimmed;
}
return sMask;
} //end of setMaskText
//*******************************************
public int setMaskArray(string s)
{
int numL = 0;
int numM = 0;
int numD = 0;
int numY = 0;
int totY = 0;
string strmask = s;
int masklen = strmask.Length;
ArrayList workArray1 = new ArrayList();
string[] workArray2 = new string[masklen];
string strWork = "";
string strWork1 = "";
string strWork2 = "";
int x = 0;
for( int i = 0; i < masklen; i++)
{
strWork1 = "";
strWork2 = "";
strWork = strmask.Substring(i,1);
if(strWork == "#")
{
strWork1 = "[0-9]";
workArray1.Add(strWork1);
workArray2[x] = strWork1;
x = x + 1;
}
else if(strWork == "9")
{
strWork1 = "[\s0-9]";
workArray1.Add(strWork1);
workArray2[x] = strWork1;
x = x + 1;
}
else if(strWork == "A")
{
strWork1 = "[A-Z]";
workArray1.Add(strWork1);
workArray2[x] = strWork1;
x = x + 1;
}
else if(strWork == "a")
{
strWork1 = "[a-z]";
workArray1.Add(strWork1);
workArray2[x] = strWork1;
x = x + 1;
}
else if(strWork == "B")
{
strWork1 = "[A-Za-z]";
workArray1.Add(strWork1);
workArray2[x] = strWork1;
x = x + 1;
}
else if(strWork == "C")
{
strWork1 = "[A-Za-z0-9]";
workArray1.Add(strWork1);
workArray2[x] = strWork1;
x = x + 1;
}
else if(strWork == "M")
{
if(numM == 0)
{
strWork1 = "[0-1]";
numM = 1;
}
else if(numM >= 1)
{
strWork1 = "[0-9]";
numM = 2;
}
workArray1.Add(strWork1);
workArray2[x] = strWork1;
x = x + 1;
}
else if(strWork == "D")
{
if(numD == 0)
{
strWork1 = "[0-3]";
numD = 1;
}
else if(numD >= 1)
{
strWork1 = "[0-9]";
numD = 2;
}
workArray1.Add(strWork1);
workArray2[x] = strWork1;
x = x + 1;
}
else if(strWork == "Y")
{
if(numY == 0)//Y
{
totY = masklen - i;
if(totY == 2)
{
strWork1 = "[0-59]";
}
else
{
strWork1 = "[0-2]";
}
numY = 1;
}
else if(numY == 1)//yY
{
if(totY == 2)
{
strWork1 = "[0-9]";
}
else
{
strWork1 = "[09]";
}
numY = 2;
}
else //yyYorY
{
strWork1 = "[0-9]";
numY = numY + 1;
}
workArray1.Add(strWork1);
workArray2[x] = strWork1;
x = x + 1;
}
else if((strWork == "/")||(strWork == "*")||(strWork == ",")||(strWork == ".")
||(strWork == "-")||(strWork == ":")||(strWork == "(")||(strWork == ")")
||(strWork == "{")||(strWork == "}")||(strWork == "=")||(strWork == "+"))
{
strWork1 = strWork;
strWork1 = "L" + strWork1;
workArray1.Add(strWork1);
workArray2[x] = strWork1;
x = x + 1;
numL = numL + 1;
}
else if(strWork == "[")
{
int remaining = masklen - i;
int test = 0;
for(int r = 0; r < remaining; r++)
{
test = i + r;
strWork1 = strmask.Substring(test,1);
strWork2 = strWork2 + strWork1;
if(strWork1 == "]")
{
i = test;
break;
}
}
if(strWork2.EndsWith("]"))
{
workArray1.Add(strWork2);
workArray2[x] = strWork2;
x = x + 1;
}
else
{
MessageBox.Show("error reading Mask, no closing bracket ]");
}
//i = test; //already did this
}//end of else if starts with "["
}//end of big For Loop
int j = workArray1.Count;
int k = workArray2.Length;
string[] workArray = new string[j];
for(int y = 0; y < workArray1.Count; y++)
{
workArray[y] = (string)workArray1[y];
}
this.regexArray.Clear();
this.regexArray.AddRange(workArray);
int l = this.regexArray.Count;
return numL;
}//end of setMaskArray
//*******************************************
private string GetKeyRegEx(int startcaret)
{
int iPos = startcaret;
int iSizeRegExArray = this.regexArray.Count;//not set to instance of object, called from text changed
string sReturnRegEx = "";
if((iPos < iSizeRegExArray)&&(iPos != -1))
{
sReturnRegEx = (string)regexArray[iPos];
}
else
{
sReturnRegEx = "TooFar";//this.regexArray[iSizeRegExArray - 1];
}
return sReturnRegEx;
}