using System; using System.Reactive.Linq; using System.Linq; using System.ComponentModel; using Avalonia.Controls.Platform; using System.Collections.Generic; using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Controls.Primitives; namespace Avalonia.Controls { public class ContextMenu : SelectingItemsControl, IMenu { private readonly IMenuInteractionHandler _interaction; private bool _isOpen; private Popup _popup; /// /// Defines the property. /// public static readonly DirectProperty IsOpenProperty = AvaloniaProperty.RegisterDirect(nameof(IsOpen), o => o.IsOpen); /// /// Initializes a new instance of the class. /// public ContextMenu() { _interaction = AvaloniaLocator.Current.GetService() ?? new DefaultMenuInteractionHandler(); } /// /// Initializes a new instance of the class. /// /// The menu iteraction handler. public ContextMenu(IMenuInteractionHandler interactionHandler) { Contract.Requires(interactionHandler != null); _interaction = interactionHandler; } /// /// Initializes static members of the class. /// static ContextMenu() { ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); } /// /// Gets a value indicating whether the popup is open /// public bool IsOpen => _isOpen; /// IMenuInteractionHandler IMenu.InteractionHandler => _interaction; /// IMenuItem IMenuElement.SelectedItem { get { var index = SelectedIndex; return (index != -1) ? (IMenuItem)ItemContainerGenerator.ContainerFromIndex(index) : null; } set { SelectedIndex = ItemContainerGenerator.IndexFromContainer(value); } } /// IEnumerable IMenuElement.SubItems { get { return ItemContainerGenerator.Containers .Select(x => x.ContainerControl) .OfType(); } } /// /// Occurs when the value of the /// /// property is changing from false to true. /// public event CancelEventHandler ContextMenuOpening; /// /// Occurs when the value of the /// /// property is changing from true to false. /// public event CancelEventHandler ContextMenuClosing; /// /// Called when the property changes on a control. /// /// The event args. private static void ContextMenuChanged(AvaloniaPropertyChangedEventArgs e) { var control = (Control)e.Sender; if (e.OldValue != null) { control.PointerReleased -= ControlPointerReleased; } if (e.NewValue != null) { control.PointerReleased += ControlPointerReleased; } } /// /// Opens the menu. /// public void Open() => Open(null); /// /// Opens a context menu on the specified control. /// /// The control. public void Open(Control control) { if (_popup == null) { _popup = new Popup() { PlacementMode = PlacementMode.Pointer, PlacementTarget = control, StaysOpen = false, ObeyScreenEdges = true }; _popup.Closed += PopupClosed; _interaction.Attach(this); } ((ISetLogicalParent)_popup).SetParent(control); _popup.Child = this; _popup.IsOpen = true; SetAndRaise(IsOpenProperty, ref _isOpen, true); } /// /// Closes the menu. /// public void Close() { if (_popup != null && _popup.IsVisible) { _popup.IsOpen = false; } SelectedIndex = -1; SetAndRaise(IsOpenProperty, ref _isOpen, false); } private void PopupClosed(object sender, EventArgs e) { var contextMenu = (sender as Popup)?.Child as ContextMenu; if (contextMenu != null) { foreach (var i in contextMenu.GetLogicalChildren().OfType()) { i.IsSubMenuOpen = false; } contextMenu._isOpen = false; contextMenu.SelectedIndex = -1; } } private static void ControlPointerReleased(object sender, PointerReleasedEventArgs e) { var control = (Control)sender; var contextMenu = control.ContextMenu; if (control.ContextMenu._isOpen) { if (contextMenu.CancelClosing()) return; control.ContextMenu.Close(); e.Handled = true; } if (e.MouseButton == MouseButton.Right) { if (contextMenu.CancelOpening()) return; contextMenu.Open(control); e.Handled = true; } } private bool CancelClosing() { var eventArgs = new CancelEventArgs(); ContextMenuClosing?.Invoke(this, eventArgs); return eventArgs.Cancel; } private bool CancelOpening() { var eventArgs = new CancelEventArgs(); ContextMenuOpening?.Invoke(this, eventArgs); return eventArgs.Cancel; } bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) { throw new NotImplementedException(); } } }