Dealing with invalid input in NumericUpDown

 

What is the NumericUpDown control?

System.Windows.Forms.NumericUpDown is a control that lets the user input a number, or use up/down buttons to change a number that’s currently entered. It hides the Text property it inherits from Control, showing a decimal Value property instead.

Why is the Text property hidden?

At any given time, the text that’s put in may be invalid, meaning that it doesn’t represent the value at all. It would be common for a user to select all the text in the control and type in a new value. The new value might be “-3”, where the value isn’t complete until the user types “3”. Or the value might represent a year, where the valid range might be set to 1900-2200. The user types “1997”, but the value isn’t valid until the user is done putting in the value.

The text in the control seems to update at funny times.

The NumericUpDown updates its text when it knows the user is done putting in text. When the user moves focus off the control, it knows that the user is done and the text updates. When the Value property is retrieved, the text also updates. Depending on your UI, this may be a problem: users may think they’ve put in a valid value and click OK, only to find that the value they had typed was ignored and another value was used instead.

I could deal with an invalid value more effectively than that. I want to know if the user put in an invalid value.

The most common request here is an indication of whether the text represents a valid value. There are a bunch of variants on the theme that you might want, such as what the pending value is, what the pending value would be if it were valid, when the value is out of minimum/maximum range, etc. This code will give an indication of whether the input value is a number that is in the minimum/maximum range, leaving the variants as an exercise. I thought of putting some in, but you really end up with several properties that are very close in meaning, and it becomes hard to make the class make sense as a whole, so I just decided to go with the most common.

using System;

using System.Collections.Generic;

using System.Text;

using System.Windows.Forms;

using System.Globalization;

using System.ComponentModel;

namespace NumericUpDownTest

{

    class ExtendedNumericUpdown : NumericUpDown

    {

        public event EventHandler HasValidValueChanged;

        private bool hasValidValue = false;

        [Browsable(false)]

        public bool HasValidValue

        {

            get

            {

                decimal value;

                if (Hexadecimal)

                {

                    int intValue;

                    if (!int.TryParse(Text, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out intValue))

                    {

                        return false;

                    }

                    value = (decimal)intValue;

                }

                else

                {

                    NumberStyles numberStyle = NumberStyles.Any & ~NumberStyles.AllowCurrencySymbol;

                 if (!decimal.TryParse(Text, numberStyle, CultureInfo.CurrentCulture, out value))

                    {

                        return false;

                    }

                }

                return value >= Minimum && value <= Maximum;

           }

        }

        protected override void OnTextChanged(EventArgs e)

        {

            base.OnTextChanged(e);

            bool newHasValidValue = HasValidValue;

            if (newHasValidValue != hasValidValue)

            {

                hasValidValue = newHasValidValue;

                OnHasValidValueChanged();

            }

        }

        protected virtual void OnHasValidValueChanged()

        {

            if (HasValidValueChanged != null)

            {

                HasValidValueChanged(this, EventArgs.Empty);

            }

        }

    }

}