// Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // Copyright (c) 2005 Novell, Inc. // // Authors: // Jonathan Gilbert // // Integration into MWF: // Peter Bartok // using System; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Globalization; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; namespace System.Windows.Forms { [DefaultEvent("ValueChanged")] [DefaultProperty("Value")] #if NET_2_0 [DefaultBindingProperty ("Value")] [ClassInterface (ClassInterfaceType.AutoDispatch)] [ComVisible (true)] #endif public class NumericUpDown : UpDownBase, ISupportInitialize { #region Local Variables private bool suppress_validation; private int decimal_places; private bool hexadecimal; private decimal increment; private decimal maximum; private decimal minimum; private bool thousands_separator; private decimal dvalue; #if NET_2_0 NumericUpDownAccelerationCollection accelerations; // private long buttonPressedTicks; //private bool isSpinning; #endif #endregion // Local Variables #region Public Constructors public NumericUpDown() { suppress_validation = false; decimal_places = 0; hexadecimal = false; increment = 1M; maximum = 100M; minimum = 0.0M; thousands_separator = false; Text = "0"; } #endregion // Public Constructors #region Private Methods void wide_number_multiply_by_10(int[] number) { long carry = 0; for (int i=0; i < number.Length; i++) { long multiplication = unchecked(carry + 10 * (long)(uint)number[i]); carry = multiplication >> 32; number[i] = unchecked((int)multiplication); } } void wide_number_multiply_by_16(int[] number) { int carry = 0; for (int i=0; i < number.Length; i++) { int multiplication = unchecked(carry | (number[i] << 4)); carry = (number[i] >> 28) & 0x0F; number[i] = multiplication; } } void wide_number_divide_by_16(int[] number) { int carry = 0; for (int i=number.Length - 1; i >= 0; i--) { int division = unchecked(carry | ((number[i] >> 4) & 0x0FFFFFFF)); carry = (number[i] << 28); number[i] = division; } } bool wide_number_less_than(int[] left, int[] right) { unchecked { for (int i=left.Length - 1; i >= 0; i--) { uint leftvalue = (uint)left[i]; uint rightvalue = (uint)right[i]; if (leftvalue > rightvalue) return false; if (leftvalue < rightvalue) return true; } } // equal return false; } void wide_number_subtract(int[] subtrahend, int[] minuend) { long carry = 0; unchecked { for (int i=0; i < subtrahend.Length; i++) { long subtrahendvalue = (uint)subtrahend[i]; long minuendvalue = (uint)minuend[i]; long result = subtrahendvalue - minuendvalue + carry; if (result < 0) { carry = -1; result -= int.MinValue; result -= int.MinValue; } else carry = 0; subtrahend[i] = unchecked((int)result); } } } #endregion // Private Methods #region Public Instance Properties #if NET_2_0 [Browsable (false)] [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public NumericUpDownAccelerationCollection Accelerations { get { if (accelerations == null) accelerations = new NumericUpDownAccelerationCollection (); return accelerations; } } #endif [DefaultValue(0)] public int DecimalPlaces { get { return decimal_places; } set { decimal_places = value; UpdateEditText(); } } [DefaultValue(false)] public bool Hexadecimal { get { return hexadecimal; } set { hexadecimal = value; UpdateEditText(); } } public decimal Increment { get { return increment; } set { if (value < 0) { throw new ArgumentOutOfRangeException("value", value, "NumericUpDown increment cannot be negative"); } increment = value; } } [RefreshProperties(RefreshProperties.All)] public decimal Maximum { get { return maximum; } set { maximum = value; if (minimum > maximum) minimum = maximum; if (dvalue > maximum) Value = maximum; } } [RefreshProperties(RefreshProperties.All)] public decimal Minimum { get { return minimum; } set { minimum = value; if (maximum < minimum) maximum = minimum; if (dvalue < minimum) Value = minimum; } } #if NET_2_0 [Browsable (false)] [EditorBrowsable (EditorBrowsableState.Never)] [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public new Padding Padding { get { return Padding.Empty; } set { } } #endif [Bindable(false)] [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [EditorBrowsable(EditorBrowsableState.Never)] public override string Text { get { return base.Text; } set { base.Text = value; } } [DefaultValue(false)] [Localizable(true)] public bool ThousandsSeparator { get { return thousands_separator; } set { thousands_separator = value; UpdateEditText(); } } [Bindable(true)] public decimal Value { get { if (UserEdit) ValidateEditText (); return dvalue; } set { if (value != dvalue) { if (!suppress_validation && ((value < minimum) || (value > maximum))) { #if NET_2_0 throw new ArgumentOutOfRangeException ("value", "NumericUpDown.Value must be within the specified Minimum and Maximum values"); #else throw new ArgumentException ("NumericUpDown.Value must be within the specified Minimum and Maximum values", "value"); #endif } dvalue = value; OnValueChanged (EventArgs.Empty); UpdateEditText (); } } } #endregion // Public Instance Properties #region Public Instance Methods public void BeginInit() { suppress_validation = true; } public void EndInit() { suppress_validation = false; Value = Check (dvalue); UpdateEditText (); } public override string ToString() { return string.Format("{0}, Minimum = {1}, Maximum = {2}", base.ToString(), minimum, maximum); } public override void DownButton() { if (UserEdit) { ParseEditText (); } Value = Math.Max (minimum, unchecked (dvalue - increment)); } public override void UpButton() { if (UserEdit) { ParseEditText (); } Value = Math.Min (maximum, unchecked (dvalue + increment)); } #endregion // Public Instance Methods #region Protected Instance Methods protected override AccessibleObject CreateAccessibilityInstance() { AccessibleObject acc; acc = new AccessibleObject(this); acc.role = AccessibleRole.SpinButton; return acc; } protected override void OnTextBoxKeyPress(object source, KeyPressEventArgs e) { if ((ModifierKeys & ~Keys.Shift) != Keys.None) { return; } NumberFormatInfo nfi = CultureInfo.CurrentCulture.NumberFormat; string pressedKey = e.KeyChar.ToString (); if ((pressedKey != nfi.NegativeSign) && (pressedKey != nfi.NumberDecimalSeparator) && (pressedKey != nfi.NumberGroupSeparator)) { string acceptable = hexadecimal ? "\b0123456789abcdefABCDEF" : "\b0123456789"; if (acceptable.IndexOf (e.KeyChar) == -1) { // FIXME: produce beep to signal that "invalid" key was pressed // prevent the key from reaching the text box e.Handled = true; } } base.OnTextBoxKeyPress(source, e); } protected virtual void OnValueChanged(EventArgs e) { EventHandler eh = (EventHandler)(Events [ValueChangedEvent]); if (eh != null) eh (this, e); } protected void ParseEditText () { try { if (!hexadecimal) { Value = decimal.Parse (Text, CultureInfo.CurrentCulture); } else { #if !NET_2_0 Value = Check (Convert.ToDecimal (Convert.ToInt32 (Text, 16))); #else Value = Check (Convert.ToDecimal (Convert.ToInt32 (Text, 10))); #endif } } catch { } finally { UserEdit = false; } } private decimal Check (decimal val) { decimal ret = val; if (ret < minimum) { ret = minimum; } if (ret > maximum) { ret = maximum; } return ret; } protected override void UpdateEditText () { if (suppress_validation) return; if (UserEdit) ParseEditText (); ChangingText = true; if (!hexadecimal) { // "N" and "F" differ only in that "N" includes commas // every 3 digits to the left of the decimal and "F" // does not. string format_string; if (thousands_separator) { format_string = "N"; } else { format_string = "F"; } format_string += decimal_places; Text = dvalue.ToString (format_string, CultureInfo.CurrentCulture); } else { // Decimal.ToString doesn't know the "X" formatter, and // converting it to an int is narrowing, so do it // manually... int[] bits = decimal.GetBits (dvalue); bool negative = (bits[3] < 0); int scale = (bits[3] >> 16) & 0x1F; bits[3] = 0; int[] radix = new int[4]; radix[0] = 1; for (int i = 0; i < scale; i++) wide_number_multiply_by_10 (radix); int num_chars = 0; while (!wide_number_less_than (bits, radix)) { num_chars++; wide_number_multiply_by_16 (radix); } if (num_chars == 0) { Text = "0"; } StringBuilder chars = new StringBuilder (); if (negative) chars.Append ('-'); for (int i = 0; i < num_chars; i++) { int digit = 0; wide_number_divide_by_16 (radix); while (!wide_number_less_than (bits, radix)) { // greater than or equals digit++; wide_number_subtract (bits, radix); } if (digit < 10) { chars.Append ((char) ('0' + digit)); } else { chars.Append ((char) ('A' + digit - 10)); } } Text = chars.ToString (); } } protected override void ValidateEditText() { ParseEditText (); UpdateEditText (); } #if NET_2_0 protected override void OnLostFocus(EventArgs e) { base.OnLostFocus(e); if (UserEdit) UpdateEditText(); } protected override void OnKeyUp (KeyEventArgs e) { // isSpinning = false; base.OnKeyUp (e); } protected override void OnKeyDown (KeyEventArgs e) { // buttonPressedTicks = DateTime.Now.Ticks; // isSpinning = true; base.OnKeyDown (e); } #endif #endregion // Protected Instance Methods #region Events #if NET_2_0 [Browsable (false)] [EditorBrowsable (EditorBrowsableState.Never)] public new event EventHandler PaddingChanged { add { base.PaddingChanged += value; } remove { base.PaddingChanged -= value; } } #endif static object ValueChangedEvent = new object (); public event EventHandler ValueChanged { add { Events.AddHandler (ValueChangedEvent, value); } remove { Events.RemoveHandler (ValueChangedEvent, value); } } [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] public new event EventHandler TextChanged { add { base.TextChanged += value; } remove { base.TextChanged -= value; } } #endregion // Events } }