I am trying to make a textbox which filters user input to match specified type, so I can discard some of my validation logic.
For example, if I specify ushort
I want my textbox to only accept text changes which result in a valid ushort
value and nothing else.
This is what I've ended up with:
public abstract class CustomTextBox<T> : TextBox
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(T), typeof(CustomTextBox<T>), new FrameworkPropertyMetadata(default(T), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Inherits, OnValueChanged));
public T Value
{
get { return (T)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
{
if (_regex != null && !_regex.IsMatch(e.Text))
{
e.Handled = true;
}
else
{
base.OnPreviewTextInput(e);
}
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
if (String.IsNullOrEmpty(Text))
{
return;
}
T val;
if (!TryParse(out val))
{
var index = CaretIndex;
Text = _validText;
CaretIndex = index > 0 ? index - 1 : 0;
e.Handled = true;
}
else
{
Value = val;
_validText = Text;
}
}
protected CustomTextBox(string regexPattern = null)
{
if (!String.IsNullOrEmpty(regexPattern))
{
_regex = new Regex(regexPattern);
}
_validText = ToString(Value);
Loaded += OnTextboxLoaded;
}
protected override void OnLostFocus(RoutedEventArgs e)
{
ValidateText();
base.OnLostFocus(e);
}
protected virtual string ToString(T value)
{
return value.ToString();
}
protected abstract bool TryParse(out T val);
private readonly Regex _regex;
private string _validText;
private void ValidateText()
{
T val;
if (!TryParse(out val))
{
Text = ToString(Value);
}
}
private void OnTextboxLoaded(object sender, RoutedEventArgs e)
{
ValidateText();
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tb = (CustomTextBox<T>)d;
if (!e.OldValue.Equals(e.NewValue))
{
var str = tb.ToString((T)e.NewValue);
if (!str.Equals(tb.Text))
{
tb.Text = str;
}
}
}
}
ushort
implementation:
public sealed class UshortTextBox : CustomTextBox<ushort>
{
public UshortTextBox()
: base(@"^[0-9]$")
{
}
protected override bool TryParse(out ushort val)
{
return UInt16.TryParse(Text, out val);
}
}
I'm new to this whole custom-control-making, so if you see some rookie mistakes or ways to improve my code, please let me know.