How do I make a textbox that only accepts numbers?

后端 未结 30 1679
梦如初夏
梦如初夏 2020-11-21 06:05

I have a windows forms app with a textbox control that I want to only accept integer values. In the past I\'ve done this kind of validation by overloading the KeyPress event

30条回答
  •  青春惊慌失措
    2020-11-21 06:49

    Here is a simple standalone Winforms custom control, derived from the standard TextBox, that allows only System.Int32 input (it could be easily adapted for other types such as System.Int64, etc.). It supports copy/paste operations and negative numbers:

    public class Int32TextBox : TextBox
    {
        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            base.OnKeyPress(e);
    
            NumberFormatInfo fi = CultureInfo.CurrentCulture.NumberFormat;
    
            string c = e.KeyChar.ToString();
            if (char.IsDigit(c, 0))
                return;
    
            if ((SelectionStart == 0) && (c.Equals(fi.NegativeSign)))
                return;
    
            // copy/paste
            if ((((int)e.KeyChar == 22) || ((int)e.KeyChar == 3))
                && ((ModifierKeys & Keys.Control) == Keys.Control))
                return;
    
            if (e.KeyChar == '\b')
                return;
    
            e.Handled = true;
        }
    
        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            const int WM_PASTE = 0x0302;
            if (m.Msg == WM_PASTE)
            {
                string text = Clipboard.GetText();
                if (string.IsNullOrEmpty(text))
                    return;
    
                if ((text.IndexOf('+') >= 0) && (SelectionStart != 0))
                    return;
    
                int i;
                if (!int.TryParse(text, out i)) // change this for other integer types
                    return;
    
                if ((i < 0) && (SelectionStart != 0))
                    return;
            }
            base.WndProc(ref m);
        }
    

    Update 2017: My first answer has some issues:

    • you can type something that's longer than an integer of a given type (for example 2147483648 is greater than Int32.MaxValue);
    • more generally, there's no real validation of the result of what has been typed;
    • it only handles int32, you'll have to write specific TextBox derivated control for each type (Int64, etc.)

    So I came up with another version that's more generic, that still supports copy/paste, + and - sign, etc.

    public class ValidatingTextBox : TextBox
    {
        private string _validText;
        private int _selectionStart;
        private int _selectionEnd;
        private bool _dontProcessMessages;
    
        public event EventHandler TextValidating;
    
        protected virtual void OnTextValidating(object sender, TextValidatingEventArgs e) => TextValidating?.Invoke(sender, e);
    
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
            if (_dontProcessMessages)
                return;
    
            const int WM_KEYDOWN = 0x100;
            const int WM_ENTERIDLE = 0x121;
            const int VK_DELETE = 0x2e;
    
            bool delete = m.Msg == WM_KEYDOWN && (int)m.WParam == VK_DELETE;
            if ((m.Msg == WM_KEYDOWN && !delete) || m.Msg == WM_ENTERIDLE)
            {
                DontProcessMessage(() =>
                {
                    _validText = Text;
                    _selectionStart = SelectionStart;
                    _selectionEnd = SelectionLength;
                });
            }
    
            const int WM_CHAR = 0x102;
            const int WM_PASTE = 0x302;
            if (m.Msg == WM_CHAR || m.Msg == WM_PASTE || delete)
            {
                string newText = null;
                DontProcessMessage(() =>
                {
                    newText = Text;
                });
    
                var e = new TextValidatingEventArgs(newText);
                OnTextValidating(this, e);
                if (e.Cancel)
                {
                    DontProcessMessage(() =>
                    {
                        Text = _validText;
                        SelectionStart = _selectionStart;
                        SelectionLength = _selectionEnd;
                    });
                }
            }
        }
    
        private void DontProcessMessage(Action action)
        {
            _dontProcessMessages = true;
            try
            {
                action();
            }
            finally
            {
                _dontProcessMessages = false;
            }
        }
    }
    
    public class TextValidatingEventArgs : CancelEventArgs
    {
        public TextValidatingEventArgs(string newText) => NewText = newText;
        public string NewText { get; }
    }
    

    For Int32, you can either derive from it, like this:

    public class Int32TextBox : ValidatingTextBox
    {
        protected override void OnTextValidating(object sender, TextValidatingEventArgs e)
        {
            e.Cancel = !int.TryParse(e.NewText, out int i);
        }
    }
    

    or w/o derivation, use the new TextValidating event like this:

    var vtb = new ValidatingTextBox();
    ...
    vtb.TextValidating += (sender, e) => e.Cancel = !int.TryParse(e.NewText, out int i);
    

    but what's nice is it works with any string, and any validation routine.

提交回复
热议问题