ContextMenu.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. using System;
  2. using System.ComponentModel;
  3. using System.Linq;
  4. using Avalonia.Controls.Generators;
  5. using Avalonia.Controls.Platform;
  6. using Avalonia.Controls.Primitives;
  7. using Avalonia.Controls.Templates;
  8. using Avalonia.Input;
  9. using Avalonia.Interactivity;
  10. using Avalonia.Layout;
  11. using Avalonia.LogicalTree;
  12. namespace Avalonia.Controls
  13. {
  14. /// <summary>
  15. /// A control context menu.
  16. /// </summary>
  17. public class ContextMenu : MenuBase
  18. {
  19. private static readonly ITemplate<IPanel> DefaultPanel =
  20. new FuncTemplate<IPanel>(() => new StackPanel { Orientation = Orientation.Vertical });
  21. private Popup _popup;
  22. /// <summary>
  23. /// Initializes a new instance of the <see cref="ContextMenu"/> class.
  24. /// </summary>
  25. public ContextMenu()
  26. : this(new DefaultMenuInteractionHandler(true))
  27. {
  28. }
  29. /// <summary>
  30. /// Initializes a new instance of the <see cref="ContextMenu"/> class.
  31. /// </summary>
  32. /// <param name="interactionHandler">The menu interaction handler.</param>
  33. public ContextMenu(IMenuInteractionHandler interactionHandler)
  34. : base(interactionHandler)
  35. {
  36. }
  37. /// <summary>
  38. /// Initializes static members of the <see cref="ContextMenu"/> class.
  39. /// </summary>
  40. static ContextMenu()
  41. {
  42. ItemsPanelProperty.OverrideDefaultValue(typeof(ContextMenu), DefaultPanel);
  43. ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
  44. }
  45. /// <summary>
  46. /// Occurs when the value of the
  47. /// <see cref="P:Avalonia.Controls.ContextMenu.IsOpen" />
  48. /// property is changing from false to true.
  49. /// </summary>
  50. public event CancelEventHandler ContextMenuOpening;
  51. /// <summary>
  52. /// Occurs when the value of the
  53. /// <see cref="P:Avalonia.Controls.ContextMenu.IsOpen" />
  54. /// property is changing from true to false.
  55. /// </summary>
  56. public event CancelEventHandler ContextMenuClosing;
  57. /// <summary>
  58. /// Called when the <see cref="Control.ContextMenu"/> property changes on a control.
  59. /// </summary>
  60. /// <param name="e">The event args.</param>
  61. private static void ContextMenuChanged(AvaloniaPropertyChangedEventArgs e)
  62. {
  63. var control = (Control)e.Sender;
  64. if (e.OldValue != null)
  65. {
  66. control.PointerReleased -= ControlPointerReleased;
  67. }
  68. if (e.NewValue != null)
  69. {
  70. control.PointerReleased += ControlPointerReleased;
  71. }
  72. }
  73. /// <summary>
  74. /// Opens the menu.
  75. /// </summary>
  76. public override void Open() => Open(null);
  77. /// <summary>
  78. /// Opens a context menu on the specified control.
  79. /// </summary>
  80. /// <param name="control">The control.</param>
  81. public void Open(Control control)
  82. {
  83. if (control == null)
  84. throw new ArgumentNullException(nameof(control));
  85. if (IsOpen)
  86. {
  87. return;
  88. }
  89. if (_popup == null)
  90. {
  91. _popup = new Popup
  92. {
  93. PlacementMode = PlacementMode.Pointer,
  94. PlacementTarget = control,
  95. StaysOpen = false,
  96. ObeyScreenEdges = true
  97. };
  98. _popup.Opened += PopupOpened;
  99. _popup.Closed += PopupClosed;
  100. }
  101. ((ISetLogicalParent)_popup).SetParent(control);
  102. _popup.Child = this;
  103. _popup.IsOpen = true;
  104. IsOpen = true;
  105. RaiseEvent(new RoutedEventArgs
  106. {
  107. RoutedEvent = MenuOpenedEvent,
  108. Source = this,
  109. });
  110. }
  111. /// <summary>
  112. /// Closes the menu.
  113. /// </summary>
  114. public override void Close()
  115. {
  116. if (!IsOpen)
  117. {
  118. return;
  119. }
  120. if (_popup != null && _popup.IsVisible)
  121. {
  122. _popup.IsOpen = false;
  123. }
  124. }
  125. protected override IItemContainerGenerator CreateItemContainerGenerator()
  126. {
  127. return new MenuItemContainerGenerator(this);
  128. }
  129. private void CloseCore()
  130. {
  131. SelectedIndex = -1;
  132. IsOpen = false;
  133. RaiseEvent(new RoutedEventArgs
  134. {
  135. RoutedEvent = MenuClosedEvent,
  136. Source = this,
  137. });
  138. }
  139. private void PopupOpened(object sender, EventArgs e)
  140. {
  141. Focus();
  142. }
  143. private void PopupClosed(object sender, EventArgs e)
  144. {
  145. var contextMenu = (sender as Popup)?.Child as ContextMenu;
  146. if (contextMenu != null)
  147. {
  148. foreach (var i in contextMenu.GetLogicalChildren().OfType<MenuItem>())
  149. {
  150. i.IsSubMenuOpen = false;
  151. }
  152. contextMenu.CloseCore();
  153. }
  154. }
  155. private static void ControlPointerReleased(object sender, PointerReleasedEventArgs e)
  156. {
  157. var control = (Control)sender;
  158. var contextMenu = control.ContextMenu;
  159. if (control.ContextMenu.IsOpen)
  160. {
  161. if (contextMenu.CancelClosing())
  162. return;
  163. control.ContextMenu.Close();
  164. e.Handled = true;
  165. }
  166. if (e.MouseButton == MouseButton.Right)
  167. {
  168. if (contextMenu.CancelOpening())
  169. return;
  170. contextMenu.Open(control);
  171. e.Handled = true;
  172. }
  173. }
  174. private bool CancelClosing()
  175. {
  176. var eventArgs = new CancelEventArgs();
  177. ContextMenuClosing?.Invoke(this, eventArgs);
  178. return eventArgs.Cancel;
  179. }
  180. private bool CancelOpening()
  181. {
  182. var eventArgs = new CancelEventArgs();
  183. ContextMenuOpening?.Invoke(this, eventArgs);
  184. return eventArgs.Cancel;
  185. }
  186. }
  187. }