2
0

NumericUpDown.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. #nullable enable
  2. using System.ComponentModel;
  3. using Terminal.Gui;
  4. /// <summary>
  5. /// Enables the user to increase or decrease a value by clicking on the up or down buttons.
  6. /// </summary>
  7. /// <remarks>
  8. /// Supports the following types: <see cref="int"/>, <see cref="long"/>, <see cref="double"/>, <see cref="double"/>,
  9. /// <see cref="decimal"/>.
  10. /// Supports only one digit of precision.
  11. /// </remarks>
  12. public class NumericUpDown<T> : View
  13. {
  14. private readonly Button _down;
  15. // TODO: Use a TextField instead of a Label
  16. private readonly View _number;
  17. private readonly Button _up;
  18. public NumericUpDown ()
  19. {
  20. Type type = typeof (T);
  21. if (!(type == typeof (int) || type == typeof (long) || type == typeof (double) || type == typeof (float) || type == typeof (double) || type == typeof (decimal)))
  22. {
  23. // Support object for AllViewsTester
  24. if (type != typeof (object))
  25. {
  26. throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction.");
  27. }
  28. }
  29. switch (typeof (T))
  30. {
  31. case { } i when i == typeof (int):
  32. Minimum = (dynamic)int.MinValue;
  33. Maximum = (dynamic)int.MaxValue;
  34. Increment = (dynamic)1;
  35. break;
  36. case { } i when i == typeof (long):
  37. Minimum = (dynamic)long.MinValue;
  38. Maximum = (dynamic)long.MaxValue;
  39. Increment = (dynamic)1;
  40. break;
  41. case { } i when i == typeof (double):
  42. Minimum = (dynamic)double.MinValue;
  43. Maximum = (dynamic)double.MaxValue;
  44. Increment = (dynamic)1;
  45. break;
  46. case { } i when i == typeof (float):
  47. Minimum = (dynamic)float.MinValue;
  48. Maximum = (dynamic)float.MaxValue;
  49. Increment = (dynamic)1;
  50. break;
  51. case { } i when i == typeof (decimal):
  52. Minimum = (dynamic)decimal.MinValue;
  53. Maximum = (dynamic)decimal.MaxValue;
  54. Increment = (dynamic)1;
  55. break;
  56. }
  57. Width = Dim.Auto (DimAutoStyle.Content); //Dim.Function (() => Digits + 2); // button + 3 for number + button
  58. Height = Dim.Auto (DimAutoStyle.Content);
  59. _down = new ()
  60. {
  61. Height = 1,
  62. Width = 1,
  63. NoPadding = true,
  64. NoDecorations = true,
  65. Title = $"{Glyphs.DownArrow}",
  66. WantContinuousButtonPressed = true,
  67. CanFocus = false,
  68. ShadowStyle = ShadowStyle.None
  69. };
  70. _number = new ()
  71. {
  72. Text = Value?.ToString () ?? "Err",
  73. X = Pos.Right (_down),
  74. Y = Pos.Top (_down),
  75. Width = Dim.Func (() => Digits),
  76. Height = 1,
  77. TextAlignment = Alignment.Center,
  78. CanFocus = true
  79. };
  80. _up = new ()
  81. {
  82. X = Pos.AnchorEnd (),
  83. Y = Pos.Top (_number),
  84. Height = 1,
  85. Width = 1,
  86. NoPadding = true,
  87. NoDecorations = true,
  88. Title = $"{Glyphs.UpArrow}",
  89. WantContinuousButtonPressed = true,
  90. CanFocus = false,
  91. ShadowStyle = ShadowStyle.None
  92. };
  93. CanFocus = true;
  94. _down.Accept += OnDownButtonOnAccept;
  95. _up.Accept += OnUpButtonOnAccept;
  96. Add (_down, _number, _up);
  97. AddCommand (
  98. Command.ScrollUp,
  99. () =>
  100. {
  101. if (type == typeof (object))
  102. {
  103. return false;
  104. }
  105. if (Value is { })
  106. {
  107. Value = (dynamic)Value + Increment;
  108. _number.Text = Value?.ToString () ?? string.Empty;
  109. }
  110. return true;
  111. });
  112. AddCommand (
  113. Command.ScrollDown,
  114. () =>
  115. {
  116. if (type == typeof (object))
  117. {
  118. return false;
  119. }
  120. if (Value is { })
  121. {
  122. Value = (dynamic)Value - Increment;
  123. _number.Text = Value.ToString () ?? string.Empty;
  124. }
  125. return true;
  126. });
  127. KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
  128. KeyBindings.Add (Key.CursorDown, Command.ScrollDown);
  129. return;
  130. void OnDownButtonOnAccept (object s, HandledEventArgs e)
  131. {
  132. InvokeCommand (Command.ScrollDown);
  133. }
  134. void OnUpButtonOnAccept (object s, HandledEventArgs e)
  135. {
  136. InvokeCommand (Command.ScrollUp);
  137. }
  138. }
  139. private void _up_Enter (object sender, FocusEventArgs e) { throw new NotImplementedException (); }
  140. private T _value;
  141. /// <summary>
  142. /// The value that will be incremented or decremented.
  143. /// </summary>
  144. public T Value
  145. {
  146. get => _value;
  147. set
  148. {
  149. if (_value.Equals (value))
  150. {
  151. return;
  152. }
  153. T oldValue = value;
  154. CancelEventArgs<T> args = new (ref _value, ref value);
  155. ValueChanging?.Invoke (this, args);
  156. if (args.Cancel)
  157. {
  158. return;
  159. }
  160. if (Comparer<T>.Default.Compare (value, Minimum) < 0)
  161. {
  162. value = Minimum;
  163. }
  164. if (Comparer<T>.Default.Compare (value, Maximum) > 0)
  165. {
  166. value = Maximum;
  167. }
  168. _value = value;
  169. _number.Text = _value?.ToString () ?? string.Empty;
  170. ValueChanged?.Invoke (this, new (ref _value));
  171. }
  172. }
  173. /// <summary>
  174. /// Fired when the value is about to change. Set <see cref="CancelEventArgs{T}.Cancel"/> to true to prevent the change.
  175. /// </summary>
  176. [CanBeNull]
  177. public event EventHandler<CancelEventArgs<T>> ValueChanging;
  178. /// <summary>
  179. /// Fired when the value has changed.
  180. /// </summary>
  181. [CanBeNull]
  182. public event EventHandler<EventArgs<T>> ValueChanged;
  183. /// <summary>
  184. /// The number of digits to display. The <see cref="View.Viewport"/> will be resized to fit this number of characters
  185. /// plus the buttons. The default is 3.
  186. /// </summary>
  187. public int Digits { get; set; } = 3;
  188. private T _minimum;
  189. /// <summary>
  190. ///
  191. /// </summary>
  192. public T Minimum
  193. {
  194. get { return _minimum; }
  195. set { _minimum = value; }
  196. }
  197. private T _maximum;
  198. /// <summary>
  199. ///
  200. /// </summary>
  201. public T Maximum
  202. {
  203. get { return _maximum; }
  204. set { _maximum = value; }
  205. }
  206. private T _increment;
  207. /// <summary>
  208. ///
  209. /// </summary>
  210. public T Increment
  211. {
  212. get { return _increment; }
  213. set { _increment = value; }
  214. }
  215. }
  216. /// <summary>
  217. /// Enables the user to increase or decrease an <see langword="int"/> by clicking on the up or down buttons.
  218. /// </summary>
  219. public class NumericUpDown : NumericUpDown<int>
  220. {
  221. }