using System; using System.Collections.Generic; using System.ComponentModel; using Avalonia.Automation.Peers; using System.Linq; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Generators; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Styling; using Avalonia.Automation; using Avalonia.Reactive; namespace Avalonia.Controls { /// /// A control context menu. /// public class ContextMenu : MenuBase, ISetterValue, IPopupHostProvider { /// /// Defines the property. /// public static readonly StyledProperty HorizontalOffsetProperty = Popup.HorizontalOffsetProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty VerticalOffsetProperty = Popup.VerticalOffsetProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty PlacementAnchorProperty = Popup.PlacementAnchorProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty PlacementConstraintAdjustmentProperty = Popup.PlacementConstraintAdjustmentProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty PlacementGravityProperty = Popup.PlacementGravityProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty PlacementModeProperty = Popup.PlacementProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty PlacementRectProperty = Popup.PlacementRectProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty WindowManagerAddShadowHintProperty = Popup.WindowManagerAddShadowHintProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty PlacementTargetProperty = Popup.PlacementTargetProperty.AddOwner(); private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel { Orientation = Orientation.Vertical }); private Popup? _popup; private List? _attachedControls; private IInputElement? _previousFocus; private Action? _popupHostChangedHandler; /// /// 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(DefaultPanel); PlacementModeProperty.OverrideDefaultValue(PlacementMode.Pointer); ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); AutomationProperties.AccessibilityViewProperty.OverrideDefaultValue(AccessibilityView.Control); AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.Menu); } /// /// Gets or sets the Horizontal offset of the context menu in relation to the . /// public double HorizontalOffset { get { return GetValue(HorizontalOffsetProperty); } set { SetValue(HorizontalOffsetProperty, value); } } /// /// Gets or sets the Vertical offset of the context menu in relation to the . /// public double VerticalOffset { get { return GetValue(VerticalOffsetProperty); } set { SetValue(VerticalOffsetProperty, value); } } /// /// Gets or sets the anchor point on the when /// is . /// public PopupAnchor PlacementAnchor { get { return GetValue(PlacementAnchorProperty); } set { SetValue(PlacementAnchorProperty, value); } } /// /// Gets or sets a value describing how the context menu position will be adjusted if the /// unadjusted position would result in the context menu being partly constrained. /// public PopupPositionerConstraintAdjustment PlacementConstraintAdjustment { get { return GetValue(PlacementConstraintAdjustmentProperty); } set { SetValue(PlacementConstraintAdjustmentProperty, value); } } /// /// Gets or sets a value which defines in what direction the context menu should open /// when is . /// public PopupGravity PlacementGravity { get { return GetValue(PlacementGravityProperty); } set { SetValue(PlacementGravityProperty, value); } } /// /// Gets or sets the placement mode of the context menu in relation to the. /// public PlacementMode PlacementMode { get { return GetValue(PlacementModeProperty); } set { SetValue(PlacementModeProperty, value); } } public bool WindowManagerAddShadowHint { get { return GetValue(WindowManagerAddShadowHintProperty); } set { SetValue(WindowManagerAddShadowHintProperty, value); } } /// /// Gets or sets the the anchor rectangle within the parent that the context menu will be placed /// relative to when is . /// /// /// The placement rect defines a rectangle relative to around /// which the popup will be opened, with determining which edge /// of the placement target is used. /// /// If unset, the anchor rectangle will be the bounds of the . /// public Rect? PlacementRect { get { return GetValue(PlacementRectProperty); } set { SetValue(PlacementRectProperty, value); } } /// /// Gets or sets the control that is used to determine the popup's position. /// public Control? PlacementTarget { get { return GetValue(PlacementTargetProperty); } set { SetValue(PlacementTargetProperty, value); } } /// /// 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.ContextRequested -= ControlContextRequested; control.DetachedFromVisualTree -= ControlDetachedFromVisualTree; oldMenu._attachedControls?.Remove(control); ((ISetLogicalParent?)oldMenu._popup)?.SetParent(null); } if (e.NewValue is ContextMenu newMenu) { newMenu._attachedControls ??= new List(); newMenu._attachedControls.Add(control); control.ContextRequested += ControlContextRequested; control.DetachedFromVisualTree += ControlDetachedFromVisualTree; } } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == WindowManagerAddShadowHintProperty && _popup != null) { _popup.WindowManagerAddShadowHint = change.GetNewValue(); } } /// /// 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]; Open(control, PlacementTarget ?? control, false); } /// /// 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