NumericUpDown.cs 7.6 KB

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