2
0

NumericUpDown.cs 7.1 KB

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