NumericUpDown.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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.Accept += OnDownButtonOnAccept;
  78. _up.Accept += 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, HandledEventArgs e) { InvokeCommand (Command.ScrollDown); }
  113. void OnUpButtonOnAccept (object? s, HandledEventArgs e) { InvokeCommand (Command.ScrollUp); }
  114. }
  115. private T _value = default!;
  116. /// <summary>
  117. /// Gets or sets the value that will be incremented or decremented.
  118. /// </summary>
  119. /// <remarks>
  120. /// <para>
  121. /// <see cref="ValueChanging"/> and <see cref="ValueChanged"/> events are raised when the value changes.
  122. /// The <see cref="ValueChanging"/> event can be canceled the change setting
  123. /// <see cref="CancelEventArgs{T}"/><c>.Cancel</c> to <see langword="true"/>.
  124. /// </para>
  125. /// </remarks>
  126. public T Value
  127. {
  128. get => _value;
  129. set
  130. {
  131. if (_value.Equals (value))
  132. {
  133. return;
  134. }
  135. T oldValue = value;
  136. CancelEventArgs<T> args = new (in _value, ref value);
  137. ValueChanging?.Invoke (this, args);
  138. if (args.Cancel)
  139. {
  140. return;
  141. }
  142. _value = value;
  143. SetText ();
  144. ValueChanged?.Invoke (this, new (in value));
  145. }
  146. }
  147. /// <summary>
  148. /// Raised when the value is about to change. Set <see cref="CancelEventArgs{T}"/><c>.Cancel</c> to true to prevent the
  149. /// change.
  150. /// </summary>
  151. public event EventHandler<CancelEventArgs<T>>? ValueChanging;
  152. /// <summary>
  153. /// Raised when the value has changed.
  154. /// </summary>
  155. public event EventHandler<EventArgs<T>>? ValueChanged;
  156. private string _format = "{0}";
  157. /// <summary>
  158. /// Gets or sets the format string used to display the value. The default is "{0}".
  159. /// </summary>
  160. public string Format
  161. {
  162. get => _format;
  163. set
  164. {
  165. if (_format == value)
  166. {
  167. return;
  168. }
  169. _format = value;
  170. FormatChanged?.Invoke (this, new (value));
  171. SetText ();
  172. }
  173. }
  174. /// <summary>
  175. /// Raised when <see cref="Format"/> has changed.
  176. /// </summary>
  177. public event EventHandler<EventArgs<string>>? FormatChanged;
  178. private void SetText ()
  179. {
  180. _number.Text = string.Format (Format, _value);
  181. Text = _number.Text;
  182. }
  183. private T? _increment;
  184. /// <summary>
  185. /// </summary>
  186. public T? Increment
  187. {
  188. get => _increment;
  189. set
  190. {
  191. if (_increment is { } && value is { } && (dynamic)_increment == (dynamic)value)
  192. {
  193. return;
  194. }
  195. _increment = value;
  196. IncrementChanged?.Invoke (this, new (value!));
  197. }
  198. }
  199. /// <summary>
  200. /// Raised when <see cref="Increment"/> has changed.
  201. /// </summary>
  202. public event EventHandler<EventArgs<T>>? IncrementChanged;
  203. }
  204. /// <summary>
  205. /// Enables the user to increase or decrease an <see langword="int"/> by clicking on the up or down buttons.
  206. /// </summary>
  207. public class NumericUpDown : NumericUpDown<int>
  208. { }