using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Data; using System.Windows.Forms; namespace PiggyBank.Custom_Controls { /// /// Custom control that handles numeric values. /// Supports comma formatting, percentages, currency, adn decimal precision. /// Author: Cody Powell /// Contact: cody@codypowell.com /// /// /// This library is free software; you can redistribute it and/or modify /// it under the terms fo the GNU General Public License as published by /// the Free Software Foundation. /// /// This program is distributed in the hope that it will be useful, but /// without any warranty. /// public class NumericTextbox : System.Windows.Forms.UserControl { private System.Windows.Forms.TextBox txtNumeric; protected bool nonNumberEntered; protected bool commaFormat; protected bool percentFormat; protected bool currencyFormat; protected char prefix; protected char suffix; protected int decimalPrecision; /// /// Required designer variable. /// private System.ComponentModel.Container components = null; /// /// Constructor is largely blank. /// public NumericTextbox() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); //makes the blanks easier to spot. prefix = char.MinValue; suffix = char.MinValue; } /// /// Clean up any resources being used. /// protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Component Designer generated code /// /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// private void InitializeComponent() { this.txtNumeric = new System.Windows.Forms.TextBox(); this.SuspendLayout(); // // txtNumeric // this.txtNumeric.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.txtNumeric.Location = new System.Drawing.Point(0, 0); this.txtNumeric.Name = "txtNumeric"; this.txtNumeric.TabIndex = 0; this.txtNumeric.Text = ""; this.txtNumeric.KeyDown += new System.Windows.Forms.KeyEventHandler(this.txtNumeric_KeyDown); this.txtNumeric.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.txtNumeric_KeyPress); this.txtNumeric.Leave += new System.EventHandler(this.txtNumeric_Leave); // // NumericTextbox // this.Controls.Add(this.txtNumeric); this.Name = "NumericTextbox"; this.Size = new System.Drawing.Size(104, 24); this.ResumeLayout(false); } #endregion #region Check Methods /// /// minus can only be at the start of the number, and there can only be one. /// /// If it's a bad minus, we return true. protected bool CheckMinus() { return (txtNumeric.Text.IndexOf("-") > -1) || (txtNumeric.SelectionStart> 0); } /// /// period can only occur once in number. /// /// If it's a bad period, we return true. protected bool CheckPeriod() { return (txtNumeric.Text.IndexOf(".") > -1); } #endregion #region UI Logic /// /// A good deal of this specific method is taken from an MSDN article (no link onhand). /// Basically, this is the method that determines whether or not the user has entered /// a valid input (number, or appropriate mark-up sign). /// /// /// protected void txtNumeric_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) { //If a non number is entered, we don't want to recognize it as valid input. //thus, we need a flag we can track. nonNumberEntered = false; // Determine whether the keystroke is a number from the top of the keyboard. if (e.KeyCode < Keys.D0 || e.KeyCode > Keys.D9) { // Determine whether the keystroke is a number from the keypad. if (e.KeyCode < Keys.NumPad0 || e.KeyCode > Keys.NumPad9) { //there are certain rules for determining if it's valid to use a period or a minus. //we need to check those rules. if (e.KeyCode == Keys.OemMinus) nonNumberEntered = CheckMinus(); else if (e.KeyCode == Keys.OemPeriod) nonNumberEntered = CheckPeriod(); else if(e.KeyCode != Keys.Back) { // A non-numerical keystroke was pressed. // Set the flag to true and evaluate in KeyPress event. nonNumberEntered = true; } } } } /// /// Checks to see if we caught any bad characters. /// /// /// protected void txtNumeric_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) { if (nonNumberEntered == true) { // If bad, mark it as handled so it doesn't show up. e.Handled = true; } } /// /// Makes sure the number is pretty for the user. /// /// /// protected void txtNumeric_Leave(object sender, System.EventArgs e) { try { //if our number field has some text in it, let's apply our formatting //rules so they get something purty. txtNumeric.Text = FormatNumberForDisplay(txtNumeric.Text); } catch(Exception ex) { MessageBox.Show(txtNumeric.Text); } } #endregion #region Accessors /// /// Allows the user to get/set the value fo the textbox. /// public override string Text { get { //our format for output is a little bit different than our //format for display. return FormatNumberForOutput(txtNumeric.Text); } set { //If the user supplies a val, let's apply our formatting rules to it. txtNumeric.Text = FormatNumberForDisplay(value); } } /// /// Allows the user to get/set the readonly status of the textbox. /// public bool ReadOnly { get { return txtNumeric.ReadOnly; } set { txtNumeric.ReadOnly = value; } } /// /// Allows the user to get/set the CommaFormat status. /// public bool CommaFormat { get { return commaFormat; } set { commaFormat = value; } } /// /// Allows the user to get/set percent formatting. /// public bool PercentFormat { get { return percentFormat; } set { percentFormat = value; if (percentFormat) { suffix = '%'; currencyFormat = false; decimalPrecision = 2; } } } /// /// Allows the user to get/set currency formatting. /// public bool CurrencyFormat { get { return currencyFormat; } set { currencyFormat = value; if (currencyFormat) { prefix = '$'; percentFormat = false; decimalPrecision = 2; commaFormat = true; } } } /// /// Allows the user to get/set decimal precision /// public int DecimalPrecision { get { return decimalPrecision; } set { decimalPrecision = value; } } #endregion #region Formatting /// /// Formatting the number for display means we apply our rules /// so that the user gets something like 10,000 or 5.30% /// (depending on settings) /// /// the number to mark-up /// mark-uped number protected string FormatNumberForDisplay(string val) { if (val.Length == 0) return null; string returnVal; string decimalPlaces = ""; val = StripPrefixSuffix(val); for (int i = 0; i < decimalPrecision; i++) { decimalPlaces += "0"; } //format provider for comma formatting. string commaFormatString = "#,##0." + decimalPlaces + ";(#,##0." + decimalPlaces + ");0"; string regFormatString = "###0." + decimalPlaces + ";(###0." + decimalPlaces + ");0"; double myVal = Double.Parse(val); if (commaFormat) { //if comma formatting is enabled, we apply our format //provider. returnVal = myVal.ToString(commaFormatString); } else { returnVal = myVal.ToString(regFormatString); } //if there's a prefix ($) or suffix (%), let's apply those. if (prefix != char.MinValue) returnVal = prefix + returnVal; if (suffix != char.MinValue) returnVal += suffix; return returnVal; } /// /// If we're formatting for output (that is to say, formatting /// to return this as a variable, we need to strip some of our /// characters, like % and $. /// /// number to strip mark-up /// stripped number protected string FormatNumberForOutput(string val) { string retVal = StripPrefixSuffix(val); //if it's a percent, divide it by 100, since we display things //as 5.00%, isntead of .05. if (percentFormat) retVal = (Double.Parse(retVal) / 100).ToString(); return retVal; } protected string StripPrefixSuffix(string val) { if (val.Length == 0) return null; int start = val.IndexOf(prefix); int end = val.IndexOf(suffix); string retVal = ""; if (start < 0) start = -1; start++; if (end < 0) end = val.Length; //get the portion of the string without the prefix or suffix. retVal = val.Substring(start, end - start); return retVal; } #endregion } }