moel@345: using System;
moel@345: using System.ComponentModel;
moel@345: using System.Windows.Forms;
moel@345: using System.Globalization;
moel@345: 
moel@345: 
moel@345: namespace Aga.Controls
moel@345: {
moel@345: 	/// <summary>
moel@345: 	/// Restricts the entry of characters to digits, the negative sign,
moel@345: 	/// the decimal point, and editing keystrokes (backspace).
moel@345: 	/// It does not handle the AltGr key so any keys that can be created in any
moel@345: 	/// combination with AltGr these are not filtered
moel@345: 	/// </summary>
moel@345: 	public class NumericTextBox : TextBox
moel@345: 	{
moel@345: 		private const int WM_PASTE = 0x302;
moel@345: 		private NumberStyles numberStyle = NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign;
moel@345: 
moel@345: 		/// <summary>
moel@345: 		/// Restricts the entry of characters to digits, the negative sign,
moel@345: 		/// the decimal point, and editing keystrokes (backspace).
moel@345: 		/// It does not handle the AltGr key
moel@345: 		/// </summary>
moel@345: 		/// <param name="e"></param>
moel@345: 		protected override void OnKeyPress(KeyPressEventArgs e)
moel@345: 		{
moel@345: 			base.OnKeyPress(e);
moel@345: 
moel@345: 			e.Handled = invalidNumeric(e.KeyChar);
moel@345: 		}
moel@345: 
moel@345: 
moel@345: 		/// <summary>
moel@345: 		/// Main method for verifying allowed keypresses.
moel@345: 		/// This does not catch cut paste copy ... operations.
moel@345: 		/// </summary>
moel@345: 		/// <param name="key"></param>
moel@345: 		/// <returns></returns>
moel@345: 		private bool invalidNumeric(char key)
moel@345: 		{
moel@345: 			bool handled = false;
moel@345: 
moel@345: 			NumberFormatInfo numberFormatInfo = CultureInfo.CurrentCulture.NumberFormat;
moel@345: 			string decimalSeparator = numberFormatInfo.NumberDecimalSeparator;
moel@345: 			string negativeSign = numberFormatInfo.NegativeSign;
moel@345: 
moel@345: 			string keyString = key.ToString();
moel@345: 
moel@345: 			if (Char.IsDigit(key))
moel@345: 			{
moel@345: 				// Digits are OK
moel@345: 			}
moel@345: 			else if (AllowDecimalSeparator && keyString.Equals(decimalSeparator))
moel@345: 			{
moel@345: 				if (Text.IndexOf(decimalSeparator) >= 0)
moel@345: 				{
moel@345: 					handled = true;
moel@345: 				}
moel@345: 			}
moel@345: 			else if (AllowNegativeSign && keyString.Equals(negativeSign))
moel@345: 			{
moel@345: 				if (Text.IndexOf(negativeSign) >= 0)
moel@345: 				{
moel@345: 					handled = true;
moel@345: 				}
moel@345: 			}
moel@345: 			else if (key == '\b')
moel@345: 			{
moel@345: 				// Backspace key is OK
moel@345: 			}
moel@345: 			else if ((ModifierKeys & (Keys.Control)) != 0)
moel@345: 			{
moel@345: 				// Let the edit control handle control and alt key combinations
moel@345: 			}
moel@345: 			else
moel@345: 			{
moel@345: 				// Swallow this invalid key and beep
moel@345: 				handled = true;
moel@345: 			}
moel@345: 			return handled;
moel@345: 		}
moel@345: 
moel@345: 
moel@345: 		/// <summary>
moel@345: 		/// Method invoked when Windows sends a message.
moel@345: 		/// </summary>
moel@345: 		/// <param name="m">Message from Windows.</param>
moel@345: 		/// <remarks>
moel@345: 		/// This is over-ridden so that the user can not use
moel@345: 		/// cut or paste operations to bypass the TextChanging event.
moel@345: 		/// This catches ContextMenu Paste, Shift+Insert, Ctrl+V,
moel@345: 		/// While it is generally frowned upon to override WndProc, no
moel@345: 		/// other simple mechanism was apparent to simultaneously and
moel@345: 		/// transparently intercept so many different operations.
moel@345: 		/// </remarks>
moel@345: 		protected override void WndProc(ref Message m)
moel@345: 		{
moel@345: 			// Switch to handle message...
moel@345: 			switch (m.Msg)
moel@345: 			{
moel@345: 				case WM_PASTE:
moel@345: 					{
moel@345: 						// Get clipboard object to paste
moel@345: 						IDataObject clipboardData = Clipboard.GetDataObject();
moel@345: 
moel@345: 						// Get text from clipboard data
moel@345: 						string pasteText = (string)clipboardData.GetData(
moel@345: 								DataFormats.UnicodeText);
moel@345: 
moel@345: 						// Get the number of characters to replace
moel@345: 						int selectionLength = SelectionLength;
moel@345: 
moel@345: 						// If no replacement or insertion, we are done
moel@345: 						if (pasteText.Length == 0)
moel@345: 						{
moel@345: 							break;
moel@345: 						}
moel@345: 						else if (selectionLength != 0)
moel@345: 						{
moel@345: 							base.Text = base.Text.Remove(SelectionStart, selectionLength);
moel@345: 						}
moel@345: 
moel@345: 						bool containsInvalidChars = false;
moel@345: 						foreach (char c in pasteText)
moel@345: 						{
moel@345: 							if (containsInvalidChars)
moel@345: 							{
moel@345: 								break;
moel@345: 							}
moel@345: 							else if (invalidNumeric(c))
moel@345: 							{
moel@345: 								containsInvalidChars = true;
moel@345: 							}
moel@345: 						}
moel@345: 
moel@345: 						if (!containsInvalidChars)
moel@345: 						{
moel@345: 							base.Text = base.Text.Insert(SelectionStart, pasteText);
moel@345: 						}
moel@345: 
moel@345: 						return;
moel@345: 					}
moel@345: 
moel@345: 			}
moel@345: 			base.WndProc(ref m);
moel@345: 		}
moel@345: 
moel@345: 
moel@345: 		public int IntValue
moel@345: 		{
moel@345: 			get
moel@345: 			{
moel@345: 				int intValue;
moel@345: 				Int32.TryParse(this.Text, numberStyle, CultureInfo.CurrentCulture.NumberFormat, out intValue);
moel@345: 				return intValue;
moel@345: 			}
moel@345: 		}
moel@345: 
moel@345: 		public decimal DecimalValue
moel@345: 		{
moel@345: 			get
moel@345: 			{
moel@345: 				decimal decimalValue;
moel@345: 				Decimal.TryParse(this.Text, numberStyle, CultureInfo.CurrentCulture.NumberFormat, out decimalValue);
moel@345: 				return decimalValue;
moel@345: 			}
moel@345: 		}
moel@345: 
moel@345: 
moel@345: 		private bool allowNegativeSign;
moel@345: 		[DefaultValue(true)]
moel@345: 		public bool AllowNegativeSign
moel@345: 		{
moel@345: 			get { return allowNegativeSign; }
moel@345: 			set { allowNegativeSign = value; }
moel@345: 		}
moel@345: 
moel@345: 		private bool allowDecimalSeparator;
moel@345: 		[DefaultValue(true)]
moel@345: 		public bool AllowDecimalSeparator
moel@345: 		{
moel@345: 			get { return allowDecimalSeparator; }
moel@345: 			set { allowDecimalSeparator = value; }
moel@345: 		}
moel@345: 
moel@345: 	}
moel@345: 
moel@345: }