MenuBase.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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.Collections.Generic;
  5. using System.Linq;
  6. using Avalonia.Controls.Generators;
  7. using Avalonia.Controls.Platform;
  8. using Avalonia.Controls.Primitives;
  9. using Avalonia.Controls.Templates;
  10. using Avalonia.Input;
  11. using Avalonia.Interactivity;
  12. using Avalonia.LogicalTree;
  13. namespace Avalonia.Controls
  14. {
  15. /// <summary>
  16. /// Base class for menu controls.
  17. /// </summary>
  18. public abstract class MenuBase : SelectingItemsControl, IMenu
  19. {
  20. /// <summary>
  21. /// Defines the <see cref="IsOpen"/> property.
  22. /// </summary>
  23. public static readonly DirectProperty<Menu, bool> IsOpenProperty =
  24. AvaloniaProperty.RegisterDirect<Menu, bool>(
  25. nameof(IsOpen),
  26. o => o.IsOpen);
  27. /// <summary>
  28. /// Defines the <see cref="MenuOpened"/> event.
  29. /// </summary>
  30. public static readonly RoutedEvent<RoutedEventArgs> MenuOpenedEvent =
  31. RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(MenuOpened), RoutingStrategies.Bubble);
  32. /// <summary>
  33. /// Defines the <see cref="MenuClosed"/> event.
  34. /// </summary>
  35. public static readonly RoutedEvent<RoutedEventArgs> MenuClosedEvent =
  36. RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(MenuClosed), RoutingStrategies.Bubble);
  37. private bool _isOpen;
  38. /// <summary>
  39. /// Initializes a new instance of the <see cref="MenuBase"/> class.
  40. /// </summary>
  41. public MenuBase()
  42. {
  43. InteractionHandler = AvaloniaLocator.Current.GetService<IMenuInteractionHandler>() ??
  44. new DefaultMenuInteractionHandler();
  45. }
  46. /// <summary>
  47. /// Initializes a new instance of the <see cref="MenuBase"/> class.
  48. /// </summary>
  49. /// <param name="interactionHandler">The menu interaction handler.</param>
  50. public MenuBase(IMenuInteractionHandler interactionHandler)
  51. {
  52. Contract.Requires<ArgumentNullException>(interactionHandler != null);
  53. InteractionHandler = interactionHandler;
  54. }
  55. /// <summary>
  56. /// Initializes static members of the <see cref="MenuBase"/> class.
  57. /// </summary>
  58. static MenuBase()
  59. {
  60. MenuItem.SubmenuOpenedEvent.AddClassHandler<MenuBase>(x => x.OnSubmenuOpened);
  61. }
  62. /// <summary>
  63. /// Gets a value indicating whether the menu is open.
  64. /// </summary>
  65. public bool IsOpen
  66. {
  67. get { return _isOpen; }
  68. protected set { SetAndRaise(IsOpenProperty, ref _isOpen, value); }
  69. }
  70. /// <inheritdoc/>
  71. IMenuInteractionHandler IMenu.InteractionHandler => InteractionHandler;
  72. /// <inheritdoc/>
  73. IMenuItem IMenuElement.SelectedItem
  74. {
  75. get
  76. {
  77. var index = SelectedIndex;
  78. return (index != -1) ?
  79. (IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) :
  80. null;
  81. }
  82. set
  83. {
  84. SelectedIndex = ItemContainerGenerator.IndexFromContainer(value);
  85. }
  86. }
  87. /// <inheritdoc/>
  88. IEnumerable<IMenuItem> IMenuElement.SubItems
  89. {
  90. get
  91. {
  92. return ItemContainerGenerator.Containers
  93. .Select(x => x.ContainerControl)
  94. .OfType<IMenuItem>();
  95. }
  96. }
  97. /// <summary>
  98. /// Gets the interaction handler for the menu.
  99. /// </summary>
  100. protected IMenuInteractionHandler InteractionHandler { get; }
  101. /// <summary>
  102. /// Occurs when a <see cref="Menu"/> is opened.
  103. /// </summary>
  104. public event EventHandler<RoutedEventArgs> MenuOpened
  105. {
  106. add { AddHandler(MenuOpenedEvent, value); }
  107. remove { RemoveHandler(MenuOpenedEvent, value); }
  108. }
  109. /// <summary>
  110. /// Occurs when a <see cref="Menu"/> is closed.
  111. /// </summary>
  112. public event EventHandler<RoutedEventArgs> MenuClosed
  113. {
  114. add { AddHandler(MenuClosedEvent, value); }
  115. remove { RemoveHandler(MenuClosedEvent, value); }
  116. }
  117. /// <summary>
  118. /// Closes the menu.
  119. /// </summary>
  120. public abstract void Close();
  121. /// <summary>
  122. /// Opens the menu.
  123. /// </summary>
  124. public abstract void Open();
  125. /// <inheritdoc/>
  126. bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap);
  127. /// <inheritdoc/>
  128. protected override IItemContainerGenerator CreateItemContainerGenerator()
  129. {
  130. return new ItemContainerGenerator<MenuItem>(this, MenuItem.HeaderProperty, null);
  131. }
  132. /// <inheritdoc/>
  133. protected override void OnKeyDown(KeyEventArgs e)
  134. {
  135. // Don't handle here: let the interaction handler handle it.
  136. }
  137. /// <inheritdoc/>
  138. protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
  139. {
  140. base.OnAttachedToVisualTree(e);
  141. InteractionHandler.Attach(this);
  142. }
  143. /// <inheritdoc/>
  144. protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
  145. {
  146. base.OnDetachedFromVisualTree(e);
  147. InteractionHandler.Detach(this);
  148. }
  149. /// <summary>
  150. /// Called when a submenu opens somewhere in the menu.
  151. /// </summary>
  152. /// <param name="e">The event args.</param>
  153. protected virtual void OnSubmenuOpened(RoutedEventArgs e)
  154. {
  155. if (e.Source is MenuItem menuItem && menuItem.Parent == this)
  156. {
  157. foreach (var child in this.GetLogicalChildren().OfType<MenuItem>())
  158. {
  159. if (child != menuItem && child.IsSubMenuOpen)
  160. {
  161. child.IsSubMenuOpen = false;
  162. }
  163. }
  164. }
  165. IsOpen = true;
  166. }
  167. }
  168. }