Button.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Linq;
  5. using System.Windows.Input;
  6. using Avalonia.Data;
  7. using Avalonia.Input;
  8. using Avalonia.Interactivity;
  9. using Avalonia.VisualTree;
  10. namespace Avalonia.Controls
  11. {
  12. /// <summary>
  13. /// Defines how a <see cref="Button"/> reacts to clicks.
  14. /// </summary>
  15. public enum ClickMode
  16. {
  17. /// <summary>
  18. /// The <see cref="Button.Click"/> event is raised when the pointer is released.
  19. /// </summary>
  20. Release,
  21. /// <summary>
  22. /// The <see cref="Button.Click"/> event is raised when the pointer is pressed.
  23. /// </summary>
  24. Press,
  25. }
  26. /// <summary>
  27. /// A button control.
  28. /// </summary>
  29. public class Button : ContentControl
  30. {
  31. /// <summary>
  32. /// Defines the <see cref="ClickMode"/> property.
  33. /// </summary>
  34. public static readonly StyledProperty<ClickMode> ClickModeProperty =
  35. AvaloniaProperty.Register<Button, ClickMode>(nameof(ClickMode));
  36. /// <summary>
  37. /// Defines the <see cref="Command"/> property.
  38. /// </summary>
  39. public static readonly DirectProperty<Button, ICommand> CommandProperty =
  40. AvaloniaProperty.RegisterDirect<Button, ICommand>(nameof(Command),
  41. button => button.Command, (button, command) => button.Command = command, enableDataValidation: true);
  42. /// <summary>
  43. /// Defines the <see cref="HotKey"/> property.
  44. /// </summary>
  45. public static readonly StyledProperty<KeyGesture> HotKeyProperty =
  46. HotKeyManager.HotKeyProperty.AddOwner<Button>();
  47. /// <summary>
  48. /// Defines the <see cref="CommandParameter"/> property.
  49. /// </summary>
  50. public static readonly StyledProperty<object> CommandParameterProperty =
  51. AvaloniaProperty.Register<Button, object>(nameof(CommandParameter));
  52. /// <summary>
  53. /// Defines the <see cref="IsDefaultProperty"/> property.
  54. /// </summary>
  55. public static readonly StyledProperty<bool> IsDefaultProperty =
  56. AvaloniaProperty.Register<Button, bool>(nameof(IsDefault));
  57. /// <summary>
  58. /// Defines the <see cref="Click"/> event.
  59. /// </summary>
  60. public static readonly RoutedEvent<RoutedEventArgs> ClickEvent =
  61. RoutedEvent.Register<Button, RoutedEventArgs>(nameof(Click), RoutingStrategies.Bubble);
  62. private ICommand _command;
  63. public static readonly StyledProperty<bool> IsPressedProperty =
  64. AvaloniaProperty.Register<Button, bool>(nameof(IsPressed));
  65. /// <summary>
  66. /// Initializes static members of the <see cref="Button"/> class.
  67. /// </summary>
  68. static Button()
  69. {
  70. FocusableProperty.OverrideDefaultValue(typeof(Button), true);
  71. CommandProperty.Changed.Subscribe(CommandChanged);
  72. IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
  73. PseudoClass<Button>(IsPressedProperty, ":pressed");
  74. }
  75. /// <summary>
  76. /// Raised when the user clicks the button.
  77. /// </summary>
  78. public event EventHandler<RoutedEventArgs> Click
  79. {
  80. add { AddHandler(ClickEvent, value); }
  81. remove { RemoveHandler(ClickEvent, value); }
  82. }
  83. /// <summary>
  84. /// Gets or sets a value indicating how the <see cref="Button"/> should react to clicks.
  85. /// </summary>
  86. public ClickMode ClickMode
  87. {
  88. get { return GetValue(ClickModeProperty); }
  89. set { SetValue(ClickModeProperty, value); }
  90. }
  91. /// <summary>
  92. /// Gets or sets an <see cref="ICommand"/> to be invoked when the button is clicked.
  93. /// </summary>
  94. public ICommand Command
  95. {
  96. get { return _command; }
  97. set { SetAndRaise(CommandProperty, ref _command, value); }
  98. }
  99. /// <summary>
  100. /// Gets or sets an <see cref="KeyGesture"/> associated with this control
  101. /// </summary>
  102. public KeyGesture HotKey
  103. {
  104. get { return GetValue(HotKeyProperty); }
  105. set { SetValue(HotKeyProperty, value); }
  106. }
  107. /// <summary>
  108. /// Gets or sets a parameter to be passed to the <see cref="Command"/>.
  109. /// </summary>
  110. public object CommandParameter
  111. {
  112. get { return GetValue(CommandParameterProperty); }
  113. set { SetValue(CommandParameterProperty, value); }
  114. }
  115. /// <summary>
  116. /// Gets or sets a value indicating whether the button is the default button for the
  117. /// window.
  118. /// </summary>
  119. public bool IsDefault
  120. {
  121. get { return GetValue(IsDefaultProperty); }
  122. set { SetValue(IsDefaultProperty, value); }
  123. }
  124. public bool IsPressed
  125. {
  126. get { return GetValue(IsPressedProperty); }
  127. private set { SetValue(IsPressedProperty, value); }
  128. }
  129. /// <inheritdoc/>
  130. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
  131. {
  132. base.OnAttachedToVisualTree(e);
  133. if (IsDefault)
  134. {
  135. if (e.Root is IInputElement inputElement)
  136. {
  137. ListenForDefault(inputElement);
  138. }
  139. }
  140. }
  141. /// <inheritdoc/>
  142. protected override void OnKeyDown(KeyEventArgs e)
  143. {
  144. if (e.Key == Key.Enter)
  145. {
  146. OnClick();
  147. e.Handled = true;
  148. }
  149. else if (e.Key == Key.Space)
  150. {
  151. if (ClickMode == ClickMode.Press)
  152. {
  153. OnClick();
  154. }
  155. IsPressed = true;
  156. e.Handled = true;
  157. }
  158. base.OnKeyDown(e);
  159. }
  160. /// <inheritdoc/>
  161. protected override void OnKeyUp(KeyEventArgs e)
  162. {
  163. if (e.Key == Key.Space)
  164. {
  165. if (ClickMode == ClickMode.Release)
  166. {
  167. OnClick();
  168. }
  169. IsPressed = false;
  170. e.Handled = true;
  171. }
  172. }
  173. /// <inheritdoc/>
  174. protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
  175. {
  176. base.OnDetachedFromVisualTree(e);
  177. if (IsDefault)
  178. {
  179. if (e.Root is IInputElement inputElement)
  180. {
  181. StopListeningForDefault(inputElement);
  182. }
  183. }
  184. }
  185. /// <summary>
  186. /// Invokes the <see cref="Click"/> event.
  187. /// </summary>
  188. protected virtual void OnClick()
  189. {
  190. var e = new RoutedEventArgs(ClickEvent);
  191. RaiseEvent(e);
  192. if (!e.Handled && Command?.CanExecute(CommandParameter) == true)
  193. {
  194. Command.Execute(CommandParameter);
  195. e.Handled = true;
  196. }
  197. }
  198. /// <inheritdoc/>
  199. protected override void OnPointerPressed(PointerPressedEventArgs e)
  200. {
  201. base.OnPointerPressed(e);
  202. if (e.MouseButton == MouseButton.Left)
  203. {
  204. e.Device.Capture(this);
  205. IsPressed = true;
  206. e.Handled = true;
  207. if (ClickMode == ClickMode.Press)
  208. {
  209. OnClick();
  210. }
  211. }
  212. }
  213. /// <inheritdoc/>
  214. protected override void OnPointerReleased(PointerReleasedEventArgs e)
  215. {
  216. base.OnPointerReleased(e);
  217. if (IsPressed && e.MouseButton == MouseButton.Left)
  218. {
  219. e.Device.Capture(null);
  220. IsPressed = false;
  221. e.Handled = true;
  222. if (ClickMode == ClickMode.Release &&
  223. this.GetVisualsAt(e.GetPosition(this)).Any(c => this == c || this.IsVisualAncestorOf(c)))
  224. {
  225. OnClick();
  226. }
  227. }
  228. }
  229. protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status)
  230. {
  231. base.UpdateDataValidation(property, status);
  232. if (property == CommandProperty)
  233. {
  234. if (status?.ErrorType == BindingErrorType.Error)
  235. {
  236. IsEnabled = false;
  237. }
  238. }
  239. }
  240. /// <summary>
  241. /// Called when the <see cref="Command"/> property changes.
  242. /// </summary>
  243. /// <param name="e">The event args.</param>
  244. private static void CommandChanged(AvaloniaPropertyChangedEventArgs e)
  245. {
  246. if (e.Sender is Button button)
  247. {
  248. var oldCommand = e.OldValue as ICommand;
  249. var newCommand = e.NewValue as ICommand;
  250. if (oldCommand != null)
  251. {
  252. oldCommand.CanExecuteChanged -= button.CanExecuteChanged;
  253. }
  254. if (newCommand != null)
  255. {
  256. newCommand.CanExecuteChanged += button.CanExecuteChanged;
  257. }
  258. button.CanExecuteChanged(button, EventArgs.Empty);
  259. }
  260. }
  261. /// <summary>
  262. /// Called when the <see cref="IsDefault"/> property changes.
  263. /// </summary>
  264. /// <param name="e">The event args.</param>
  265. private static void IsDefaultChanged(AvaloniaPropertyChangedEventArgs e)
  266. {
  267. var button = e.Sender as Button;
  268. var isDefault = (bool)e.NewValue;
  269. if (button?.VisualRoot is IInputElement inputRoot)
  270. {
  271. if (isDefault)
  272. {
  273. button.ListenForDefault(inputRoot);
  274. }
  275. else
  276. {
  277. button.StopListeningForDefault(inputRoot);
  278. }
  279. }
  280. }
  281. /// <summary>
  282. /// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires.
  283. /// </summary>
  284. /// <param name="sender">The event sender.</param>
  285. /// <param name="e">The event args.</param>
  286. private void CanExecuteChanged(object sender, EventArgs e)
  287. {
  288. // HACK: Just set the IsEnabled property for the moment. This needs to be changed to
  289. // use IsEnabledCore etc. but it will do for now.
  290. IsEnabled = Command == null || Command.CanExecute(CommandParameter);
  291. }
  292. /// <summary>
  293. /// Starts listening for the Enter key when the button <see cref="IsDefault"/>.
  294. /// </summary>
  295. /// <param name="root">The input root.</param>
  296. private void ListenForDefault(IInputElement root)
  297. {
  298. root.AddHandler(KeyDownEvent, RootKeyDown);
  299. }
  300. /// <summary>
  301. /// Stops listening for the Enter key when the button is no longer <see cref="IsDefault"/>.
  302. /// </summary>
  303. /// <param name="root">The input root.</param>
  304. private void StopListeningForDefault(IInputElement root)
  305. {
  306. root.RemoveHandler(KeyDownEvent, RootKeyDown);
  307. }
  308. /// <summary>
  309. /// Called when a key is pressed on the input root and the button <see cref="IsDefault"/>.
  310. /// </summary>
  311. /// <param name="sender">The event sender.</param>
  312. /// <param name="e">The event args.</param>
  313. private void RootKeyDown(object sender, KeyEventArgs e)
  314. {
  315. if (e.Key == Key.Enter && IsVisible && IsEnabled)
  316. {
  317. OnClick();
  318. }
  319. }
  320. }
  321. }