Introduction
This article describes how to create a custom WPF textbox control which permits the user to input only those characters based on the data type.
For example, for int
data type, only numeric characters are permitted. Also there is a feature to define a Regular Expression mask for the textbox.
Using the code
First we need to create a dependency property which will allow us to set the data type.
public static DependencyProperty DataTypeProperty = DependencyProperty.Register("DataType",
typeof(string), typeof(MaskTextBox), new PropertyMetadata("string"));
public string DataType
{
get { return (string)GetValue(DataTypeProperty); }
set { SetValue(DataTypeProperty, value); }
}
...
In this sample, we have used three data types: int
, float
, and RegEx
. For RegEx
, we will also require another
dependency property called RegEx
which will enable us to specify the Regular Expression.
public static DependencyProperty RegExProperty = DependencyProperty.Register("RegEx",
typeof(string), typeof(MaskTextBox), new PropertyMetadata("string"));
public string RegEx
{
get { return (string)GetValue(RegExProperty); }
set { SetValue(RegExProperty, value); }
}
...
Next, we write a method that will validate the input depending on the data type. This method uses the TryParse
method to verify the int
and float
data types. For RegEx
, we use the IsMatch
method to verify the input with the Regular Expression specified in the Regex
property.
This method is used mainly when data is pasted into the textbox.
private Boolean IsDataValid(IDataObject data)
{
Boolean isValid = false;
if (data != null)
{
String text = data.GetData(DataFormats.Text) as String;
if (!String.IsNullOrEmpty(text == null ? null : text.Trim()))
{
switch (DataType)
{
case "INT":
Int32 result = -1;
if (Int32.TryParse(text.Trim(), out result))
{
if (result > 0)
{
isValid = true;
}
}
break;
case "FLOAT":
float floatResult = -1;
if (float.TryParse(text.Trim(), out floatResult))
{
if (floatResult > 0)
{
isValid = true;
}
}
break;
case "RegEx":
if (System.Text.RegularExpressions.Regex.IsMatch(text, RegEx))
{
isValid = true;
}
break;
}
}
}
return isValid;
}
protected override void OnDrop(DragEventArgs e)
{
e.Handled = !IsDataValid(e.Data);
base.OnDrop(e);
}
protected override void OnDragOver(DragEventArgs e)
{
if (!IsDataValid(e.Data))
{
e.Handled = true;
e.Effects = DragDropEffects.None;
}
base.OnDragEnter(e);
}
EventManager.RegisterClassHandler(
typeof(MaskTextBox),
DataObject.PastingEvent,
(DataObjectPastingEventHandler)((sender, e) =>
{
if (!IsDataValid(e.DataObject))
{
DataObject data = new DataObject();
data.SetText(String.Empty);
e.DataObject = data;
e.Handled = false;
}
}));
...
We also need to restrict the user from entering invalid characters. For this, we override the OnPreviewTextInput
method of the WPF textbox.
In this method, we generate the final text by inserting the new character at the caret index. This ensures that even if the user types the new character in between
or at the beginning of the existing characters, the validation does not fail. Next, the text is validated according to the data type specified.
If the validation fails, e.Handled
is set to true and the user action will get cancelled.
string text = this.Text;
text = text.Insert(this.CaretIndex, e.Text);
switch (DataType)
{
case "INT":
Int32 result = -1;
if (!Int32.TryParse(text.Trim(), out result))
{
if (!text.Equals("-"))
e.Handled = true;
}
break;
case "FLOAT":
float floatResult = -1;
if (!float.TryParse(text.Trim(), out floatResult))
{
if (!text.Equals("-"))
e.Handled = true;
}
break;
case "RegEx":
if (!System.Text.RegularExpressions.Regex.IsMatch(text, RegEx))
{
e.Handled = true;
}
break;
}
...
Note that in the above code, for cases "INT
" and "FLOAT
", there is an if
condition. This is because for data type int
,
if the user has to enter -5, the first character that the user will type is "-". However the method Int32.TryParse
will return false for "-".
Hence the check is required so that the validation does not fail.
We also need to take care that the user does not input just the "-" sign within the textbox. For this, we can add a handler to the LostFocusEvent
of the textbox. So whenever the textbox loses focus, the text is verified and if it is just "-", the text is cleared.
this.AddHandler(MaskTextBox.LostFocusEvent, new RoutedEventHandler(LostFocusEventHandler));
public void LostFocusEventHandler(object sender, RoutedEventArgs e)
{
if (this.Text == "-")
this.Text = string.Empty;
}
...
Also, Int32.TryParse
does not fail the validation if the user inputs a space. We can handle this in the PreviewKeyDownEvent
of the textbox
and set e.Handled = true
if a space is input.
this.AddHandler(MaskTextBox.PreviewKeyDownEvent,
new RoutedEventHandler(PreviewKeyDownEventHandler));
public void PreviewKeyDownEventHandler(object sender, RoutedEventArgs e)
{
KeyEventArgs ke = e as KeyEventArgs;
if (ke.Key == Key.Space)
{
ke.Handled = true;
}
}
...