whit
whit

Reputation: 127

MaxLength Property breaks in my custom TextBox

For my WPF apps I have created a couple custom controls based on a TextBox. These include NumericTextBox, WatermarkTextBox, and ReturnTextBox.

Numeric and Watermark inherit from ReturnTextBox, which inherits from TextBox.

When I go to use any of my custom textboxes they work great. The one problem seems to be NumericTextBox and the MaxLength property. The property is now ignored and does not work. There is no code in any of my custom controls overriding or messing with the MaxLength property.

When I use MaxLength on my ReturnTextBox, it works just as you would expect:

<ui:ReturnTextBox MaxLength="35" Width="500" Background="LightYellow" Text="{Binding BrRptCorpName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" TabIndex="2" />

However when I use the property on my NumericTextBox it is ignored and does not work:

<ui:NumericTextBox MaxLength="9" Background="LightYellow" Text="{Binding BrRptAmt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" TabIndex="1" />

Can anyone help me figure out why MaxLength stops working? Is it because NumericTextBox does not directly inherit from TextBox? Should I be overriding MaxLength property in ReturnTextBox so it can be used by Numeric?

UPDATED WITH CODE

ReturnTextBox class:

public class ReturnTextBox : TextBox
{
    static ReturnTextBox()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ReturnTextBox), new FrameworkPropertyMetadata(typeof(ReturnTextBox)));
    }

    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
        if (e.Key == Key.Return)
        {
            e.Handled = true;
            MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        }
        base.OnPreviewKeyDown(e);
    }
}

NumericTextBox

public class NumericTextBox : ReturnTextBox
{
    #region Base Class Overrides

    static NumericTextBox()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericTextBox), new FrameworkPropertyMetadata(typeof(NumericTextBox)));
    }

    public static readonly DependencyProperty TypeProperty = DependencyProperty.Register("Type",
           typeof(NumericType), typeof(NumericTextBox), new UIPropertyMetadata (NumericType.Integer));
    public static readonly DependencyProperty SelectAllOnGotFocusProperty = DependencyProperty.Register("SelectAllOnGotFocus", 
           typeof(bool), typeof(NumericTextBox), new PropertyMetadata(false));

    [Description("Numeric Type of the TextBox"), Category("Common Properties")]
    public NumericType Type
    {
        get { return (NumericType)GetValue(TypeProperty); }
        set { SetValue(TypeProperty, value); }
    }

    [Description("Select text on focus"), Category("Common Properties")]
    public bool SelectAllOnGotFocus
    {
        get
        {
            return (bool)GetValue(SelectAllOnGotFocusProperty);
        }
        set
        {
            SetValue(SelectAllOnGotFocusProperty, value);
        }
    }

    protected override void OnPreviewTextInput(TextCompositionEventArgs e)
    {
        Text = ValidateValue(Type, Text);
        bool isValid = IsSymbolValid(Type, e.Text);
        e.Handled = !isValid;
        if (isValid)
        {
            int caret = CaretIndex;
            string text = Text;
            bool textInserted = false;
            int selectionLength = 0;

            if (SelectionLength > 0)
            {
                text = text.Substring(0, SelectionStart) + text.Substring(SelectionStart + SelectionLength);
                caret = SelectionStart;
            }
            if (e.Text == NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
            {
                while (true)
                {
                    int ind = text.IndexOf(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator);
                    if (ind == -1)
                        break;
                    text = text.Substring(0, ind) + text.Substring(ind + 1);
                    if (caret > ind)
                        caret--;
                }
                if (caret == 0)
                {
                    text = "0" + text;
                    caret++;
                }
                else
                {
                    if (caret == 1 && string.Empty + text[0] == NumberFormatInfo.CurrentInfo.NegativeSign)
                    {
                        text = NumberFormatInfo.CurrentInfo.NegativeSign + "0" + text.Substring(1);
                        caret++;
                    }
                }

                if (caret == text.Length)
                {
                    selectionLength = 1;
                    textInserted = true;
                    text = text + NumberFormatInfo.CurrentInfo.NumberDecimalSeparator + "0";
                    caret++;
                }
            }
            else if (e.Text == NumberFormatInfo.CurrentInfo.NegativeSign)
            {
                textInserted = true;
                if (Text.Contains(NumberFormatInfo.CurrentInfo.NegativeSign))
                {
                    text = text.Replace(NumberFormatInfo.CurrentInfo.NegativeSign, string.Empty);
                    if (caret != 0)
                        caret--;
                }
                else
                {
                    text = NumberFormatInfo.CurrentInfo.NegativeSign + Text;
                    caret++;
                }
            }

            if (!textInserted)
            {
                text = text.Substring(0, caret) + e.Text +
                    ((caret < Text.Length) ? text.Substring(caret) : string.Empty);

                caret++;
            }

            try
            {
                double val = Convert.ToDouble(text);
                double newVal = val;//ValidateLimits(GetMinimumValue(_this), GetMaximumValue(_this), val);
                if (val != newVal)
                {
                    text = newVal.ToString();
                }
                else if (val == 0)
                {
                    if (!text.Contains(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator))
                        text = "0";
                }
            }
            catch
            {
                text = "0";
            }

            while (text.Length > 1 && text[0] == '0' && string.Empty + text[1] != NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
            {
                text = text.Substring(1);
                if (caret > 0)
                    caret--;
            }

            while (text.Length > 2 && string.Empty + text[0] == NumberFormatInfo.CurrentInfo.NegativeSign && text[1] == '0' && string.Empty + text[2] != NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
            {
                text = NumberFormatInfo.CurrentInfo.NegativeSign + text.Substring(2);
                if (caret > 1)
                    caret--;
            }

            if (caret > text.Length)
                caret = text.Length;

            Text = text;
            CaretIndex = caret;
            SelectionStart = caret;
            SelectionLength = selectionLength;
            e.Handled = true;
        }

        base.OnPreviewTextInput(e);
    }

    private static string ValidateValue(NumericType type, string value)
    {
        if (string.IsNullOrEmpty(value))
            return string.Empty;

        value = value.Trim();
        switch (type)
        {
            case NumericType.Integer:
                try
                {
                    Convert.ToInt64(value);
                    return value;
                }
                catch
                {
                }
                return string.Empty;

            case NumericType.Decimal:
                try
                {
                    Convert.ToDouble(value);
                    return value;
                }
                catch
                {
                }
                return string.Empty;
        }

        return value;
    }

    private static bool IsSymbolValid(NumericType type, string str)
    {
        switch (type)
        {
            case NumericType.Decimal:
                if (str == NumberFormatInfo.CurrentInfo.NegativeSign ||
                    str == NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
                    return true;
                break;

            case NumericType.Integer:
                if (str == NumberFormatInfo.CurrentInfo.NegativeSign)
                    return true;
                break;

            case NumericType.Any:
                return true;
        }

        if (type.Equals(NumericType.Integer) || type.Equals(NumericType.Decimal))
        {
            foreach (char ch in str)
            {
                if (!Char.IsDigit(ch))
                    return false;
            }

            return true;
        }

        return false;
    }

    protected override void OnGotFocus(RoutedEventArgs e)
    {
        base.OnGotFocus(e);

        if (SelectAllOnGotFocus)
            SelectAll();
    }

    protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        if (!IsKeyboardFocused && SelectAllOnGotFocus)
        {
            e.Handled = true;
            Focus();
        }

        base.OnPreviewMouseLeftButtonDown(e);
    }

As you can see I never override or make any changes to the MaxLength property in either of these two controls. However the MaxLength works with the ReturnTextBox and does not work with the NumericTextBox.

Thank you for your help!

Upvotes: 0

Views: 1039

Answers (1)

Zache
Zache

Reputation: 1043

e.Handled = !isValid and if(isValid) { e.Handled = true } are your problem. When you use the PreviewSomeEvent the they are tunneling. That means they go from the root and down to the source that caused the event. Setting the Handled property on the event means that other handlers in the events route won't be raised. MaxLength is something defined on the TextBox or at least on something more "primite" than your NumericTextBox.

What happens is that if the users input fail your validation, the event won't be raised "deeper down the tunnel" to check the length: e.Handled = !isValid. And if it passes your validation it wont be raised either because e.Handled = true.

Perhaps a useful analogy can be overriding a method and calling base or not.

For more on events check here

Upvotes: 1

Related Questions