5
\$\begingroup\$

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.

\$\endgroup\$
3
\$\begingroup\$

I think you can avoid inheritance here ("Favor Composition Over Inheritance") and use Attached Behavior instead.

Also, did you take a look at MaskedTextBox from Extended WPF Toolkit (http://wpftoolkit.codeplex.com/wikipage?title=MaskedTextBox)?

\$\endgroup\$
  • \$\begingroup\$ Yes, i did look at the masked textbox, but it is not quite what i needed. It filters input, based on string format specified. And i wanted to filter input, based on parsing logic of specified type. But i kind of like your idea about attached behaviors. I've never made my own, but i'll certainly give it a try. Hopefully i will be able to keep my implementation generic. Thx for your answer. \$\endgroup\$ – Nikita B Jun 27 '13 at 6:33
2
\$\begingroup\$

I think what your code is missing the most is documentation and some better naming. The constructor takes a regex string (with a very generic name regexPattern) and you also have to override TryParse(). There is absolutely no indication of why both are needed, without looking at the source of CustomTextBox.

Also, the name CustomTextBox pretty much doesn't say anything, I think you should think of a better name for it, one that actually describes how is the type different from normal TextBox.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.