// ----------------------------------------------------------------------- // // Copyright 2015 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Controls { using System; using System.Linq; using System.Windows.Input; using Perspex.Controls.Primitives; using Perspex.Input; using Perspex.Interactivity; using Perspex.Rendering; using Perspex.Controls.Templates; using Perspex.Controls.Presenters; /// /// A menu item control. /// public class MenuItem : HeaderedItemsControl { /// /// Defines the property. /// public static readonly PerspexProperty CommandProperty = Button.CommandProperty.AddOwner(); /// /// Defines the property. /// public static readonly PerspexProperty CommandParameterProperty = Button.CommandParameterProperty.AddOwner(); /// /// Defines the property. /// public static readonly PerspexProperty IconProperty = PerspexProperty.Register(nameof(Icon)); /// /// Defines the property. /// public static readonly PerspexProperty IsSubMenuOpenProperty = PerspexProperty.Register(nameof(IsSubMenuOpen)); /// /// Defines the event. /// public static readonly RoutedEvent ClickEvent = RoutedEvent.Register(nameof(Click), RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent SubmenuOpenedEvent = RoutedEvent.Register(nameof(SubmenuOpened), RoutingStrategies.Bubble); /// /// The timer used to display submenus. /// private IDisposable submenuTimer; /// /// Initializes static members of the class. /// static MenuItem() { ClickEvent.AddClassHandler(x => x.OnClick); SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened); IsSubMenuOpenProperty.Changed.Subscribe(SubMenuOpenChanged); } /// /// Occurs when a without a submenu is clicked. /// public event EventHandler Click { add { this.AddHandler(ClickEvent, value); } remove { this.RemoveHandler(ClickEvent, value); } } /// /// Occurs when a 's submenu is opened. /// public event EventHandler SubmenuOpened { add { this.AddHandler(SubmenuOpenedEvent, value); } remove { this.RemoveHandler(SubmenuOpenedEvent, value); } } /// /// Gets or sets the command associated with the menu item. /// public ICommand Command { get { return this.GetValue(CommandProperty); } set { this.SetValue(CommandProperty, value); } } /// /// Gets or sets the parameter to pass to the property of a /// . /// public object CommandParameter { get { return this.GetValue(CommandParameterProperty); } set { this.SetValue(CommandParameterProperty, value); } } /// /// Gets or sets the icon that appears in a . /// public object Icon { get { return this.GetValue(IconProperty); } set { this.SetValue(IconProperty, value); } } /// /// Gets or sets a value that indicates whether the submenu of the is /// open. /// public bool IsSubMenuOpen { get { return this.GetValue(IsSubMenuOpenProperty); } set { this.SetValue(IsSubMenuOpenProperty, value); } } /// /// Gets or sets a value that indicates whether the has a submenu. /// public bool HasSubMenu { get { return !this.Classes.Contains(":empty"); } } /// /// Gets a value that indicates whether the is a top-level menu item. /// public bool IsTopLevel { get; private set; } /// /// Called when the is attached to the visual tree. /// /// The root of the visual tree. protected override void OnAttachedToVisualTree(IRenderRoot root) { base.OnAttachedToVisualTree(root); this.IsTopLevel = this.Parent is Menu; } /// /// Called when the is clicked. /// /// The click event args. protected virtual void OnClick(RoutedEventArgs e) { if (this.Command != null) { this.Command.Execute(this.CommandParameter); } } /// /// Called when the pointer enters the . /// /// The event args. protected override void OnPointerEnter(PointerEventArgs e) { base.OnPointerEnter(e); var menu = this.Parent as Menu; if (menu != null && menu.IsOpen) { this.IsSubMenuOpen = true; } } /// /// Called when the pointer leaves the . /// /// The event args. protected override void OnPointerLeave(PointerEventArgs e) { base.OnPointerLeave(e); if (this.submenuTimer != null) { this.submenuTimer.Dispose(); this.submenuTimer = null; } } /// /// Called when the pointer is pressed over the . /// /// The event args. protected override void OnPointerPressed(PointerPressEventArgs e) { base.OnPointerPressed(e); if (!this.HasSubMenu) { this.RaiseEvent(new RoutedEventArgs(ClickEvent)); } else if (this.IsTopLevel) { this.IsSubMenuOpen = !this.IsSubMenuOpen; } else { this.IsSubMenuOpen = true; } e.Handled = true; } /// /// Called when a submenu is opened on this MenuItem or a child MenuItem. /// /// The event args. protected virtual void OnSubmenuOpened(RoutedEventArgs e) { var menuItem = e.Source as MenuItem; if (menuItem != null && menuItem.Parent == this) { foreach (var child in this.Items.OfType()) { if (child != menuItem && child.IsSubMenuOpen) { child.IsSubMenuOpen = false; } } } } /// /// Called when the MenuItem's template has been applied. /// protected override void OnTemplateApplied() { base.OnTemplateApplied(); var popup = this.FindTemplateChild("popup"); if (popup != null) { popup.Opened += this.PopupFirstOpened; } } /// /// Closes all submenus of the menu item. /// private void CloseSubmenus() { foreach (var child in this.Items.OfType()) { child.IsSubMenuOpen = false; } } /// /// Called when the property changes. /// /// The property change event. private static void SubMenuOpenChanged(PerspexPropertyChangedEventArgs e) { var sender = e.Sender as MenuItem; var value = (bool)e.NewValue; if (sender != null) { if (value) { sender.RaiseEvent(new RoutedEventArgs(SubmenuOpenedEvent)); } else { sender.CloseSubmenus(); } } } /// /// Called the first time the MenuItem's popup is opened. /// /// The event sender. /// The event args. private void PopupFirstOpened(object sender, EventArgs e) { var popup = (Popup)sender; // Our ItemsPresenter is in a Popup which means that it's only created when the // Popup is opened, therefore it wasn't found by ItemsControl.OnTemplateApplied. // Now the Popup has been opened for the first time it should exist, so make sure // the PopupRoot's template is applied and look for the ItemsPresenter. popup.PopupRoot.ApplyTemplate(); var presenter = popup.PopupRoot.FindControl("itemsPresenter"); if (presenter != null) { // The presenter was found. First make its Panel's ChildLogicalParent point to // this so that the child MenuItems will be logically parented by the parent // MenuItem and then assign it to our Presenter property. presenter.ApplyTemplate(); ((IItemsPanel)presenter.Panel).ChildLogicalParent = this; this.Presenter = presenter; } // Don't call this event handler again. popup.Opened -= this.PopupFirstOpened; } } }