diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index a69152c42b..e71934912b 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -1,34 +1,29 @@ using System; -using System.Reactive.Linq; -using System.Linq; using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; using Avalonia.Controls.Platform; -using System.Collections.Generic; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.LogicalTree; -using Avalonia.Controls.Primitives; namespace Avalonia.Controls { - public class ContextMenu : SelectingItemsControl, IMenu + /// + /// A control context menu. + /// + public class ContextMenu : MenuBase { - private readonly IMenuInteractionHandler _interaction; - private bool _isOpen; + private static readonly ITemplate DefaultPanel = + new FuncTemplate(() => new StackPanel { Orientation = Orientation.Vertical }); 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(); } /// @@ -36,10 +31,8 @@ namespace Avalonia.Controls /// /// The menu interaction handler. public ContextMenu(IMenuInteractionHandler interactionHandler) + : base(interactionHandler) { - Contract.Requires(interactionHandler != null); - - _interaction = interactionHandler; } /// @@ -47,44 +40,10 @@ namespace Avalonia.Controls /// static ContextMenu() { + ItemsPanelProperty.OverrideDefaultValue(typeof(ContextMenu), DefaultPanel); 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 /// @@ -121,7 +80,7 @@ namespace Avalonia.Controls /// /// Opens the menu. /// - public void Open() => Open(null); + public override void Open() => Open(null); /// /// Opens a context menu on the specified control. @@ -140,20 +99,18 @@ namespace Avalonia.Controls }; _popup.Closed += PopupClosed; - _interaction.Attach(this); } ((ISetLogicalParent)_popup).SetParent(control); _popup.Child = this; _popup.IsOpen = true; - - SetAndRaise(IsOpenProperty, ref _isOpen, true); + IsOpen = true; } /// /// Closes the menu. /// - public void Close() + public override void Close() { if (_popup != null && _popup.IsVisible) { @@ -161,8 +118,7 @@ namespace Avalonia.Controls } SelectedIndex = -1; - - SetAndRaise(IsOpenProperty, ref _isOpen, false); + IsOpen = false; } private void PopupClosed(object sender, EventArgs e) @@ -176,7 +132,7 @@ namespace Avalonia.Controls i.IsSubMenuOpen = false; } - contextMenu._isOpen = false; + contextMenu.IsOpen = false; contextMenu.SelectedIndex = -1; } } @@ -186,7 +142,7 @@ namespace Avalonia.Controls var control = (Control)sender; var contextMenu = control.ContextMenu; - if (control.ContextMenu._isOpen) + if (control.ContextMenu.IsOpen) { if (contextMenu.CancelClosing()) return; @@ -218,10 +174,5 @@ namespace Avalonia.Controls ContextMenuOpening?.Invoke(this, eventArgs); return eventArgs.Cancel; } - - bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) - { - throw new NotImplementedException(); - } } } diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs index 00fca385a0..290e16d8c8 100644 --- a/src/Avalonia.Controls/Menu.cs +++ b/src/Avalonia.Controls/Menu.cs @@ -1,56 +1,26 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; -using System.Collections.Generic; -using System.Linq; -using Avalonia.Controls.Generators; using Avalonia.Controls.Platform; -using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.LogicalTree; namespace Avalonia.Controls { /// /// A top-level menu control. /// - public class Menu : SelectingItemsControl, IFocusScope, IMainMenu, IMenu + public class Menu : MenuBase, IFocusScope, IMainMenu { - /// - /// Defines the property. - /// - public static readonly DirectProperty IsOpenProperty = - AvaloniaProperty.RegisterDirect( - nameof(IsOpen), - o => o.IsOpen); - - /// - /// Defines the event. - /// - public static readonly RoutedEvent MenuOpenedEvent = - RoutedEvent.Register(nameof(MenuOpened), RoutingStrategies.Bubble); - - /// - /// Defines the event. - /// - public static readonly RoutedEvent MenuClosedEvent = - RoutedEvent.Register(nameof(MenuClosed), RoutingStrategies.Bubble); - private static readonly ITemplate DefaultPanel = new FuncTemplate(() => new StackPanel { Orientation = Orientation.Horizontal }); - private readonly IMenuInteractionHandler _interaction; - private bool _isOpen; /// /// Initializes a new instance of the class. /// public Menu() { - _interaction = AvaloniaLocator.Current.GetService() ?? - new DefaultMenuInteractionHandler(); } /// @@ -58,82 +28,17 @@ namespace Avalonia.Controls /// /// The menu interaction handler. public Menu(IMenuInteractionHandler interactionHandler) + : base(interactionHandler) { - Contract.Requires(interactionHandler != null); - - _interaction = interactionHandler; } - /// - /// Initializes static members of the class. - /// static Menu() { ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel); - MenuItem.SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened); - } - - /// - /// Gets a value indicating whether the menu is open. - /// - public bool IsOpen - { - get { return _isOpen; } - private set { SetAndRaise(IsOpenProperty, ref _isOpen, value); } - } - - /// - 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 a is opened. - /// - public event EventHandler MenuOpened - { - add { AddHandler(MenuOpenedEvent, value); } - remove { RemoveHandler(MenuOpenedEvent, value); } - } - - /// - /// Occurs when a is closed. - /// - public event EventHandler MenuClosed - { - add { AddHandler(MenuClosedEvent, value); } - remove { RemoveHandler(MenuClosedEvent, value); } - } - - /// - /// Closes the menu. - /// - public void Close() + public override void Close() { if (IsOpen) { @@ -153,10 +58,8 @@ namespace Avalonia.Controls } } - /// - /// Opens the menu in response to the Alt/F10 key. - /// - public void Open() + /// + public override void Open() { if (!IsOpen) { @@ -170,15 +73,6 @@ namespace Avalonia.Controls } } - /// - bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap); - - /// - protected override IItemContainerGenerator CreateItemContainerGenerator() - { - return new ItemContainerGenerator(this, MenuItem.HeaderProperty, null); - } - /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { @@ -190,41 +84,6 @@ namespace Avalonia.Controls { inputRoot.AccessKeyHandler.MainMenu = this; } - - _interaction.Attach(this); - } - - /// - protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnDetachedFromVisualTree(e); - _interaction.Detach(this); - } - - /// - protected override void OnKeyDown(KeyEventArgs e) - { - // Don't handle here: let the interaction handler handle it. - } - - /// - /// Called when a submenu opens somewhere in the menu. - /// - /// The event args. - protected virtual void OnSubmenuOpened(RoutedEventArgs e) - { - if (e.Source is MenuItem menuItem && menuItem.Parent == this) - { - foreach (var child in this.GetLogicalChildren().OfType()) - { - if (child != menuItem && child.IsSubMenuOpen) - { - child.IsSubMenuOpen = false; - } - } - } - - IsOpen = true; } } } diff --git a/src/Avalonia.Controls/MenuBase.cs b/src/Avalonia.Controls/MenuBase.cs new file mode 100644 index 0000000000..836e3609c4 --- /dev/null +++ b/src/Avalonia.Controls/MenuBase.cs @@ -0,0 +1,193 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls.Generators; +using Avalonia.Controls.Platform; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.LogicalTree; + +namespace Avalonia.Controls +{ + /// + /// Base class for menu controls. + /// + public abstract class MenuBase : SelectingItemsControl, IMenu + { + /// + /// Defines the property. + /// + public static readonly DirectProperty IsOpenProperty = + AvaloniaProperty.RegisterDirect( + nameof(IsOpen), + o => o.IsOpen); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent MenuOpenedEvent = + RoutedEvent.Register(nameof(MenuOpened), RoutingStrategies.Bubble); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent MenuClosedEvent = + RoutedEvent.Register(nameof(MenuClosed), RoutingStrategies.Bubble); + + private bool _isOpen; + + /// + /// Initializes a new instance of the class. + /// + public MenuBase() + { + InteractionHandler = AvaloniaLocator.Current.GetService() ?? + new DefaultMenuInteractionHandler(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The menu interaction handler. + public MenuBase(IMenuInteractionHandler interactionHandler) + { + Contract.Requires(interactionHandler != null); + + InteractionHandler = interactionHandler; + } + + /// + /// Initializes static members of the class. + /// + static MenuBase() + { + MenuItem.SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened); + } + + /// + /// Gets a value indicating whether the menu is open. + /// + public bool IsOpen + { + get { return _isOpen; } + protected set { SetAndRaise(IsOpenProperty, ref _isOpen, value); } + } + + /// + IMenuInteractionHandler IMenu.InteractionHandler => InteractionHandler; + + /// + 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(); + } + } + + /// + /// Gets the interaction handler for the menu. + /// + protected IMenuInteractionHandler InteractionHandler { get; } + + /// + /// Occurs when a is opened. + /// + public event EventHandler MenuOpened + { + add { AddHandler(MenuOpenedEvent, value); } + remove { RemoveHandler(MenuOpenedEvent, value); } + } + + /// + /// Occurs when a is closed. + /// + public event EventHandler MenuClosed + { + add { AddHandler(MenuClosedEvent, value); } + remove { RemoveHandler(MenuClosedEvent, value); } + } + + /// + /// Closes the menu. + /// + public abstract void Close(); + + /// + /// Opens the menu. + /// + public abstract void Open(); + + /// + bool IMenuElement.MoveSelection(NavigationDirection direction, bool wrap) => MoveSelection(direction, wrap); + + /// + protected override IItemContainerGenerator CreateItemContainerGenerator() + { + return new ItemContainerGenerator(this, MenuItem.HeaderProperty, null); + } + + /// + protected override void OnKeyDown(KeyEventArgs e) + { + // Don't handle here: let the interaction handler handle it. + } + + /// + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + InteractionHandler.Attach(this); + } + + /// + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); + InteractionHandler.Detach(this); + } + + /// + /// Called when a submenu opens somewhere in the menu. + /// + /// The event args. + protected virtual void OnSubmenuOpened(RoutedEventArgs e) + { + if (e.Source is MenuItem menuItem && menuItem.Parent == this) + { + foreach (var child in this.GetLogicalChildren().OfType()) + { + if (child != menuItem && child.IsSubMenuOpen) + { + child.IsSubMenuOpen = false; + } + } + } + + IsOpen = true; + } + } +}