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)
SelectAll();
result = false;
}
if (!result)
{
e.SuppressKeyPress = true;
e.Handled = true;
if (ctrlA) { }
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()
{
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)
{
textResult = (
Text.Remove(SelectionStart,
SelectionLength).Insert(SelectionStart, clipboardText));
foreach (char c in clipboardText)
{
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.
| You must Sign In to use this message board. |
|
|
 |
|
 |
Hi
This is nice. But any advice on how to add a decimal place say 2 digits such as 9999.99 along with this control / approach?
TIA
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Obviously you'd need to add that to the allowed characters and with a little extra parsing to ensure there is only one point and a max of two decimal places following it if it exists you should be good to go.
Dave"My code works, but I don't understand why!" - DaveyM69 (Me) BTW, in software, hope and pray is not a viable strategy. (Luc Pattyn) Why are you using VB6? Do you hate yourself? (Christian Graus)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Please do not take me wrong but I dunno why you're doing this while NumericUpDown have everything already plus some extra too(like min, max value)
TVMU^P[[IGIOQHG^JSH`A#@`RFJ\c^JPL>;"[,*/|+&WLEZGc`AFXc!L %^]*IRXD#@GKCQ`R\^SF_WcHbORY87֦ʻ6ϣN8ȤBcRAV\Z^&SU~%CSWQ@#2 W_AD`EPABIKRDFVS)EVLQK)JKQUFK[M`UKs*$GwU#QDXBER@CBN% R0~53%eYrd8mt^7Z6]iTF+(EWfJ9zaK-iTV.C\y<pjxsg-b$f4ia> ----------------------------------------------- 128 bit encrypted signature, crack if you can
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Good question - and not taken wrongly
NumericUpDown allows decimal point(s) (multiple one's for some reason) and the - sign (in any position), as well as having the annoying Up/Down thing at the end.
It didn't suit my purpose. I figured sharing my code would help anyone else looking at how to control the TextBox - not just for numerics.
DaveBTW, 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)
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
oh yeah, I just checked the .Net NumericUpDown and found it guilty as you said...It'd been a long time when I used it 'coz I've my own where I used TextBox too
DaveyM69 wrote: I figured sharing my code would help anyone
me too, but you know...time thing 
Good article BTW
TVMU^P[[IGIOQHG^JSH`A#@`RFJ\c^JPL>;"[,*/|+&WLEZGc`AFXc!L %^]*IRXD#@GKCQ`R\^SF_WcHbORY87֦ʻ6ϣN8ȤBcRAV\Z^&SU~%CSWQ@#2 W_AD`EPABIKRDFVS)EVLQK)JKQUFK[M`UKs*$GwU#QDXBER@CBN% R0~53%eYrd8mt^7Z6]iTF+(EWfJ9zaK-iTV.C\y<pjxsg-b$f4ia> ----------------------------------------------- 128 bit encrypted signature, crack if you can
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
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
DaveBTW, 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)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
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); } }
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
This I like 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.
DaveBTW, 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
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
I had thought about that, but KeyDown doesn't give us a char value. KeyPress does, but that doesn't receive control keys.
DaveBTW, 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)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Damn..., I deleted my first message . It was about simplifying the tests on pressed keys by using Char.IsControl() method.
You're right, I missed the point.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
No problem - all suggestions welcomed . 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!
DaveBTW, 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)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
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)
|
| Sign In·View Thread·PermaLink | 4.00/5 |
|
|
|
 |
|
 |
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.
DaveBTW, 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)
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
|