NumericUpDown.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  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).GetColumns())),
  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.Up,
  82. (ctx) =>
  83. {
  84. if (type == typeof (object))
  85. {
  86. return false;
  87. }
  88. // BUGBUG: If this is uncommented, the numericupdown in a shortcut will not work
  89. //if (RaiseSelecting (ctx) is true)
  90. //{
  91. // return true;
  92. //}
  93. if (Value is { } && Increment is { })
  94. {
  95. Value = (dynamic)Value + (dynamic)Increment;
  96. }
  97. return true;
  98. });
  99. AddCommand (
  100. Command.Down,
  101. (ctx) =>
  102. {
  103. if (type == typeof (object))
  104. {
  105. return false;
  106. }
  107. // BUGBUG: If this is uncommented, the numericupdown in a shortcut will not work
  108. //if (RaiseSelecting (ctx) is true)
  109. //{
  110. // return true;
  111. //}
  112. if (Value is { } && Increment is { })
  113. {
  114. Value = (dynamic)Value - (dynamic)Increment;
  115. }
  116. return true;
  117. });
  118. KeyBindings.Add (Key.CursorUp, Command.Up);
  119. KeyBindings.Add (Key.CursorDown, Command.Down);
  120. SetText ();
  121. return;
  122. void OnDownButtonOnAccept (object? s, CommandEventArgs e)
  123. {
  124. InvokeCommand (Command.Down);
  125. e.Cancel = true;
  126. }
  127. void OnUpButtonOnAccept (object? s, CommandEventArgs e)
  128. {
  129. InvokeCommand (Command.Up);
  130. e.Cancel = true;
  131. }
  132. }
  133. private T _value = default!;
  134. /// <summary>
  135. /// Gets or sets the value that will be incremented or decremented.
  136. /// </summary>
  137. /// <remarks>
  138. /// <para>
  139. /// <see cref="ValueChanging"/> and <see cref="ValueChanged"/> events are raised when the value changes.
  140. /// The <see cref="ValueChanging"/> event can be canceled the change setting
  141. /// <see cref="CancelEventArgs{T}"/><c>.Cancel</c> to <see langword="true"/>.
  142. /// </para>
  143. /// </remarks>
  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 (in _value, ref value);
  155. ValueChanging?.Invoke (this, args);
  156. if (args.Cancel)
  157. {
  158. return;
  159. }
  160. _value = value;
  161. SetText ();
  162. ValueChanged?.Invoke (this, new (in value));
  163. }
  164. }
  165. /// <summary>
  166. /// Raised when the value is about to change. Set <see cref="CancelEventArgs{T}"/><c>.Cancel</c> to true to prevent the
  167. /// change.
  168. /// </summary>
  169. public event EventHandler<CancelEventArgs<T>>? ValueChanging;
  170. /// <summary>
  171. /// Raised when the value has changed.
  172. /// </summary>
  173. public event EventHandler<EventArgs<T>>? ValueChanged;
  174. private string _format = "{0}";
  175. /// <summary>
  176. /// Gets or sets the format string used to display the value. The default is "{0}".
  177. /// </summary>
  178. public string Format
  179. {
  180. get => _format;
  181. set
  182. {
  183. if (_format == value)
  184. {
  185. return;
  186. }
  187. _format = value;
  188. FormatChanged?.Invoke (this, new (value));
  189. SetText ();
  190. }
  191. }
  192. /// <summary>
  193. /// Raised when <see cref="Format"/> has changed.
  194. /// </summary>
  195. public event EventHandler<EventArgs<string>>? FormatChanged;
  196. private void SetText ()
  197. {
  198. _number.Text = string.Format (Format, _value);
  199. Text = _number.Text;
  200. }
  201. private T? _increment;
  202. /// <summary>
  203. /// </summary>
  204. public T? Increment
  205. {
  206. get => _increment;
  207. set
  208. {
  209. if (_increment is { } && value is { } && (dynamic)_increment == (dynamic)value)
  210. {
  211. return;
  212. }
  213. _increment = value;
  214. IncrementChanged?.Invoke (this, new (value!));
  215. }
  216. }
  217. /// <summary>
  218. /// Raised when <see cref="Increment"/> has changed.
  219. /// </summary>
  220. public event EventHandler<EventArgs<T>>? IncrementChanged;
  221. // Prevent the drawing of Text
  222. /// <inheritdoc />
  223. protected override bool OnDrawingText () { return true; }
  224. }
  225. /// <summary>
  226. /// Enables the user to increase or decrease an <see langword="int"/> by clicking on the up or down buttons.
  227. /// </summary>
  228. public class NumericUpDown : NumericUpDown<int>
  229. { }