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
- WindowPatternIdentifiers.cs
- StylusShape.cs
- AutomationProperties.cs
- XmlIlVisitor.cs
- RadioButtonFlatAdapter.cs
- Tracer.cs
- RoutedEvent.cs
- ComponentTray.cs
- StrongNamePublicKeyBlob.cs
- SecUtil.cs
- VectorCollectionValueSerializer.cs
- DispatchOperationRuntime.cs
- SqlNotificationRequest.cs
- RuntimeConfigLKG.cs
- InkCanvasSelectionAdorner.cs
- InfoCardListRequest.cs
- PersonalizationProviderHelper.cs
- DesignerRegion.cs
- CharacterString.cs
- Asn1IntegerConverter.cs
- ClientTargetSection.cs
- SqlFunctionAttribute.cs
- AnnotationMap.cs
- TemplateXamlParser.cs
- TogglePattern.cs
- UnsafeNativeMethods.cs
- ReferenceSchema.cs
- manifestimages.cs
- AttachmentService.cs
- bindurihelper.cs
- KeyBinding.cs
- LoginUtil.cs
- VisualCollection.cs
- MaskedTextBoxDesigner.cs
- XmlSchemaNotation.cs
- DataSet.cs
- PathGradientBrush.cs
- PointValueSerializer.cs
- VideoDrawing.cs
- XPathCompileException.cs
- MemoryStream.cs
- DrawingContextDrawingContextWalker.cs
- WebPartConnectionsDisconnectVerb.cs
- ManagedIStream.cs
- ModelItemImpl.cs
- ReflectPropertyDescriptor.cs
- ObjectStateEntryDbDataRecord.cs
- FileController.cs
- PanelStyle.cs
- CheckBoxField.cs
- ToolStripDropDownButton.cs
- TableLayoutStyle.cs
- PropVariant.cs
- StdValidatorsAndConverters.cs
- TreeViewTemplateSelector.cs
- PackUriHelper.cs
- TCPClient.cs
- HttpMethodConstraint.cs
- DecoratedNameAttribute.cs
- ConfigurationPermission.cs
- fixedPageContentExtractor.cs
- uribuilder.cs
- CodeSubDirectoriesCollection.cs
- IndexExpression.cs
- EventProviderWriter.cs
- MaskDescriptor.cs
- DaylightTime.cs
- SiteOfOriginContainer.cs
- hebrewshape.cs
- PackageProperties.cs
- DocComment.cs
- DataServiceConfiguration.cs
- InputLanguageCollection.cs
- ResourceDictionary.cs
- EntityDataSourceEntityTypeFilterItem.cs
- Expression.cs
- MenuItemStyleCollectionEditor.cs
- IMembershipProvider.cs
- WebSysDescriptionAttribute.cs
- HighContrastHelper.cs
- XmlSerializationWriter.cs
- BinaryKeyIdentifierClause.cs
- NavigationService.cs
- DataGridRowsPresenter.cs
- Int16Storage.cs
- counter.cs
- AssociationTypeEmitter.cs
- XmlSchemaComplexContent.cs
- SvcMapFileLoader.cs
- WebPartDisplayMode.cs
- FixedSOMLineCollection.cs
- RequestCachingSection.cs
- ICspAsymmetricAlgorithm.cs
- DesigntimeLicenseContext.cs
- SoapIncludeAttribute.cs
- DataObject.cs
- compensatingcollection.cs
- EraserBehavior.cs
- CollectionViewGroup.cs
- ChunkedMemoryStream.cs