Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Framework / System / Windows / Controls / Primitives / MenuBase.cs / 1 / MenuBase.cs
//---------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- 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.Media; using System.Windows.Input; using System.Security; using System.Security.Permissions; using System; namespace System.Windows.Controls.Primitives { ////// Control that defines a menu of choices for users to invoke. /// [Localizability(LocalizationCategory.Menu)] [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(MenuItem))] public abstract class MenuBase : ItemsControl { //------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------- #region Constructors ////// Default DependencyObject constructor /// ////// Automatic determination of current Dispatcher. Use alternative constructor /// that accepts a Dispatcher for best performance. /// 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(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)); 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 //-------------------------------------------------------------------- // // Protected Methods // //------------------------------------------------------------------- #region Protected Methods /// /// Called when any mouse button is pressed on this subtree /// /// Sender of the event. /// Event arguments. private static void OnMouseButtonDown(object sender, MouseButtonEventArgs e) { ((MenuBase)sender).HandleMouseButton(e); } ////// Called when any mouse right button is released on this subtree /// /// Sender of the event. /// Event arguments. private static void OnMouseButtonUp(object sender, MouseButtonEventArgs e) { ((MenuBase)sender).HandleMouseButton(e); } ////// Called when any mouse button is pressed or released on this subtree /// /// Event arguments. 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; } } ////// Called when IsMouseOver changes on this element. /// /// 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; } } ////// Called when the focus is no longer on or within this element. /// /// 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), typeof(MenuBase)); private static void OnIsSelectedChanged(object sender, RoutedPropertyChangedEventArgs 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.IsSubmenuOpen = false; } 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.IsSubmenuOpen = 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; } /// /// This is the method that responds to the KeyDown event. /// /// Event arguments protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); Key key = e.Key; switch (key) { case Key.Escape: { if (CurrentSelection != null && CurrentSelection.IsSubmenuOpen) { CurrentSelection.IsSubmenuOpen = false; 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; } } ////// Return true if the item is (or is eligible to be) its own ItemUI /// protected override bool IsItemItsOwnContainerOverride(object item) { return (item is MenuItem || item is Separator); } ///Create or identify the element used to display the given item. protected override DependencyObject GetContainerForItemOverride() { return new MenuItem(); } #endregion //-------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------- #region Private Methods ////// Called when this element loses capture. /// private static void OnLostMouseCapture(object sender, MouseEventArgs e) { MenuBase menu = sender as MenuBase; // // 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; } } } } ////// Called when any menu item within this subtree got clicked. /// Closes all submenus in this tree. /// /// /// 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; } } } ////// Called when IsMenuMode changes. /// internal event EventHandler InternalMenuModeChanged { add { EventHandlersStoreAdd(InternalMenuModeChangedKey, value); } remove { EventHandlersStoreRemove(InternalMenuModeChangedKey, value); } } private static readonly EventPrivateKey InternalMenuModeChangedKey = new EventPrivateKey(); private void RestorePreviousFocus() { if (IsKeyboardFocusWithin) { Keyboard.Focus(null); } // Menus should not have focus returned to them automatically DependencyObject parent = Parent; if (parent != null) { DependencyObject parentScope = FocusManager.GetFocusScope(parent); if (parentScope != null) { IInputElement focusedElement = FocusManager.GetFocusedElement(parentScope); if (ElementIsWithin(focusedElement)) { FocusManager.SetFocusedElement(parentScope, null); } } } } private bool ElementIsWithin(IInputElement element) { if (element == this) { return true; } MenuItem menuItem = element as MenuItem; while (menuItem != null) { DependencyObject parent = menuItem.Parent; if (parent == null) { parent = ItemsControl.ItemsControlFromItemContainer(menuItem); } if (parent == this) { return true; } else { menuItem = parent as MenuItem; } } return false; } // 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 { // CurrentSelection = null; RestorePreviousFocus(); } } #endregion //------------------------------------------------------------------- // // Private Fields // //-------------------------------------------------------------------- #region Private Fields ////// Currently selected item in this menu or submenu. /// ///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.IsSelected = false; } _currentSelection = value; if (_currentSelection != null) { _currentSelection.IsSelected = true; 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 { RaiseClrEvent(InternalMenuModeChangedKey, EventArgs.Empty); } } if (!isMenuMode) { bool wasSubmenuOpen = false; if (CurrentSelection != null) { wasSubmenuOpen = CurrentSelection.IsSubmenuOpen; CurrentSelection.IsSubmenuOpen = false; CurrentSelection = null; } // 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); // 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 MenuItem _currentSelection; private BitVector32 _bitFlags = new BitVector32(0); private enum MenuBaseFlags { IgnoreNextLeftRelease = 0x01, IgnoreNextRightRelease = 0x02, IsMenuMode = 0x04, OpenOnMouseEnter = 0x08, } #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. } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- 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.Media; using System.Windows.Input; using System.Security; using System.Security.Permissions; using System; namespace System.Windows.Controls.Primitives { /// /// Control that defines a menu of choices for users to invoke. /// [Localizability(LocalizationCategory.Menu)] [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(MenuItem))] public abstract class MenuBase : ItemsControl { //------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------- #region Constructors ////// Default DependencyObject constructor /// ////// Automatic determination of current Dispatcher. Use alternative constructor /// that accepts a Dispatcher for best performance. /// 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(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)); 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 //-------------------------------------------------------------------- // // Protected Methods // //------------------------------------------------------------------- #region Protected Methods /// /// Called when any mouse button is pressed on this subtree /// /// Sender of the event. /// Event arguments. private static void OnMouseButtonDown(object sender, MouseButtonEventArgs e) { ((MenuBase)sender).HandleMouseButton(e); } ////// Called when any mouse right button is released on this subtree /// /// Sender of the event. /// Event arguments. private static void OnMouseButtonUp(object sender, MouseButtonEventArgs e) { ((MenuBase)sender).HandleMouseButton(e); } ////// Called when any mouse button is pressed or released on this subtree /// /// Event arguments. 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; } } ////// Called when IsMouseOver changes on this element. /// /// 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; } } ////// Called when the focus is no longer on or within this element. /// /// 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), typeof(MenuBase)); private static void OnIsSelectedChanged(object sender, RoutedPropertyChangedEventArgs 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.IsSubmenuOpen = false; } 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.IsSubmenuOpen = 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; } /// /// This is the method that responds to the KeyDown event. /// /// Event arguments protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); Key key = e.Key; switch (key) { case Key.Escape: { if (CurrentSelection != null && CurrentSelection.IsSubmenuOpen) { CurrentSelection.IsSubmenuOpen = false; 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; } } ////// Return true if the item is (or is eligible to be) its own ItemUI /// protected override bool IsItemItsOwnContainerOverride(object item) { return (item is MenuItem || item is Separator); } ///Create or identify the element used to display the given item. protected override DependencyObject GetContainerForItemOverride() { return new MenuItem(); } #endregion //-------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------- #region Private Methods ////// Called when this element loses capture. /// private static void OnLostMouseCapture(object sender, MouseEventArgs e) { MenuBase menu = sender as MenuBase; // // 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; } } } } ////// Called when any menu item within this subtree got clicked. /// Closes all submenus in this tree. /// /// /// 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; } } } ////// Called when IsMenuMode changes. /// internal event EventHandler InternalMenuModeChanged { add { EventHandlersStoreAdd(InternalMenuModeChangedKey, value); } remove { EventHandlersStoreRemove(InternalMenuModeChangedKey, value); } } private static readonly EventPrivateKey InternalMenuModeChangedKey = new EventPrivateKey(); private void RestorePreviousFocus() { if (IsKeyboardFocusWithin) { Keyboard.Focus(null); } // Menus should not have focus returned to them automatically DependencyObject parent = Parent; if (parent != null) { DependencyObject parentScope = FocusManager.GetFocusScope(parent); if (parentScope != null) { IInputElement focusedElement = FocusManager.GetFocusedElement(parentScope); if (ElementIsWithin(focusedElement)) { FocusManager.SetFocusedElement(parentScope, null); } } } } private bool ElementIsWithin(IInputElement element) { if (element == this) { return true; } MenuItem menuItem = element as MenuItem; while (menuItem != null) { DependencyObject parent = menuItem.Parent; if (parent == null) { parent = ItemsControl.ItemsControlFromItemContainer(menuItem); } if (parent == this) { return true; } else { menuItem = parent as MenuItem; } } return false; } // 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 { // CurrentSelection = null; RestorePreviousFocus(); } } #endregion //------------------------------------------------------------------- // // Private Fields // //-------------------------------------------------------------------- #region Private Fields ////// Currently selected item in this menu or submenu. /// ///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.IsSelected = false; } _currentSelection = value; if (_currentSelection != null) { _currentSelection.IsSelected = true; 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 { RaiseClrEvent(InternalMenuModeChangedKey, EventArgs.Empty); } } if (!isMenuMode) { bool wasSubmenuOpen = false; if (CurrentSelection != null) { wasSubmenuOpen = CurrentSelection.IsSubmenuOpen; CurrentSelection.IsSubmenuOpen = false; CurrentSelection = null; } // 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); // 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 MenuItem _currentSelection; private BitVector32 _bitFlags = new BitVector32(0); private enum MenuBaseFlags { IgnoreNextLeftRelease = 0x01, IgnoreNextRightRelease = 0x02, IsMenuMode = 0x04, OpenOnMouseEnter = 0x08, } #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. } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- Hashtable.cs
- TimeSpanOrInfiniteValidator.cs
- ImageListStreamer.cs
- DbMetaDataColumnNames.cs
- VBCodeProvider.cs
- HtmlShimManager.cs
- Int64AnimationBase.cs
- DataBindingsDialog.cs
- ProtocolImporter.cs
- RowBinding.cs
- TabControlEvent.cs
- InternalEnumValidator.cs
- ToolStripProgressBar.cs
- BitmapEncoder.cs
- CustomLineCap.cs
- PieceDirectory.cs
- DirtyTextRange.cs
- PerfService.cs
- TypeNameConverter.cs
- Function.cs
- PolicyManager.cs
- MessageQueuePermissionEntryCollection.cs
- SmtpReplyReaderFactory.cs
- Base64Encoder.cs
- SqlDataSourceQueryEditor.cs
- safex509handles.cs
- HtmlPhoneCallAdapter.cs
- StatusBar.cs
- OracleCommand.cs
- SimpleLine.cs
- AttachmentService.cs
- MSAAEventDispatcher.cs
- InfoCardAsymmetricCrypto.cs
- SiteMapNodeItemEventArgs.cs
- CustomSignedXml.cs
- RemoteArgument.cs
- AliasGenerator.cs
- IPCCacheManager.cs
- DataSourceView.cs
- DesignerResources.cs
- MenuCommandService.cs
- URLAttribute.cs
- PointConverter.cs
- SafeArrayTypeMismatchException.cs
- UiaCoreProviderApi.cs
- ReachDocumentReferenceSerializerAsync.cs
- CollectionBase.cs
- LogExtent.cs
- SignedXml.cs
- externdll.cs
- ProvideValueServiceProvider.cs
- ListViewGroupItemCollection.cs
- ArglessEventHandlerProxy.cs
- TileBrush.cs
- WebBrowserNavigatingEventHandler.cs
- MULTI_QI.cs
- ScriptManager.cs
- ConfigurationPropertyCollection.cs
- SafeCancelMibChangeNotify.cs
- AppliesToBehaviorDecisionTable.cs
- DataBindingCollection.cs
- TextTreeTextNode.cs
- LinkedList.cs
- Cell.cs
- StyleHelper.cs
- StandardMenuStripVerb.cs
- wgx_commands.cs
- CTreeGenerator.cs
- LinkedResourceCollection.cs
- FacetEnabledSchemaElement.cs
- DataGridToolTip.cs
- SQLConvert.cs
- DataGridViewLinkCell.cs
- TransformConverter.cs
- localization.cs
- X509CertificateStore.cs
- NumberSubstitution.cs
- SqlBulkCopyColumnMappingCollection.cs
- TextBlockAutomationPeer.cs
- ItemsControl.cs
- ZipIOExtraFieldPaddingElement.cs
- SymbolPair.cs
- PreviewPrintController.cs
- SecurityTokenParametersEnumerable.cs
- NameTable.cs
- DetailsViewUpdatedEventArgs.cs
- TextElement.cs
- TransactionManager.cs
- Processor.cs
- ComPlusTraceRecord.cs
- PropertyConverter.cs
- UpdatePanelControlTrigger.cs
- RectangleGeometry.cs
- MembershipSection.cs
- UserInitiatedNavigationPermission.cs
- EventHandlers.cs
- ScaleTransform.cs
- WindowsStreamSecurityUpgradeProvider.cs
- CompilationUtil.cs
- SelectQueryOperator.cs