Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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.

share|improve this question
up vote 2 down vote accepted

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)?

share|improve this answer
    
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. – Nikita Brizhak Jun 27 '13 at 6:33

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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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