1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2005 Novell, Inc.
23 // Jonathan Gilbert <logic@deltaq.org>
25 // Integration into MWF:
26 // Peter Bartok <pbartok@novell.com>
30 using System.Collections;
31 using System.ComponentModel;
33 using System.Globalization;
34 using System.Runtime.InteropServices;
36 using System.Windows.Forms;
38 namespace System.Windows.Forms {
39 [DefaultEvent("ValueChanged")]
40 [DefaultProperty("Value")]
42 [DefaultBindingProperty ("Value")]
43 [ClassInterface (ClassInterfaceType.AutoDispatch)]
46 public class NumericUpDown : UpDownBase, ISupportInitialize {
47 #region Local Variables
48 private int suppress_validation;
49 private int decimal_places;
50 private bool hexadecimal;
51 private decimal increment;
52 private decimal maximum;
53 private decimal minimum;
54 private bool thousands_separator;
55 private decimal dvalue;
56 #endregion // Local Variables
58 #region Public Constructors
59 public NumericUpDown() {
60 suppress_validation = 0;
66 thousands_separator = false;
70 #endregion // Public Constructors
72 #region Private Methods
73 void wide_number_multiply_by_10(int[] number) {
76 for (int i=0; i < number.Length; i++) {
77 long multiplication = unchecked(carry + 10 * (long)(uint)number[i]);
79 carry = multiplication >> 32;
81 number[i] = unchecked((int)multiplication);
85 void wide_number_multiply_by_16(int[] number) {
88 for (int i=0; i < number.Length; i++) {
89 int multiplication = unchecked(carry | (number[i] << 4));
91 carry = (number[i] >> 28) & 0x0F;
93 number[i] = multiplication;
97 void wide_number_divide_by_16(int[] number) {
100 for (int i=number.Length - 1; i >= 0; i--) {
101 int division = unchecked(carry | ((number[i] >> 4) & 0x0FFFFFFF));
103 carry = (number[i] << 28);
105 number[i] = division;
109 bool wide_number_less_than(int[] left, int[] right) {
111 for (int i=left.Length - 1; i >= 0; i--) {
112 uint leftvalue = (uint)left[i];
113 uint rightvalue = (uint)right[i];
115 if (leftvalue > rightvalue)
117 if (leftvalue < rightvalue)
126 void wide_number_subtract(int[] subtrahend, int[] minuend) {
130 for (int i=0; i < subtrahend.Length; i++) {
131 long subtrahendvalue = (uint)subtrahend[i];
132 long minuendvalue = (uint)minuend[i];
134 long result = subtrahendvalue - minuendvalue + carry;
138 result -= int.MinValue;
139 result -= int.MinValue;
144 subtrahend[i] = unchecked((int)result);
148 #endregion // Private Methods
150 #region Public Instance Properties
152 public int DecimalPlaces {
154 return decimal_places;
158 decimal_places = value;
163 [DefaultValue(false)]
164 public bool Hexadecimal {
175 public decimal Increment {
182 throw new ArgumentOutOfRangeException("value", value, "NumericUpDown increment cannot be negative");
189 [RefreshProperties(RefreshProperties.All)]
190 public decimal Maximum {
198 if (minimum > maximum)
201 if (dvalue > maximum)
206 [RefreshProperties(RefreshProperties.All)]
207 public decimal Minimum {
215 if (maximum < minimum)
218 if (dvalue < minimum)
225 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
226 [EditorBrowsable(EditorBrowsableState.Never)]
227 public override string Text {
237 [DefaultValue(false)]
239 public bool ThousandsSeparator {
241 return thousands_separator;
245 thousands_separator = value;
251 public decimal Value {
259 if (suppress_validation <= 0) {
260 if ((value < minimum) || (value > maximum)) {
261 throw new ArgumentException("NumericUpDown.Value must be within the specified Minimum and Maximum values", "value");
264 if (value != dvalue) {
266 OnValueChanged(EventArgs.Empty);
271 #endregion // Public Instance Properties
273 #region Public Instance Methods
274 public void BeginInit() {
275 suppress_validation++;
278 public override void DownButton() {
283 Value = Math.Max(minimum, unchecked(dvalue - increment));
286 public void EndInit() {
287 suppress_validation--;
288 if (suppress_validation == 0)
292 public override string ToString() {
293 return string.Format("{0}, Minimum = {1}, Maximum = {2}", base.ToString(), minimum, maximum);
296 public override void UpButton() {
300 Value = Math.Min(maximum, unchecked(dvalue + increment));
302 #endregion // Public Instance Methods
304 #region Protected Instance Methods
305 protected override AccessibleObject CreateAccessibilityInstance() {
306 AccessibleObject acc;
308 acc = new AccessibleObject(this);
309 acc.role = AccessibleRole.SpinButton;
314 protected override void OnTextBoxKeyPress(object source, KeyPressEventArgs e) {
315 if ((ModifierKeys & ~Keys.Shift) != Keys.None) {
319 NumberFormatInfo nfi = CultureInfo.CurrentCulture.NumberFormat;
320 string pressedKey = e.KeyChar.ToString ();
322 if ((pressedKey != nfi.NegativeSign) && (pressedKey != nfi.NumberDecimalSeparator) &&
323 (pressedKey != nfi.NumberGroupSeparator)) {
324 string acceptable = hexadecimal ? "\b0123456789abcdefABCDEF" : "\b0123456789";
325 if (acceptable.IndexOf (e.KeyChar) == -1) {
326 // FIXME: produce beep to signal that "invalid" key was pressed
327 // prevent the key from reaching the text box
332 base.OnTextBoxKeyPress(source, e);
335 protected virtual void OnValueChanged(EventArgs e) {
336 EventHandler eh = (EventHandler)(Events [ValueChangedEvent]);
341 protected void ParseEditText() {
345 string user_edit_text = Text;
348 dvalue = decimal.Parse(user_edit_text, CultureInfo.CurrentCulture);
351 dvalue = Convert.ToDecimal (Convert.ToInt32 (user_edit_text, 16));
353 dvalue = Convert.ToDecimal (Convert.ToInt32 (user_edit_text, 10));
357 if (dvalue < minimum) {
361 if (dvalue > maximum) {
365 OnValueChanged(EventArgs.Empty);
370 protected override void UpdateEditText() {
371 if (suppress_validation > 0)
375 ParseEditText(); // validate user input
378 // "N" and "F" differ only in that "N" includes commas
379 // every 3 digits to the left of the decimal and "F"
382 string format_string;
384 if (thousands_separator) {
390 format_string += decimal_places;
393 Text = dvalue.ToString(format_string, CultureInfo.CurrentCulture);
396 // Decimal.ToString doesn't know the "X" formatter, and
397 // converting it to an int is narrowing, so do it
400 int[] bits = decimal.GetBits(dvalue);
402 bool negative = (bits[3] < 0);
404 int scale = (bits[3] >> 16) & 0x1F;
408 int[] radix = new int[4];
412 for (int i=0; i < scale; i++)
413 wide_number_multiply_by_10(radix);
417 while (!wide_number_less_than(bits, radix)) {
419 wide_number_multiply_by_16(radix);
422 if (num_chars == 0) {
428 StringBuilder chars = new StringBuilder();
433 for (int i=0; i < num_chars; i++) {
436 wide_number_divide_by_16(radix);
438 while (!wide_number_less_than(bits, radix)) { // greater than or equals
440 wide_number_subtract(bits, radix);
444 chars.Append((char)('0' + digit));
446 chars.Append((char)('A' + digit - 10));
451 Text = chars.ToString();
455 protected override void ValidateEditText() {
461 protected override void OnLostFocus(EventArgs e) {
464 this.UpdateEditText();
467 #endregion // Protected Instance Methods
470 static object ValueChangedEvent = new object ();
472 public event EventHandler ValueChanged {
473 add { Events.AddHandler (ValueChangedEvent, value); }
474 remove { Events.RemoveHandler (ValueChangedEvent, value); }
478 [EditorBrowsable(EditorBrowsableState.Never)]
479 public new event EventHandler TextChanged {
480 add { base.TextChanged += value; }
481 remove { base.TextChanged -= value; }