File: System\Windows\Controls\Primitives\MenuBase.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
 
using MS.Internal;
using MS.Internal.KnownBoxes;
using MS.Utility;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Diagnostics;
using System.Windows.Threading;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Input;
using System.Security;
using System;
 
namespace System.Windows.Controls.Primitives
{
    /// <summary>
    ///     Control that defines a menu of choices for users to invoke.
    /// </summary>
    [Localizability(LocalizationCategory.Menu)]
    [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(MenuItem))]
    public abstract class MenuBase : ItemsControl
    {
        //-------------------------------------------------------------------
        //
        //  Constructors
        //
        //-------------------------------------------------------------------
 
        #region Constructors
 
        /// <summary>
        ///     Default DependencyObject constructor
        /// </summary>
        /// <remarks>
        ///     Automatic determination of current Dispatcher. Use alternative constructor
        ///     that accepts a Dispatcher for best performance.
        /// </remarks>
        protected MenuBase()
            : base()
        {
        }
 
        static MenuBase()
        {
            EventManager.RegisterClassHandler(typeof(MenuBase), MenuItem.PreviewClickEvent, new RoutedEventHandler(OnMenuItemPreviewClick));
            EventManager.RegisterClassHandler(typeof(MenuBase), Mouse.MouseDownEvent, new MouseButtonEventHandler(OnMouseButtonDown));
            EventManager.RegisterClassHandler(typeof(MenuBase), Mouse.MouseUpEvent, new MouseButtonEventHandler(OnMouseButtonUp));
            EventManager.RegisterClassHandler(typeof(MenuBase), Mouse.LostMouseCaptureEvent, new MouseEventHandler(OnLostMouseCapture));
            EventManager.RegisterClassHandler(typeof(MenuBase), MenuBase.IsSelectedChangedEvent, new RoutedPropertyChangedEventHandler<bool>(OnIsSelectedChanged));
 
            EventManager.RegisterClassHandler(typeof(MenuBase), Mouse.MouseDownEvent, new MouseButtonEventHandler(OnPromotedMouseButton));
            EventManager.RegisterClassHandler(typeof(MenuBase), Mouse.MouseUpEvent, new MouseButtonEventHandler(OnPromotedMouseButton));
 
            EventManager.RegisterClassHandler(typeof(MenuBase), Mouse.PreviewMouseDownOutsideCapturedElementEvent, new MouseButtonEventHandler(OnClickThroughThunk));
            EventManager.RegisterClassHandler(typeof(MenuBase), Mouse.PreviewMouseUpOutsideCapturedElementEvent, new MouseButtonEventHandler(OnClickThroughThunk));
 
            EventManager.RegisterClassHandler(typeof(MenuBase), Keyboard.PreviewKeyboardInputProviderAcquireFocusEvent, new KeyboardInputProviderAcquireFocusEventHandler(OnPreviewKeyboardInputProviderAcquireFocus), true);
            EventManager.RegisterClassHandler(typeof(MenuBase), Keyboard.KeyboardInputProviderAcquireFocusEvent, new KeyboardInputProviderAcquireFocusEventHandler(OnKeyboardInputProviderAcquireFocus), true);
 
            FocusManager.IsFocusScopeProperty.OverrideMetadata(typeof(MenuBase), new FrameworkPropertyMetadata(BooleanBoxes.TrueBox));
 
            // While the menu is opened, Input Method should be suspended.
            // the docusmen focus of Cicero should not be changed but key typing should not be
            // dispatched to IME/TIP.
            InputMethod.IsInputMethodSuspendedProperty.OverrideMetadata(typeof(MenuBase), new FrameworkPropertyMetadata(BooleanBoxes.TrueBox, FrameworkPropertyMetadataOptions.Inherits));
        }
 
        #endregion
 
        #region Public Properties
 
        /// <summary>
        ///     DependencyProperty for ItemContainerTemplateSelector property.
        /// </summary>
        public static readonly DependencyProperty ItemContainerTemplateSelectorProperty =
            DependencyProperty.Register(
                "ItemContainerTemplateSelector",
                typeof(ItemContainerTemplateSelector),
                typeof(MenuBase),
                new FrameworkPropertyMetadata(new DefaultItemContainerTemplateSelector()));
 
        /// <summary>
        ///     ItemContainerTemplateSelector property which provides the DataTemplate to be used to create an instance of the ItemContainer.
        /// </summary>
        public ItemContainerTemplateSelector ItemContainerTemplateSelector
        {
            get { return (ItemContainerTemplateSelector)GetValue(ItemContainerTemplateSelectorProperty); }
            set { SetValue(ItemContainerTemplateSelectorProperty, value); }
        }
 
        /// <summary>
        ///     DependencyProperty for UsesItemContainerTemplateSelector property.
        /// </summary>
        public static readonly DependencyProperty UsesItemContainerTemplateProperty =
            DependencyProperty.Register(
                "UsesItemContainerTemplate",
                typeof(bool),
                typeof(MenuBase));
 
        /// <summary>
        ///     UsesItemContainerTemplate property which says whether the ItemContainerTemplateSelector property is to be used.
        /// </summary>
        public bool UsesItemContainerTemplate
        {
            get { return (bool)GetValue(UsesItemContainerTemplateProperty); }
            set { SetValue(UsesItemContainerTemplateProperty, value); }
        }
 
        #endregion
 
        //-------------------------------------------------------------------
        //
        //  Protected Methods
        //
        //-------------------------------------------------------------------
 
        #region Protected Methods
 
        /// <summary>
        ///     Called when any mouse button is pressed on this subtree
        /// </summary>
        /// <param name="sender">Sender of the event.</param>
        /// <param name="e">Event arguments.</param>
        private static void OnMouseButtonDown(object sender, MouseButtonEventArgs e)
        {
            ((MenuBase)sender).HandleMouseButton(e);
        }
 
        /// <summary>
        ///     Called when any mouse right button is released on this subtree
        /// </summary>
        /// <param name="sender">Sender of the event.</param>
        /// <param name="e">Event arguments.</param>
        private static void OnMouseButtonUp(object sender, MouseButtonEventArgs e)
        {
            ((MenuBase)sender).HandleMouseButton(e);
        }
 
        /// <summary>
        ///     Called when any mouse button is pressed or released on this subtree
        /// </summary>
        /// <param name="e">Event arguments.</param>
        protected virtual void HandleMouseButton(MouseButtonEventArgs e)
        {
        }
 
        private static void OnClickThroughThunk(object sender, MouseButtonEventArgs e)
        {
            ((MenuBase)sender).OnClickThrough(e);
        }
 
        private void OnClickThrough(MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left || e.ChangedButton == MouseButton.Right)
            {
                if (HasCapture)
                {
                    bool close = true;
 
                    if (e.ButtonState == MouseButtonState.Released)
                    {
                        // Check to see if we should ignore the this mouse release
                        if (e.ChangedButton == MouseButton.Left && IgnoreNextLeftRelease)
                        {
                            IgnoreNextLeftRelease = false;
                            close = false; // don't close
                        }
                        else if (e.ChangedButton == MouseButton.Right && IgnoreNextRightRelease)
                        {
                            IgnoreNextRightRelease = false;
                            close = false; // don't close
                        }
                    }
 
                    if (close)
                    {
                        IsMenuMode = false;
                    }
                }
            }
        }
 
        // This is called on MouseLeftButtonDown, MouseLeftButtonUp, MouseRightButtonDown, MouseRightButtonUp
        private static void OnPromotedMouseButton(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
            {
                // If it wasn't outside the subtree, we should handle the mouse event.
                // This makes things consistent so that just in case one of our children
                // didn't handle the event, it doesn't escape the menu hierarchy.
                e.Handled = true;
            }
        }
 
        /// <summary>
        ///     Called when IsMouseOver changes on this element.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeave(MouseEventArgs e)
        {
            base.OnMouseLeave(e);
 
            // if we don't have capture and the mouse left (but the item isn't selected), then we shouldn't have anything selected.
            if (!HasCapture && !IsMouseOver && CurrentSelection != null && !CurrentSelection.IsKeyboardFocused && !CurrentSelection.IsSubmenuOpen)
            {
                CurrentSelection = null;
            }
        }
 
        // This method ensures that whenever focus is given to an element
        // within a menu, that the menu enters menu mode.  This can't be
        // done with a simple IsFocusWithin changed handler because we
        // need to actually enter menu mode before focus changes.
        private static void OnPreviewKeyboardInputProviderAcquireFocus(object sender, KeyboardInputProviderAcquireFocusEventArgs e)
        {
            MenuBase menu = (MenuBase) sender;
 
            // If we haven't already pushed menu mode, we need to do it before
            // focus enters the menu for the first time
            if (!menu.IsKeyboardFocusWithin && !menu.HasPushedMenuMode)
            {
                // Call PushMenuMode just before focus enters the menu...
                menu.PushMenuMode(/*isAcquireFocusMenuMode*/ true);
            }
        }
 
        // This method ensures that whenever focus is not acquired
        // but MenuMode has been pushed with the expection, a
        // corresponding PopMenu is performed.
        private static void OnKeyboardInputProviderAcquireFocus(object sender, KeyboardInputProviderAcquireFocusEventArgs e)
        {
            MenuBase menu = (MenuBase) sender;
            if (!menu.IsKeyboardFocusWithin && !e.FocusAcquired && menu.IsAcquireFocusMenuMode)
            {
                Debug.Assert(menu.HasPushedMenuMode);
                // The input provider did not acquire focus.  So we will not
                // succeed in setting focus to the desired element within the
                // menu.
                menu.PopMenuMode();
            }
        }
 
 
        /// <summary>
        /// Called when the focus is no longer on or within this element.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnIsKeyboardFocusWithinChanged(e);
 
            if (IsKeyboardFocusWithin)
            {
                // When focus enters the menu, we should enter menu mode.
                if (!IsMenuMode)
                {
                    IsMenuMode = true;
                    OpenOnMouseEnter = false;
                }
 
                if (KeyboardNavigation.IsKeyboardMostRecentInputDevice())
                {
                    // Turn on keyboard cues b/c we took focus with the keyboard
                    KeyboardNavigation.EnableKeyboardCues(this, true);
                }
            }
            else
            {
                // Turn off keyboard cues
                KeyboardNavigation.EnableKeyboardCues(this, false);
 
                if (IsMenuMode)
                {
                    // When showing a ContextMenu of a MenuItem, the ContextMenu will take focus
                    // out of this menu's subtree.  The ContextMenu takes capture before taking
                    // focus, so if we are in MenuMode but don't have capture then we are waiting
                    // for the context menu to close.  Thus, we should only exit menu mode when
                    // we have capture.
                    if (HasCapture)
                    {
                        IsMenuMode = false;
                    }
                }
                else
                {
                    // Okay, we weren't in menu mode but we could have had a selection (mouse hovering), so clear that
                    if (CurrentSelection != null)
                    {
                        CurrentSelection = null;
                    }
                }
            }
 
            InvokeMenuOpenedClosedAutomationEvent(IsKeyboardFocusWithin);
        }
 
        private void InvokeMenuOpenedClosedAutomationEvent(bool open)
        {
            AutomationEvents automationEvent = open ? AutomationEvents.MenuOpened : AutomationEvents.MenuClosed;
 
            if (AutomationPeer.ListenerExists(automationEvent))
            {
                AutomationPeer peer = UIElementAutomationPeer.CreatePeerForElement(this);
                if (peer != null)
                {
                    if (open)
                    {
                        // We raise the event async to allow PopupRoot to hookup
                        Dispatcher.BeginInvoke(DispatcherPriority.Input, new DispatcherOperationCallback(delegate(object param)
                        {
                            peer.RaiseAutomationEvent(automationEvent);
                            return null;
                        }), null);
                    }
                    else
                    {
                        peer.RaiseAutomationEvent(automationEvent);
                    }
                }
            }
        }
 
        internal static readonly RoutedEvent IsSelectedChangedEvent = EventManager.RegisterRoutedEvent(
            "IsSelectedChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<bool>), typeof(MenuBase));
 
        private static void OnIsSelectedChanged(object sender, RoutedPropertyChangedEventArgs<bool> e)
        {
            // We assume that within a menu the only top-level menu items are direct children of
            // the one and only top-level menu.
            MenuItem newSelectedMenuItem = e.OriginalSource as MenuItem;
 
            if (newSelectedMenuItem != null)
            {
                MenuBase menu = (MenuBase)sender;
 
                // If the selected item is a child of ours, make it the current selection.
                // If the selection changes from a top-level menu item with its submenu
                // open to another, the new selection's submenu should be open.
                if (e.NewValue)
                {
                    if ((menu.CurrentSelection != newSelectedMenuItem) && (newSelectedMenuItem.LogicalParent == menu))
                    {
                        bool wasSubmenuOpen = false;
 
                        if (menu.CurrentSelection != null)
                        {
                            wasSubmenuOpen = menu.CurrentSelection.IsSubmenuOpen;
                            menu.CurrentSelection.SetCurrentValueInternal(MenuItem.IsSubmenuOpenProperty, BooleanBoxes.FalseBox);
                        }
 
                        menu.CurrentSelection = newSelectedMenuItem;
                        if (menu.CurrentSelection != null && wasSubmenuOpen)
                        {
                            // Only open the submenu if it's a header (i.e. has items)
                            MenuItemRole role = menu.CurrentSelection.Role;
 
                            if (role == MenuItemRole.SubmenuHeader || role == MenuItemRole.TopLevelHeader)
                            {
                                if (menu.CurrentSelection.IsSubmenuOpen != wasSubmenuOpen)
                                {
                                    menu.CurrentSelection.SetCurrentValueInternal(MenuItem.IsSubmenuOpenProperty, BooleanBoxes.Box(wasSubmenuOpen));
                                }
                            }
                        }
                    }
                }
                else
                {
                    // As in MenuItem.OnIsSelectedChanged, if the item is deselected
                    // and it's our current selection, set CurrentSelection to null.
                    if (menu.CurrentSelection == newSelectedMenuItem)
                    {
                        menu.CurrentSelection = null;
                    }
                }
 
                e.Handled = true;
            }
        }
 
        private bool IsDescendant(DependencyObject node)
        {
            return IsDescendant(this, node);
        }
 
        internal static bool IsDescendant(DependencyObject reference, DependencyObject node)
        {
            bool success = false;
 
            DependencyObject curr = node;
 
            while (curr != null)
            {
                if (curr == reference)
                {
                    success = true;
                    break;
                }
 
                // Find popup if curr is a PopupRoot
                PopupRoot popupRoot = curr as PopupRoot;
                if (popupRoot != null)
                {
                    //Now Popup does not have a visual link to its parent (for context menu)
                    //it is stored in its parent's arraylist (DP)
                    //so we get its parent by looking at PlacementTarget
                    Popup popup = popupRoot.Parent as Popup;
 
                    curr = popup;
 
                    if (popup != null)
                    {
                        // Try the poup Parent
                        curr = popup.Parent;
 
                        // Otherwise fall back to placement target
                        if (curr == null)
                        {
                            curr = popup.PlacementTarget;
                        }
                    }
                }
                else // Otherwise walk tree
                {
                    curr = PopupControlService.FindParent(curr);
                }
            }
 
            return success;
        }
 
        /// <summary>
        ///     This is the method that responds to the KeyDown event.
        /// </summary>
        /// <param name="e">Event arguments</param>
        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
            Key key = e.Key;
            switch (key)
            {
                case Key.Escape:
                    {
                        if (CurrentSelection != null && CurrentSelection.IsSubmenuOpen)
                        {
                            CurrentSelection.SetCurrentValueInternal(MenuItem.IsSubmenuOpenProperty, BooleanBoxes.FalseBox);
                            OpenOnMouseEnter = false;
                            e.Handled = true;
                        }
                        else
                        {
                            KeyboardLeaveMenuMode();
 
                            e.Handled = true;
                        }
                    }
                    break;
 
                case Key.System:
                    if ((e.SystemKey == Key.LeftAlt) ||
                        (e.SystemKey == Key.RightAlt) ||
                        (e.SystemKey == Key.F10))
                    {
                        KeyboardLeaveMenuMode();
 
                        e.Handled = true;
                    }
                    break;
            }
        }
 
        private object _currentItem;
 
        /// <summary>
        /// Return true if the item is (or is eligible to be) its own ItemUI
        /// </summary>
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            bool ret = (item is MenuItem) || (item is Separator);
            if (!ret)
            {
                _currentItem = item;
            }
 
            return ret;
        }
 
        protected override DependencyObject GetContainerForItemOverride()
        {
            object currentItem = _currentItem;
            _currentItem = null;
 
            if (UsesItemContainerTemplate)
            {
                DataTemplate itemContainerTemplate = ItemContainerTemplateSelector.SelectTemplate(currentItem, this);
                if (itemContainerTemplate != null)
                {
                    object itemContainer = itemContainerTemplate.LoadContent();
                    if (itemContainer is MenuItem || itemContainer is Separator)
                    {
                        return itemContainer as DependencyObject;
                    }
                    else
                    {
                        throw new InvalidOperationException(SR.Format(SR.InvalidItemContainer, this.GetType().Name, typeof(MenuItem).Name, typeof(Separator).Name, itemContainer));
                    }
                }
            }
 
            return new MenuItem();
        }
 
        #endregion
 
        //-------------------------------------------------------------------
        //
        //  Private Methods
        //
        //-------------------------------------------------------------------
 
        #region Private Methods
 
        /// <summary>
        ///     Called when this element loses capture.
        /// </summary>
        private static void OnLostMouseCapture(object sender, MouseEventArgs e)
        {
            MenuBase menu = sender as MenuBase;
 
            // need a better solution for subcapture!
 
            // Use the same technique employed in ComoboBox.OnLostMouseCapture to allow another control in the
            // application to temporarily take capture and then take it back afterwards.
 
            if (Mouse.Captured != menu)
            {
                if (e.OriginalSource == menu)
                {
                    // If capture is null or it's not below the menu, close.
                    // More workaround for task 22022 -- check if it's a descendant (following Logical links too)
                    if (Mouse.Captured == null || !MenuBase.IsDescendant(menu, Mouse.Captured as DependencyObject))
                    {
                        menu.IsMenuMode = false;
                    }
                }
                else
                {
                    if (MenuBase.IsDescendant(menu, e.OriginalSource as DependencyObject))
                    {
                        // Take capture if one of our children gave up capture
                        if (menu.IsMenuMode && Mouse.Captured == null && MS.Win32.SafeNativeMethods.GetCapture() == IntPtr.Zero)
                        {
                            Mouse.Capture(menu, CaptureMode.SubTree);
                            e.Handled = true;
                        }
                    }
                    else
                    {
                        menu.IsMenuMode = false;
                    }
                }
            }
        }
 
        /// <summary>
        ///     Called when any menu item within this subtree got clicked.
        ///     Closes all submenus in this tree.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void OnMenuItemPreviewClick(object sender, RoutedEventArgs e)
        {
            MenuBase menu = ((MenuBase)sender);
 
            MenuItem menuItemSource = e.OriginalSource as MenuItem;
 
            if ((menuItemSource != null) && !menuItemSource.StaysOpenOnClick)
            {
                MenuItemRole role = menuItemSource.Role;
 
                if (role == MenuItemRole.TopLevelItem || role == MenuItemRole.SubmenuItem)
                {
                    menu.IsMenuMode = false;
                    e.Handled = true;
                }
            }
        }
 
        /// <summary>
        ///     Called when IsMenuMode changes.
        /// </summary>
        internal event EventHandler InternalMenuModeChanged
        {
            add { EventHandlersStoreAdd(InternalMenuModeChangedKey, value); }
            remove { EventHandlersStoreRemove(InternalMenuModeChangedKey, value); }
        }
 
        private static readonly EventPrivateKey InternalMenuModeChangedKey = new EventPrivateKey();
 
        private void RestorePreviousFocus()
        {
            // Only restore focus if focus is still within the menu.  If
            // focus has already been moved outside of the menu, then
            // we don't want to disturb it.
            if (IsKeyboardFocusWithin)
            {
                // Only restore WPF focus if the HWND with focus is an
                // HwndSource.  This enables child HWNDs, other top-level
                // non-WPF HWNDs, or even child HWNDs of other WPF top-level
                // windows to retain focus when menus are dismissed.
                IntPtr hwndWithFocus = MS.Win32.UnsafeNativeMethods.GetFocus();
                HwndSource hwndSourceWithFocus = hwndWithFocus != IntPtr.Zero ? HwndSource.CriticalFromHwnd(hwndWithFocus) : null;
                if(hwndSourceWithFocus != null)
                {
                    // We restore focus by setting focus to the parent's focus
                    // scope.  This may not seem correct, because it presumes
                    // the focus came from the logical-focus element of the
                    // parent scope.  In fact, it could have come from any
                    // number of places.  However, we have not figured out a
                    // better solution for restoring focus across scenarios
                    // such as:
                    //
                    // 1) A context menu of a menu item.
                    // 2) Two menus side-by-side
                    // 3) A menu and a toolbar side-by-side
                    //
                    // Simply remembering the last element with focus and
                    // restoring focus to it does not work.  For example,
                    // two menus side-by-side will end up remembering each
                    // other, and you can get stuck in an infinite loop.
                    //
                    // Restoring focus through the parent's focus scope will
                    // not directly work if you open one window's menu from
                    // another window. Visual Studio, as an example, will
                    // intercept the focus change events and forward
                    // appropriately for the scenario of restoring focus to
                    // an element in a different top-level window.
 
					// DependencyObject parent = Parent;
                    // if (parent == null)
                    // {
                        // If there is no logical parent, use the visual parent.
                    //     parent = VisualTreeHelper.GetParent(this);
                    // }
 
                    // if (parent != null)
                    // {
                    //     IInputElement parentScope = FocusManager.GetFocusScope(parent) as IInputElement;
                    //     if (parentScope != null)
                    //     {
                    //         Keyboard.Focus(parentScope);
                    //     }
                    // }
 
					// Unfortunately setting focus to the parent focusscope tripped up VS in the scenario where 
					// Menus are contained within ToolBars. In this case when the Menu is dismissed they want 
					// focus to be restored to the element in the main window that previously had focus. However 
					// since ToolBar is  the parent focusscope for the Menu we end up restoring focus to its 
					// focusedelment. It is also noted that this implementation is a behavioral change from .Net 3.5. 
					// Hence we are putting back the old behavior which is to set Keyboard.Focus to null which will 
					// delegate focus through the main window to its focusedelement. 
 
					Keyboard.Focus(null);
                }
                else
                {
                    // In the case where Win32 focus is not on a WPF
                    // HwndSource, we just clear WPF focus completely.
                    //
                    // Note that calling Focus(null) will set focus to the root
                    // element of the active source, which is not what we want.
                    Keyboard.ClearFocus();
                }
            }
        }
 
        // From all of our children, set the InMenuMode property
        // If turning this property off, recurse to all submenus
        internal static void SetSuspendingPopupAnimation(ItemsControl menu, MenuItem ignore, bool suspend)
        {
            // menu can be either a MenuBase or MenuItem
            if (menu != null)
            {
                int itemsCount = menu.Items.Count;
 
                for (int i = 0; i < itemsCount; i++)
                {
                    MenuItem mi = menu.ItemContainerGenerator.ContainerFromIndex(i) as MenuItem;
 
                    if (mi != null && mi != ignore && mi.IsSuspendingPopupAnimation != suspend)
                    {
                        mi.IsSuspendingPopupAnimation = suspend;
 
                        // If leaving menu mode, clear property on all
                        // submenus of this menu
                        if (!suspend)
                        {
                            SetSuspendingPopupAnimation(mi, null, suspend);
                        }
                    }
                }
            }
        }
 
        internal void KeyboardLeaveMenuMode()
        {
            // If we're in MenuMode, exit.  This will relinquish capture,
            // clear CurrentSelection, and RestorePreviousFocus
            if (IsMenuMode)
            {
                IsMenuMode = false;
            }
            else
            {
                // (IsFocusWithin flickers when moving across Popup boundaries)
                // Consider guaranteeing that IsKeyboardFocusWithin -> Mouse.Captured == this.
                // Today we can just guarantee that if a submenu is open, then we have capture.
                //
                // We can't take capture as long as we can't guarantee
                // that focus is always within the menu.
                // We should still return focus to the previously focused element.
                CurrentSelection = null;
                RestorePreviousFocus();
            }
        }
 
        #endregion
 
        //-------------------------------------------------------------------
        //
        //  Private Fields
        //
        //-------------------------------------------------------------------
 
        #region Private Fields
 
        /// <summary>
        ///     Currently selected item in this menu or submenu.
        /// </summary>
        /// <value></value>
        internal MenuItem CurrentSelection
        {
            get
            {
                return _currentSelection;
            }
            set
            {
                // Even if we don't have capture we should move focus when one item is already focused.
                bool wasFocused = false;
 
                if (_currentSelection != null)
                {
                    wasFocused = _currentSelection.IsKeyboardFocused;
                    _currentSelection.SetCurrentValueInternal(MenuItem.IsSelectedProperty, BooleanBoxes.FalseBox);
                }
 
                _currentSelection = value;
                if (_currentSelection != null)
                {
                    _currentSelection.SetCurrentValueInternal(MenuItem.IsSelectedProperty, BooleanBoxes.TrueBox);
                    if (wasFocused)
                    {
                        _currentSelection.Focus();
                    }
                }
            }
        }
 
        internal bool HasCapture
        {
            get
            {
                return Mouse.Captured == this;
            }
        }
 
        internal bool IgnoreNextLeftRelease
        {
            get { return _bitFlags[(int)MenuBaseFlags.IgnoreNextLeftRelease]; }
            set { _bitFlags[(int)MenuBaseFlags.IgnoreNextLeftRelease] = value; }
        }
 
        internal bool IgnoreNextRightRelease
        {
            get { return _bitFlags[(int)MenuBaseFlags.IgnoreNextRightRelease]; }
            set { _bitFlags[(int)MenuBaseFlags.IgnoreNextRightRelease] = value; }
        }
 
        internal bool IsMenuMode
        {
            get
            {
                return _bitFlags[(int)MenuBaseFlags.IsMenuMode];
            }
 
            set
            {
                Debug.Assert(CheckAccess(), "IsMenuMode requires context access");
                bool isMenuMode = _bitFlags[(int)MenuBaseFlags.IsMenuMode];
                if (isMenuMode != value)
                {
                    isMenuMode = _bitFlags[(int)MenuBaseFlags.IsMenuMode] = value;
 
                    if (isMenuMode)
                    {
                        // Take capture so that all mouse messages stay below the menu.
                        if (!IsDescendant(this, Mouse.Captured as Visual) && !Mouse.Capture(this, CaptureMode.SubTree))
                        {
                            // If we're unable to take capture, leave menu mode immediately.
                            isMenuMode = _bitFlags[(int)MenuBaseFlags.IsMenuMode] = false;
                        }
                        else
                        {
                            // If we haven't pushed the menu mode yet (which
                            // should have already happened if keyboard focus
                            // is set within the menu), push it now.
                            if (!HasPushedMenuMode)
                            {
                                PushMenuMode(/*isAcquireFocusMenuMode*/ false);
                            }
                            
                            RaiseClrEvent(InternalMenuModeChangedKey, EventArgs.Empty);
                        }
                    }
 
                    if (!isMenuMode)
                    {
                        bool wasSubmenuOpen = false;
 
                        if (CurrentSelection != null)
                        {
                            wasSubmenuOpen = CurrentSelection.IsSubmenuOpen;
                            CurrentSelection.IsSubmenuOpen = false;
                            CurrentSelection = null;
                        }
 
                        // Note that this code path is also used to cleanup
                        // the case where setting IsMenuMode=true fails due
                        // to failure to gain capture. We pop out of the menu
                        // mode irrespective of where it was pushed.
                        if (HasPushedMenuMode)
                        {
                            // Call PopMenuMode before we do much else, so that
                            // focus changes will properly activate windows.
                            PopMenuMode();
                        }
 
                        if (!value)
                        {
                            // Fire the event before capture is released and after submenus have been closed.
                            RaiseClrEvent(InternalMenuModeChangedKey, EventArgs.Empty);
                        }
 
                        // Clear suspending animation flags on all descendant menuitems
                        SetSuspendingPopupAnimation(this, null, false);
 
                        // In the future, make sure that after we release capture we don't do anything except
                        //       return focus to the previously focused element.
                        // Release capture.  We might not have capture b/c the popup was still open when we lost activation.
                        // This means a debugger stopped us or someone fooled around with popups opening.  May be an issue later.
                        //Debug.Assert(Mouse.Captured == this, "Menu did not have capture. Why?");
                        if (HasCapture)
                        {
                            Mouse.Capture(null);
                        }
 
                        RestorePreviousFocus();
                    }
 
                    // Assume menu items should open when the mouse hovers over them
                    OpenOnMouseEnter = isMenuMode;
                }
            }
        }
 
        // This bool is used by top level menu items to
        // determine if they should open on mouse enter
        // Menu items shouldn't open if the use hit Alt
        // to get in menu mode and then hovered over the item
        internal bool OpenOnMouseEnter
        {
            get { return _bitFlags[(int)MenuBaseFlags.OpenOnMouseEnter]; }
            set { _bitFlags[(int)MenuBaseFlags.OpenOnMouseEnter] = value; }
        }
 
        private void PushMenuMode(bool isAcquireFocusMenuMode)
        {
            Debug.Assert(_pushedMenuMode == null);
            _pushedMenuMode = PresentationSource.CriticalFromVisual(this);
            Debug.Assert(_pushedMenuMode != null);
            IsAcquireFocusMenuMode = isAcquireFocusMenuMode;
            InputManager.UnsecureCurrent.PushMenuMode(_pushedMenuMode);
        }
 
        // **** Note:  This method is called via private reflection from RibbonMenuButton.
        //             Do not rename, remove, or change the method signature without fixing RibbonMenuButton.
        private void PopMenuMode()
        {
            Debug.Assert(_pushedMenuMode != null);
 
            PresentationSource pushedMenuMode = _pushedMenuMode;
            _pushedMenuMode = null;
            IsAcquireFocusMenuMode = false;
            InputManager.UnsecureCurrent.PopMenuMode(pushedMenuMode);
        }
 
        // **** Note:  This property is read via private reflection from RibbonMenuButton.
        //             Do not rename/remove this property without fixing RibbonMenuButton.
        private bool HasPushedMenuMode
        {
            get
            {
                return _pushedMenuMode != null;
            }
        }
 
        /// <summary>
        ///     This boolean determines if the PushMenuMode was
        ///     performed due to acquire focus or due to programmatic
        ///     set of IsMenuMode.
        /// </summary>
        private bool IsAcquireFocusMenuMode
        {
            get { return _bitFlags[(int)MenuBaseFlags.IsAcquireFocusMenuMode]; }
            set { _bitFlags[(int)MenuBaseFlags.IsAcquireFocusMenuMode] = value; }
        }
 
        private PresentationSource _pushedMenuMode;
 
        private MenuItem _currentSelection;
        private BitVector32 _bitFlags = new BitVector32(0);
 
        private enum MenuBaseFlags
        {
            IgnoreNextLeftRelease  = 0x01,
            IgnoreNextRightRelease = 0x02,
            IsMenuMode             = 0x04,
            OpenOnMouseEnter       = 0x08,
            IsAcquireFocusMenuMode = 0x10,
        }
 
        #endregion
 
        // Notes:
        // We want to enforce:
        // 1) IsKeyboardFocused -> IsHighlighted
        // 2) IsKeyboardFocusWithin -> Mouse.Captured is an ancestor of Keyboard.FocusedElement
        // 3) IsSubmenuOpen -> IsHighlighted and IsKeyboardFocusWithin
        // these conditions are violated only in the case of mousing from one submenu to another and there is a delay.
    }
}