Here’s my numeric only text control. I’ll admit there’s not much too it, and there are masked edit controls, but for those who dare…
/******************************************************/
/* NULLFX FREE SOFTWARE LICENSE */
/******************************************************/
/* NumericTextBox Library */
/* by: Steve Whitley */
/* © 2005 NullFX Software */
/* */
/* NULLFX SOFTWARE DISCLAIMS ALL WARRANTIES, */
/* RESPONSIBILITIES, AND LIABILITIES ASSOCIATED WITH */
/* USE OF THIS CODE IN ANY WAY, SHAPE, OR FORM */
/* REGARDLESS HOW IMPLICIT, EXPLICIT, OR OBSCURE IT */
/* IS. IF THERE IS ANYTHING QUESTIONABLE WITH REGARDS */
/* TO THIS SOFTWARE BREAKING AND YOU GAIN A LOSS OF */
/* ANY NATURE, WE ARE NOT THE RESPONSIBLE PARTY. USE */
/* OF THIS SOFTWARE CREATES ACCEPTANCE OF THESE TERMS */
/* */
/* USE OF THIS CODE MUST RETAIN ALL COPYRIGHT NOTICES */
/* AND LICENSES (MEANING THIS TEXT). */
/* */
/******************************************************/
namespace NullFX.Controls {
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
public class NumericTextBox : TextBox {
static NumberFormatInfo CurrentNumberFormat =
CultureInfo.CurrentCulture.NumberFormat;
const int WM_KEYDOWN = 0x0100,
WM_PASTE = 0x0302;
public decimal Value {
set { Text = value.ToString ( ); }
get {
decimal.TryParse ( Text, out decimal tmpValue );
return tmpValue;
}
}
public override bool PreProcessMessage ( ref Message msg ) {
if ( msg.Msg == WM_KEYDOWN ) {
Keys keys = ( Keys ) msg.WParam.ToInt32 ( );
bool isPaste = keys == Keys.V && ModifierKeys == Keys.Control;
bool isNumeric = IsNumeric ( keys );
bool isKeyValid = IsKeyValid ( keys );
if ( isNumeric | isKeyValid ) {
return false;
} else if ( isPaste ) {
IDataObject obj = Clipboard.GetDataObject ( );
string input = ( string ) obj.GetData ( typeof ( string ) );
if ( !IsInputOrTextNumeric ( input ) ) {
return true;
}
return false;
} else {
return true;
}
} else {
return base.PreProcessMessage ( ref msg );
}
}
protected override void WndProc ( ref Message m ) {
if ( m.Msg == WM_PASTE ) {
IDataObject obj = Clipboard.GetDataObject ( );
string input = ( string ) obj.GetData ( typeof ( string ) );
if ( !IsInputOrTextNumeric ( input ) ) {
m.Result = ( IntPtr ) 0;
return;
}
}
base.WndProc ( ref m );
}
private bool IsNumeric ( Keys key ) {
return ( ( key >= Keys.D0 && key <= Keys.D9 ) ||
( key >= Keys.NumPad0 && key <= Keys.NumPad9 ) ) &&
ModifierKeys != Keys.Shift;
}
private bool IsKeyValid ( Keys key ) {
bool ctrl = key == Keys.Control;
bool ctrlZ = key == Keys.Z && ModifierKeys == Keys.Control,
ctrlX = key == Keys.X && ModifierKeys == Keys.Control,
ctrlC = key == Keys.C && ModifierKeys == Keys.Control,
ctrlV = key == Keys.V && ModifierKeys == Keys.Control,
homeEnd = key == Keys.Home | key == Keys.End,
del = key == Keys.Delete,
bksp = key == Keys.Back,
arrows = ( key == Keys.Up ) | ( key == Keys.Down ) |
( key == Keys.Left ) | ( key == Keys.Right );
bool standardTests = ctrl | del | bksp | arrows | homeEnd |
ctrlC | ctrlX | ctrlV | ctrlZ;
bool containsTokenChar = false;
if ( !standardTests ) {
string token = VKtoChar ( key );
containsTokenChar = validTokens.ContainsKey ( token );
if ( token != CurrentNumberFormat.CurrencyGroupSeparator &&
Text.Contains ( token ) ) {
containsTokenChar = false;
}
if ( !IsInputOrTextNumeric ( token ) ) {
containsTokenChar = false;
}
}
return standardTests | containsTokenChar;
}
private bool IsInputOrTextNumeric ( string tokenOrInput ) {
string tmp = Text;
string inserted = tmp.Insert ( SelectionStart, tokenOrInput );
if ( !decimal.TryParse ( inserted, out decimal result ) ) {
return false;
}
return true;
}
private static string VKtoChar ( Keys key ) {
StringBuilder sb = new StringBuilder ( 256 );
byte[] keystate = new byte[256];
if ( GetKeyboardState ( keystate ) ) {
uint scanCode = MapVirtualKey ( ( uint ) key, 2 );
int res = ToUnicode ( ( uint ) key, scanCode, keystate, sb, 256, 2 );
}
return sb.ToString ( );
}
static Dictionary<string, string> validTokens =
new Dictionary<string, string> ( );
static NumericTextBox ( ) {
validTokens.Add ( CurrentNumberFormat.NegativeSign, "" );
validTokens.Add ( CurrentNumberFormat.NumberDecimalSeparator, "" );
validTokens.Add ( CurrentNumberFormat.CurrencyGroupSeparator, "" );
}
[DllImport ( "user32.dll", SetLastError = true )]
private static extern bool GetKeyboardState ( byte[] receivingBuffer );
[DllImport ( "user32.dll", SetLastError = true )]
private static extern int ToUnicode ( uint virtualKey, uint scanCode,
byte[] keyState, [Out, MarshalAs ( UnmanagedType.LPWStr, SizeConst = 256 )]
StringBuilder buffer, int bufferSize, uint flags );
[DllImport ( "user32.dll" )]
private static extern uint MapVirtualKey ( uint keyCode, uint mapType );
}
}C#What this does, is watch the Windows Message Queue for incoming messages (specifically WM_KEYDOWN and WM_PASTE). If a key has been pressed, it will look to see if the key is actually a number. It also looks to see if a paste command has been sent. if a key has been pressed and it is a numeric key (there are a few more keys for convenience sake) or the paste operation has been invoked (the contents of the paste operation are checked to make sure that anything that gets pasted is a number, else it fails to paste in the data) and its contents are numeric, allows that data to be pasted into the textbox.
Since originally posting this control on my older website, I’ve made some minor updates, checking the key pressed against its text character, checking to see if the resulting value will parse as a decimal and adding a few more keystroke character checks in there.
For Clarity sake, PreProcessMessage is invoked before the Message is sent into the message queue. If the control handles this message, it returns true, and the message ends there. If it returns false, it will send the Message into the message pump for further processing. WndProc is similar to WindowProc in win32, and one can filter messages sent here like they used to in standard windows programming. In this sample, we’re peeking at the KEYDOWN message, in the PreProcessMessage function. Commands are not sent through here, but are passed in WndProc, where we can listen, and handle this Message ourselves (because we need to check the contents of the clip board for numeric data).
If PreProcessMessage‘s Message contains numeric or one of our allowed keys, it returns false which forces the base TextBox to handle the instruction like normal, if not it returns true meaning we’ve handled it and it doesn’t get passed into the base TextBox control and nothing flows to the textbox. If WndProc receives a numeric only string in the clip board, then it sends the message to the base TextBox
base.WndProc(ref m);
if the clipboard contains alpha or other data, it sets the LResult to 0, and returns without passing the message to the base TextBox effectively saying we’ve handled the message, and there’s no need to process it any further.