Peter
Peter

Reputation: 38455

Silverlight numeric textbox?

I tryed to create my own numeric textbox here is my code:

public class NumericTextBox : TextBox
{

    public NumericTextBox()
        : base()
    {
        this.Text = "0";
    }

    private void HandleKeyEvent(KeyEventArgs e)
    {
        e.Handled = true;
        if ((Keyboard.Modifiers & ModifierKeys.Alt) != 0)
        {
            return;
        }
        if (e.Key == Key.Back || e.Key == Key.Delete || e.Key == Key.Left || e.Key == Key.Right ||
            e.Key == Key.D0 || e.Key == Key.D1 || e.Key == Key.D2 || e.Key == Key.D3 || e.Key == Key.D4 || e.Key == Key.D5 || e.Key == Key.D6 ||
            e.Key == Key.D7 || e.Key == Key.D8 || e.Key == Key.D9 ||
            e.Key == Key.NumPad0 || e.Key == Key.NumPad1 || e.Key == Key.NumPad2 || e.Key == Key.NumPad3 || e.Key == Key.NumPad4 || e.Key == Key.NumPad5 || e.Key == Key.NumPad6 ||
            e.Key == Key.NumPad7 || e.Key == Key.NumPad8 || e.Key == Key.NumPad9)
        {
            e.Handled = false;
        }
        else if ((e.Key == Key.Subtract || (e.Key == Key.Unknown && e.PlatformKeyCode == 189)) && base.SelectionStart == 0 && (base.Text.Length == 0 || base.Text[0] != '-'))
        {
            e.Handled = false;
        }
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        HandleKeyEvent(e);
        base.OnKeyDown(e);
    }

    protected override void OnKeyUp(KeyEventArgs e)
    {
        HandleKeyEvent(e);
        base.OnKeyUp(e);
    }
}

everything works like supposed but if you press alt and some numbers it creates the ascii symbol corresponding to the number.. is there any way to block an "alt + number combination? it seems that alt + key just gets entered without going threw OnKeyUp or OnKeyDown...

Upvotes: 2

Views: 12011

Answers (5)

picrap
picrap

Reputation: 1254

Here's an alternative, requiring only an attached property and the following code. First, the code:

public enum InputType
{
    PositiveInteger,
    PositiveDecimal,
    PositiveNullableInteger,
    PositiveNullableDecimal,
}

public static class Input
{
    public static readonly DependencyProperty TypeProperty =
        DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(TextBox),
                                            new PropertyMetadata(default(InputType), OnTypeChanged));

    public static void SetType(TextBox element, InputType value)
    {
        element.SetValue(TypeProperty, value);
    }

    public static InputType GetType(TextBox element)
    {
        return (InputType)element.GetValue(TypeProperty);
    }

    private class TextSelection
    {
        public string Text { get; private set; }

        public int SelectionStart { get; private set; }

        public int SelectionLength { get; private set; }

        public TextSelection(string text, int selectionStart, int selectionLength)
        {
            Text = text;
            SelectionStart = selectionStart;
            SelectionLength = selectionLength;
        }
    }

    private static readonly DependencyProperty PreviousTextSelectionProperty =
        DependencyProperty.RegisterAttached("PreviousTextSelection", typeof(TextSelection),
        typeof(TextBox), new PropertyMetadata(default(TextSelection)));

    private static void SetPreviousTextSelection(TextBox element, TextSelection value)
    {
        element.SetValue(PreviousTextSelectionProperty, value);
    }

    private static TextSelection GetPreviousTextSelection(TextBox element)
    {
        return (TextSelection)element.GetValue(PreviousTextSelectionProperty);
    }

    private static void OnTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (UIApplication.DesignMode)
            return;

        var textBox = (TextBox)d;
        textBox.TextChanged += OnTextChanged;
        textBox.SelectionChanged += OnSelectionChanged;
    }

    /// <summary>
    /// Determines whether the specified text is valid.
    /// </summary>
    /// <param name="text">The text.</param>
    /// <param name="inputType">Type of the input.</param>
    /// <returns>
    ///   <c>true</c> if the specified text is valid; otherwise, <c>false</c>.
    /// </returns>
    private static bool IsValid(string text, InputType inputType)
    {
        switch (inputType)
        {
        case InputType.PositiveInteger:
            int i;
            return int.TryParse(text, out i);
        case InputType.PositiveDecimal:
            decimal d;
            return decimal.TryParse(text, out d) && d >= 0;
        case InputType.PositiveNullableInteger:
            return text.IsNullOrEmpty() || IsValid(text, InputType.PositiveInteger);
        case InputType.PositiveNullableDecimal:
            return text.IsNullOrEmpty() || IsValid(text, InputType.PositiveDecimal);
        default:
            throw new ArgumentOutOfRangeException("inputType");
        }
    }

    private static void OnTextChanged(object sender, TextChangedEventArgs e)
    {
        var textBox = (TextBox)sender;
        var inputType = GetType(textBox);

        if (IsValid(textBox.Text, inputType))
        {
            SetPreviousTextSelection(textBox, new TextSelection(textBox.Text, textBox.SelectionStart, textBox.SelectionLength));
        }
        else
        {
            var textSelection = GetPreviousTextSelection(textBox);
            if (textSelection == null)
            {
                textBox.Text = "";
            }
            else
            {
                textBox.Text = textSelection.Text;
                textBox.SelectionStart = textSelection.SelectionStart;
                textBox.SelectionLength = textSelection.SelectionLength;
            }
        }
    }

    private static void OnSelectionChanged(object sender, RoutedEventArgs e)
    {
        var textBox = (TextBox)sender;
        SetPreviousTextSelection(textBox, new TextSelection(textBox.Text, textBox.SelectionStart, textBox.SelectionLength));
    }
}

Then use it in your xaml code (the "ui:" namespace requires to be resolved, but hey, you still have to do your homeworks :)):

<TextBox Text="{Binding MyText, Mode=TwoWay}" ui:Input.Type="PositiveNullableDecimal" />

So basically, the extender memorizes the last valid state (text+selection) and reverts it if the new result is invalid. The enum InputType can be of course extended.

Upvotes: 2

Peter
Peter

Reputation: 38455

I got it working by using the TextChanged event here is my code...

public class NumericTextBox : TextBox
{

    int value;

    public NumericTextBox()
        : base()
    {
        this.Text = "0";
        this.TextChanged += new TextChangedEventHandler(NumericTextBox_TextChanged);
    }

    void NumericTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        int selectionStart = base.SelectionStart;
        bool changed = false;
        List<char> charList = new List<char>();
        for (int i = 0; i < base.Text.Length; i++)
        {
            if (IsValidChar(base.Text[i], i))
            {
                charList.Add(base.Text[i]);
            }
            else
            {
                if (selectionStart >= i)
                {
                    selectionStart--;
                }
                changed = true;
            }
        }
        if (changed)
        {
            string text = new string(charList.ToArray());
            this.Text = text;
            this.SelectionStart = selectionStart;
        }
        int newValue;
        if (!int.TryParse(this.Text, out newValue))
        {
            this.Text = value.ToString();
            this.SelectionStart = this.Text.Length;
        }
        else
        {
            value = newValue;
        }
    }

    private bool IsValidChar(char c, int index)
    {
        return ((c == '-' && index == 0) || c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7' || c == '8' || c == '9');
    }

    private void HandleKeyEvent(KeyEventArgs e)
    {
        e.Handled = true;
        if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
        {
            e.Handled = false;
        }
        if (e.Key == Key.Back || e.Key == Key.Delete || e.Key == Key.Left || e.Key == Key.Right ||
            e.Key == Key.D0 || e.Key == Key.D1 || e.Key == Key.D2 || e.Key == Key.D3 || e.Key == Key.D4 || e.Key == Key.D5 || e.Key == Key.D6 ||
            e.Key == Key.D7 || e.Key == Key.D8 || e.Key == Key.D9 ||
            e.Key == Key.NumPad0 || e.Key == Key.NumPad1 || e.Key == Key.NumPad2 || e.Key == Key.NumPad3 || e.Key == Key.NumPad4 || e.Key == Key.NumPad5 || e.Key == Key.NumPad6 ||
            e.Key == Key.NumPad7 || e.Key == Key.NumPad8 || e.Key == Key.NumPad9)
        {
            e.Handled = false;
        }
        else if ((e.Key == Key.Subtract || (e.Key == Key.Unknown && e.PlatformKeyCode == 189)) && base.SelectionStart == 0 && (base.Text.Length == 0 || base.Text[0] != '-'))
        {
            e.Handled = false;
        }
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        HandleKeyEvent(e);
        base.OnKeyDown(e);
    }

    protected override void OnKeyUp(KeyEventArgs e)
    {
        HandleKeyEvent(e);
        base.OnKeyUp(e);
    }
}

Upvotes: 1

AnthonyWJones
AnthonyWJones

Reputation: 189439

is there any way to block an "alt + number combination?

Not really. My advice would be don't bother and see what happens.

TBH if you really want to build a Numeric input control you shouldn't be deriving from TextBox. You would derive from Control and place a TextBox in the default control template of your new control.

In fact to be really honest I'd just used the NumericUpDown in the Toolkit.

Upvotes: 1

Dan Auclair
Dan Auclair

Reputation: 3617

Are you just trying to prevent non-numeric text entry? A different approach described in this blog post is to create a text box filter that can be added to a normal TextBox as an attached dependency property. Either way, you will still have to validate the data after it is entered as the user could paste invalid data.

Upvotes: 0

Ragepotato
Ragepotato

Reputation: 1630

Short and sweet - the Alt key is handled at a lower level then your program.

This article describes the problem in more detail while this link provides some c++ code that could help you if you really wanted to get around this issue.

Upvotes: -1

Related Questions