Click here to Skip to main content
Click here to Skip to main content

Simple Numeric TextBox

By , 9 Nov 2008
 

Overview

This is a simple extension/restriction of the System.Windows.Forms.TextBox component. Only digits can be entered into the control. Pasting is also checked, and if the text contains other characters, then it is cancelled. I found many examples on various websites, but none that I found were suitable for my purpose. Either they allowed or enforced things I didn't want (see 'What I haven't done' below), or they didn't completely handle all the standard keyboard and mouse functions (see 'Surely this is simple' below), for example, allowing the Home key or Shift+End etc.

Language

The source code is in C#, .NET 2.0, VS2008. I've included the compiled DLL so VB users can use this control.

What I haven't done

I haven't added any range or bounds control - it's a text box, not an int/double/decimal... box. There is no support for number separators, currency symbols, or even the - sign. They weren't required for the implementation I needed. If you want to add them, it shouldn't be too difficult.

Surely this is simple, you just...

That's what I thought too, until about two minutes into coding this! There's actually quite a lot that we do all the time with the text box, but never give it a second thought. As well as digits, we need to allow edit key combinations and navigation/selection keys and combinations. Pasting can be done either by keyboard or by mouse actions, so handling key events for this isn't sufficient.

The code

The interesting parts of the code are in the overridden OnKeyDown and the private CheckPasteValid methods.

OnKeyDown

I've simply built bools for numeric, edit, and navigation keys so I can test one value for each group. Ctrl+A sometimes needs separate handling, so I created one for that too.

protected override void OnKeyDown(KeyEventArgs e)
{
    bool result = true;

    bool numericKeys = (
        ((e.KeyCode >= Keys.D0 && e.KeyCode <= Keys.D9) ||
        (e.KeyCode >= Keys.NumPad0 && e.KeyCode <= Keys.NumPad9))
        && e.Modifiers != Keys.Shift);

    bool ctrlA = e.KeyCode == Keys.A && e.Modifiers == Keys.Control;

    bool editKeys = (
        (e.KeyCode == Keys.Z && e.Modifiers == Keys.Control) ||
        (e.KeyCode == Keys.X && e.Modifiers == Keys.Control) ||
        (e.KeyCode == Keys.C && e.Modifiers == Keys.Control) ||
        (e.KeyCode == Keys.V && e.Modifiers == Keys.Control) ||
        e.KeyCode == Keys.Delete ||
        e.KeyCode == Keys.Back);

    bool navigationKeys = (
        e.KeyCode == Keys.Up ||
        e.KeyCode == Keys.Right ||
        e.KeyCode == Keys.Down ||
        e.KeyCode == Keys.Left ||
        e.KeyCode == Keys.Home ||
        e.KeyCode == Keys.End);

    if (!(numericKeys || editKeys || navigationKeys))
    {
        if (ctrlA)
        // Do select all as OS/Framework
        // does not always seem to implement this.
            SelectAll();
        result = false;
    }
    if (!result) // If not valid key then suppress and handle.
    {
        e.SuppressKeyPress = true;
        e.Handled = true;
        if (ctrlA) { } // Do Nothing!
        else
            OnKeyRejected(new KeyRejectedEventArgs(e.KeyCode));
    }
    else
        base.OnKeyDown(e);
}

CheckPasteValid

When a paste message is received, it's caught in the overridden WndProc, which then calls this method. Based on the result, if necessary, it returns without calling the base's WndProc, therefore cancelling the message. The code for CheckPasteValid is given below. After setting the default values, we attempt to get the text from the clipboard. If there's an error or there's no valid text, then an appropriate reject reason is set and we return. If OK, we then build a string from the current text and the clipboard's text. The final step is to check if the clipboard's text contains any non digit characters and set the required reject reason.

private PasteEventArgs CheckPasteValid()
{
    // Default values.
    PasteRejectReasons rejectReason = PasteRejectReasons.Accepted;
    string originalText = Text;
    string clipboardText = string.Empty;
    string textResult = string.Empty;

    try
    {
        clipboardText = Clipboard.GetText(TextDataFormat.Text);
        if (clipboardText.Length > 0) // Does clipboard contain text?
        {
            // Store text value as it will be post paste assuming it is valid.
            textResult = (
                Text.Remove(SelectionStart, 
                SelectionLength).Insert(SelectionStart, clipboardText));
            foreach (char c in clipboardText) // Check for any non digit characters.
            {
                if (!char.IsDigit(c))
                {
                    rejectReason = PasteRejectReasons.InvalidCharacter;
                    break;
                }
            }
        }
        else
            rejectReason = PasteRejectReasons.NoData;
    }
    catch
    {
        rejectReason = PasteRejectReasons.Unknown;
    }
    return new PasteEventArgs(originalText, clipboardText, textResult, rejectReason);
}

New stuff

Events

  • KeyRejected - Occurs when a KeyDown event is suppressed.
  • PasteRejected - Occurs when a Paste attempt is disallowed.

Properties

  • DefaultText - The string to use when there is no value (cannot be null or empty).

Nested classes

There are two nested event argument classes.

KeyRejectedEventArgs

An instance of this is created every time a key down is suppressed. It has just one property:

  • Key - They rejected key (System.Windows.Forms.Keys).

PasteEventArgs

An instance of this is created every time a Paste message is received. It's used internally, but its primary purpose is as the event args in the PasteRejected event. It has four properties:

  • OriginalText - The text as it was before the paste (or still is, if rejected).
  • ClipboardText - The text that is attempting to paste.
  • TextResult - The text that is or would have been the result of the paste.
  • RejectReason - An enum (PasteRejectReasons) that indicates the reason for rejection if rejected, or PasteRejectReasons.Accepted for internal use if the paste is OK.

History

  • 7 November 2008: Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

DaveyM69
United Kingdom United Kingdom
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Generalvery koolmemberDonsw7 Feb '09 - 11:33 
Good work, I bet it took time.
GeneralRe: very koolmvpDaveyM698 Feb '09 - 1:21 
Thanks. Once I figured out the basic problems, it only took a couple of hours to do the numeric version (I've extended it for other purposes too).
 
Hope you find it useful Big Grin | :-D
 
Dave
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn)
Visual Basic is not used by normal people so we're not covering it here. (Uncyclopedia)

GeneralVery simple NumericTextBox for examplemember-=SerP=-10 Nov '08 - 20:41 
class NumericTextBox : TextBox
{
    const int ES_NUMBER = 0x2000; 
    const int WM_PASTE = 0x0302; 
    const string NumberTemplate = @"^\d+$";
 
    protected override CreateParams CreateParams
    {
        get 
	{ 
		CreateParams parameters = base.CreateParams; 
		parameters.Style |= ES_NUMBER; 
		return parameters; 
	}
    }
 
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_PASTE)
        {
            string data = Clipboard.GetDataObject().GetData(DataFormats.Text) as string; 
            if (!Regex.IsMatch(data, NumberTemplate)) 
		return;
        } 
        base.WndProc(ref m);
    }
}

GeneralRe: Very simple NumericTextBox for example [modified]memberDaveyM6911 Nov '08 - 1:08 
This I like Big Grin | :-D I've never used the CreateParams override before. A new toy to investigate! I'm not sure I'd want the user to have that message thrown at them every time they enter an incorrect character, I think raising an event and having the option to decide in code (possibly based on a user setting) whether to alert them or not is more flexible. Cool though, and an elegant solution.
 
The RegEx.IsMatch would be a good idea to replace the character iteration in my CheckPasteValid(). I still like the idea of a separate method so I can see and raise an event explaining exactly why the paste failed, so I'd move it there rather than having it in the WndProc override.
 
Good ideas, thanks for your input.
 
Dave
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn)
Visual Basic is not used by normal people so we're not covering it here. (Uncyclopedia)
modified on Tuesday, November 11, 2008 5:46 PM

GeneralRe: Very simple NumericTextBox for examplemember-Dy7 Jun '09 - 21:58 
Thanks for posting this, I've found it very helpful
 
- Dy

General[Message Deleted]membernicorac10 Nov '08 - 20:40 

GeneralRe: Testing all the accepted keysmemberDaveyM6911 Nov '08 - 1:38 
I had thought about that, but KeyDown doesn't give us a char value. KeyPress does, but that doesn't receive control keys.
 
Dave
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn)
Visual Basic is not used by normal people so we're not covering it here. (Uncyclopedia)

GeneralRe: Testing all the accepted keysmembernicorac11 Nov '08 - 4:45 
Damn..., I deleted my first message Sniff | :^) .
It was about simplifying the tests on pressed keys by using Char.IsControl() method.
 
You're right, I missed the point.
 

Visit my website for some interesting .NET free tools: http://coolsoft.altervista.org

GeneralRe: Testing all the accepted keysmemberDaveyM6911 Nov '08 - 11:45 
No problem - all suggestions welcomed Smile | :) . It would be useful if the KeyPress event or KeyDown event had all the info instead of some in one and some in the other. There's probably a subtle reason for it, if I'm bored one day I may investigate!
 
Dave
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn)
Visual Basic is not used by normal people so we're not covering it here. (Uncyclopedia)

QuestionCan be simplermemberJacques Bourgeois10 Nov '08 - 9:07 
You can simplify your code a lot.
 
First, instead of having someting for the paste and something for the keydown, which by the way does not validate any text entered by code, why not override OnTextChange, which will cover all the ways text goes into the TextBox.
 
Second, there is a TryParse method on all the numeric types that can be used to validate if the text can be converted to a number. It handles non only the digits, but also the sign, the decimal separator (which can change with culture), makes sure that there are not 2 decimals separators, and so on.
 
Jacques Bourgeois (http://www3.sympatico.ca/jbfi/homeus.htm)

AnswerRe: Can be simplermemberDaveyM6910 Nov '08 - 15:33 
Thanks for your comments and taking the time to read the article. I had considered both the points you mentioned when designing the control but rejected them for the following reasons.
 
I rejected doing everything in the OnTextChanged, primarily because it is raised AFTER the text has changed - the idea is to not get unwanted data into the control in the first place. The KeyDown is BEFORE the control get's redrawn and WM_PASTE only gets completed if the base.WndProc gets called, so again I could act BEFORE the data gets there.
 
TryParse is great, if it's a number you're wanting. As I said in the 'What I haven't done' section
 
it's a text box, not an int/double/decimal... box.

 
This is still supposed to be a string, but numbers only - specifically no sign or decimal/number separators etc. Also, even using the largest numeric type supported (UInt64 if no sign) - it gives a maximum of 20 characters, I'd have to break the string into n blocks of 20 and iterate over them and make sure I was specifying a culture that had no separators etc.
 
Dave
BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn)
Visual Basic is not used by normal people so we're not covering it here. (Uncyclopedia)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 9 Nov 2008
Article Copyright 2008 by DaveyM69
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid