using System; using System.Collections.Generic; using System.ComponentModel; using Avalonia.Controls.Generators; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Styling; #nullable enable namespace Avalonia.Controls { /// /// A control context menu. /// public class ContextMenu : MenuBase, ISetterValue { private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel { Orientation = Orientation.Vertical }); private Popup? _popup; private List? _attachedControls; private IInputElement? _previousFocus; /// /// Initializes a new instance of the class. /// public ContextMenu() : this(new DefaultMenuInteractionHandler(true)) { } /// /// Initializes a new instance of the class. /// /// The menu interaction handler. public ContextMenu(IMenuInteractionHandler interactionHandler) : base(interactionHandler) { } /// /// Initializes static members of the class. /// static ContextMenu() { ItemsPanelProperty.OverrideDefaultValue(typeof(ContextMenu), DefaultPanel); ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); } /// /// 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 is ContextMenu oldMenu) { control.PointerReleased -= ControlPointerReleased; oldMenu._attachedControls?.Remove(control); ((ISetLogicalParent?)oldMenu._popup)?.SetParent(null); } if (e.NewValue is ContextMenu newMenu) { newMenu._attachedControls ??= new List(); newMenu._attachedControls.Add(control); control.PointerReleased += ControlPointerReleased; } } /// /// Opens the menu. /// public override void Open() => Open(null); /// /// Opens a context menu on the specified control. /// /// The control. public void Open(Control? control) { if (control is null && (_attachedControls is null || _attachedControls.Count == 0)) { throw new ArgumentNullException(nameof(control)); } if (control is object && _attachedControls is object && !_attachedControls.Contains(control)) { throw new ArgumentException( "Cannot show ContentMenu on a different control to the one it is attached to.", nameof(control)); } control ??= _attachedControls![0]; if (IsOpen) { return; } if (_popup == null) { _popup = new Popup { PlacementMode = PlacementMode.Pointer, PlacementTarget = control, StaysOpen = false }; _popup.Opened += PopupOpened; _popup.Closed += PopupClosed; } if (_popup.Parent != control) { ((ISetLogicalParent)_popup).SetParent(null); ((ISetLogicalParent)_popup).SetParent(control); } _popup.Child = this; _popup.IsOpen = true; IsOpen = true; RaiseEvent(new RoutedEventArgs { RoutedEvent = MenuOpenedEvent, Source = this, }); } /// /// Closes the menu. /// public override void Close() { if (!IsOpen) { return; } if (_popup != null && _popup.IsVisible) { _popup.IsOpen = false; } } void ISetterValue.Initialize(ISetter setter) { // ContextMenu can be assigned to the ContextMenu property in a setter. This overrides // the behavior defined in Control which requires controls to be wrapped in a