|
//------------------------------------------------------------------------------
// <copyright file="ToolStripManager.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Windows.Forms {
using System;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using System.ComponentModel;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Drawing;
using Microsoft.Win32;
using System.Globalization;
using System.Security;
using System.Security.Permissions;
/// <include file='doc\ToolStripManager.uex' path='docs/doc[@for="ToolStripManager"]/*' />
public sealed class ToolStripManager {
// WARNING: ThreadStatic initialization happens only on the first thread at class CTOR time.
// use InitializeThread mechanism to initialize ThreadStatic members
[ThreadStatic]
private static ClientUtils.WeakRefCollection toolStripWeakArrayList;
[ThreadStatic]
private static ClientUtils.WeakRefCollection toolStripPanelWeakArrayList;
[ThreadStatic]
private static bool initialized;
private static Font defaultFont;
private static ConcurrentDictionary<int, Font> defaultFontCache = new ConcurrentDictionary<int, Font>();
// WARNING: When subscribing to static event handlers - make sure you unhook from them
// otherwise you can leak USER objects on process shutdown.
// Consider: use WeakRefCollection
[ThreadStatic]
private static Delegate[] staticEventHandlers;
private const int staticEventDefaultRendererChanged = 0;
private const int staticEventCount = 1;
private static object internalSyncObject = new object();
private static int currentDpi = DpiHelper.DeviceDpi;
private static void InitalizeThread() {
if (!initialized) {
initialized = true;
currentRendererType = ProfessionalRendererType;
}
}
private ToolStripManager() {
}
static ToolStripManager() {
SystemEvents.UserPreferenceChanging += new UserPreferenceChangingEventHandler(OnUserPreferenceChanging);
}
internal static Font DefaultFont {
get {
Font sysFont = null;
// We need to cache the default fonts for the different DPIs.
if (DpiHelper.EnableToolStripPerMonitorV2HighDpiImprovements) {
int dpi = CurrentDpi;
Font retFont = null;
if (defaultFontCache.TryGetValue(dpi, out retFont) == false || retFont == null) {
// default to menu font
sysFont = SystemInformation.GetMenuFontForDpi(dpi);
if (sysFont != null) {
// ensure font is in pixels so it displays properly in the property grid at design time.
if (sysFont.Unit != GraphicsUnit.Point) {
retFont = ControlPaint.FontInPoints(sysFont);
sysFont.Dispose();
}
else {
retFont = sysFont;
}
defaultFontCache[dpi] = retFont;
}
}
return retFont;
}
else {
Font retFont = defaultFont; // threadsafe local reference
if (retFont == null) {
lock (internalSyncObject) {
// double check the defaultFont after the lock.
retFont = defaultFont;
if (retFont == null) {
// default to menu font
sysFont = SystemFonts.MenuFont;
if (sysFont == null) {
// ...or to control font if menu font unavailable
sysFont = Control.DefaultFont;
}
if (sysFont != null) {
// ensure font is in pixels so it displays properly in the property grid at design time.
if (sysFont.Unit != GraphicsUnit.Point) {
defaultFont = ControlPaint.FontInPoints(sysFont);
retFont = defaultFont;
sysFont.Dispose();
}
else {
defaultFont = sysFont;
retFont = defaultFont;
}
}
return retFont;
}
}
}
return retFont;
}
}
}
internal static int CurrentDpi {
get {
return currentDpi;
}
set {
currentDpi = value;
}
}
internal static ClientUtils.WeakRefCollection ToolStrips {
get {
if (toolStripWeakArrayList == null) {
toolStripWeakArrayList = new ClientUtils.WeakRefCollection();
}
return toolStripWeakArrayList;
}
}
///<devdoc>Static events only!!!</devdoc>
private static void AddEventHandler(int key, Delegate value) {
lock (internalSyncObject) {
if (staticEventHandlers == null) {
staticEventHandlers = new Delegate[staticEventCount];
}
staticEventHandlers[key] = Delegate.Combine(staticEventHandlers[key], value);
}
}
/// <include file='doc\ToolStripManager.uex' path='docs/doc[@for="ToolStripManager.FindToolStrip"]/*' />
/// <devdoc>
/// Find a toolstrip in the weak ref arraylist, return null if nothing was found
/// </devdoc>
[UIPermission(SecurityAction.Demand, Window = UIPermissionWindow.AllWindows)]
public static ToolStrip FindToolStrip(string toolStripName) {
ToolStrip result = null;
for (int i = 0; i < ToolStrips.Count; i++) {
// is this the right string comparaison?
if (ToolStrips[i] != null && String.Equals(((ToolStrip)ToolStrips[i]).Name, toolStripName, StringComparison.Ordinal)) {
result = (ToolStrip)ToolStrips[i];
break;
}
}
return result;
}
/// <include file='doc\ToolStripManager.uex' path='docs/doc[@for="ToolStripManager.FindToolStrip"]/*' />
/// <devdoc>
/// Find a toolstrip in the weak ref arraylist, return null if nothing was found
/// </devdoc>
[UIPermission(SecurityAction.Demand, Window = UIPermissionWindow.AllWindows)]
internal static ToolStrip FindToolStrip(Form owningForm, string toolStripName)
{
ToolStrip result = null;
for (int i = 0; i < ToolStrips.Count; i++)
{
// is this the right string comparaison?
if (ToolStrips[i] != null && String.Equals(((ToolStrip)ToolStrips[i]).Name, toolStripName, StringComparison.Ordinal))
{
result = (ToolStrip)ToolStrips[i];
if (result.FindForm() == owningForm) {
break;
}
}
}
return result;
}
private static bool CanChangeSelection(ToolStrip start, ToolStrip toolStrip) {
if (toolStrip == null) {
Debug.Fail("passed in bogus toolstrip, why?");
return false;
}
bool canChange = toolStrip.TabStop == false &&
toolStrip.Enabled &&
toolStrip.Visible &&
!toolStrip.IsDisposed &&
!toolStrip.Disposing &&
!toolStrip.IsDropDown &&
IsOnSameWindow(start, toolStrip);
if (canChange) {
foreach (ToolStripItem item in toolStrip.Items) {
if (item.CanSelect) {
return true;
}
}
}
return false;
}
private static bool ChangeSelection(ToolStrip start, ToolStrip toolStrip) {
if (toolStrip == null || start == null) {
Debug.Assert(toolStrip != null, "passed in bogus toolstrip, why?");
Debug.Assert(start != null, "passed in bogus start, why?");
return false;
}
if (start == toolStrip) {
return false;
}
if (ModalMenuFilter.InMenuMode) {
if (ModalMenuFilter.GetActiveToolStrip() == start) {
ModalMenuFilter.RemoveActiveToolStrip(start);
start.NotifySelectionChange(null);
}
ModalMenuFilter.SetActiveToolStrip(toolStrip);
}
else {
toolStrip.FocusInternal();
}
// copy over the hwnd that we want to restore focus to on ESC
start.SnapFocusChange(toolStrip);
toolStrip.SelectNextToolStripItem(null, toolStrip.RightToLeft != RightToLeft.Yes);
return true;
}
private static Delegate GetEventHandler(int key) {
lock (internalSyncObject) {
if (staticEventHandlers == null)
return null;
else
return (Delegate)staticEventHandlers[key];
}
}
private static bool IsOnSameWindow(Control control1, Control control2) {
return (WindowsFormsUtils.GetRootHWnd(control1).Handle == WindowsFormsUtils.GetRootHWnd(control2).Handle);
}
internal static bool IsThreadUsingToolStrips() {
return (toolStripWeakArrayList != null && (toolStripWeakArrayList.Count > 0));
}
private static void OnUserPreferenceChanging(object sender, UserPreferenceChangingEventArgs e) {
// using changing here so that the cache will be cleared by the time the ToolStrip
// hooks onto the changed event.
// SPI_SETNONCLIENTMETRICS is put up in WM_SETTINGCHANGE if the Menu font changes.
// this corresponds to UserPreferenceCategory.Window.
if (e.Category == UserPreferenceCategory.Window) {
if (DpiHelper.EnableToolStripPerMonitorV2HighDpiImprovements) {
defaultFontCache.Clear();
}
else {
lock (internalSyncObject) {
defaultFont = null;
}
}
}
}
internal static void NotifyMenuModeChange(bool invalidateText, bool activationChange) {
bool toolStripPruneNeeded = false;
// If we've toggled the ShowUnderlines value, we'll need to invalidate
for (int i = 0; i < ToolStrips.Count; i++) {
ToolStrip toolStrip = ToolStrips[i] as ToolStrip;
if (toolStrip == null) {
toolStripPruneNeeded = true;
continue;
}
if (invalidateText) {
toolStrip.InvalidateTextItems();
}
if (activationChange) {
toolStrip.KeyboardActive = false;
}
}
if (toolStripPruneNeeded) {
PruneToolStripList();
}
}
/// <devdoc> removes dead entries from the toolstrip weak reference collection. </devdoc>
internal static void PruneToolStripList() {
if (toolStripWeakArrayList != null) {
if (toolStripWeakArrayList.Count > 0) {
for (int i = toolStripWeakArrayList.Count - 1; i >= 0; i--) {
if (toolStripWeakArrayList[i] == null) {
toolStripWeakArrayList.RemoveAt(i);
}
}
}
}
}
/// <devdoc> static events only!!!</devdoc>
private static void RemoveEventHandler(int key, Delegate value) {
lock (internalSyncObject) {
if (staticEventHandlers != null) {
staticEventHandlers[key] = Delegate.Remove(staticEventHandlers[key], value);
}
}
}
// this is a special version of SelectNextControl which looks for ToolStrips
// that are TabStop = false in TabOrder. This is used from Control+Tab
// handling to swap focus between ToolStrips.
internal static bool SelectNextToolStrip(ToolStrip start, bool forward) {
if (start == null || start.ParentInternal == null) {
Debug.Assert(start != null, "why is null passed here?");
return false;
}
ToolStrip wrappedControl = null;
ToolStrip nextControl = null;
int startTabIndex = start.TabIndex;
int index = ToolStrips.IndexOf(start);
int totalCount = ToolStrips.Count;
for (int i = 0; i < totalCount; i++) {
index = (forward) ? (index + 1) % totalCount
: (index + totalCount - 1) % totalCount;
ToolStrip toolStrip = ToolStrips[index] as ToolStrip;
if (toolStrip == null ||
toolStrip == start) {
continue;
}
int nextControlTabIndex = toolStrip.TabIndex;
Debug.WriteLineIf(ToolStrip.ControlTabDebug.TraceVerbose, "SELECTNEXTTOOLSTRIP: start: " + startTabIndex.ToString(CultureInfo.CurrentCulture) + " " + start.Name);
// since CanChangeSelection can iterate through all the items in a toolstrip,
// defer the checking until we think we've got a viable TabIndex candidate.
// this brings it to O(n+m) instead of O(n*m) where n is # toolstrips & m is avg number
// items/toolstrip
if (forward) {
if (nextControlTabIndex >= startTabIndex && CanChangeSelection(start, toolStrip)) {
Debug.WriteLineIf(ToolStrip.ControlTabDebug.TraceVerbose, "FORWARD considering selection " + toolStrip.Name + " " + toolStrip.TabIndex.ToString(CultureInfo.CurrentCulture));
if (nextControl == null) {
nextControl = toolStrip;
}
else if (toolStrip.TabIndex < nextControl.TabIndex) {
// we want to pick a larger index, but one that's
// closest to the start tab index.
nextControl = toolStrip;
}
}
else if (((wrappedControl == null) || (toolStrip.TabIndex < wrappedControl.TabIndex))
&& CanChangeSelection(start, toolStrip)) {
// we've found a candidate for wrapping (the one with the smallest tab index in the collection)
Debug.WriteLineIf(ToolStrip.ControlTabDebug.TraceVerbose, "\tFORWARD new wrap candidate " + toolStrip.Name);
wrappedControl = toolStrip;
}
}
else {
if (nextControlTabIndex <= startTabIndex && CanChangeSelection(start, toolStrip)) {
Debug.WriteLineIf(ToolStrip.ControlTabDebug.TraceVerbose, "\tREVERSE selecting " + toolStrip.Name);
if (nextControl == null) {
nextControl = toolStrip;
}
else if (toolStrip.TabIndex > nextControl.TabIndex) {
// we want to pick a smaller index, but one that's
// closest to the start tab index.
nextControl = toolStrip;
}
}
else if (((wrappedControl == null) || (toolStrip.TabIndex > wrappedControl.TabIndex))
&& CanChangeSelection(start, toolStrip)) {
// we've found a candidate for wrapping (the one with the largest tab index in the collection)
Debug.WriteLineIf(ToolStrip.ControlTabDebug.TraceVerbose, "\tREVERSE new wrap candidate " + toolStrip.Name);
wrappedControl = toolStrip;
}
else {
Debug.WriteLineIf(ToolStrip.ControlTabDebug.TraceVerbose, "\tREVERSE skipping wrap candidate " + toolStrip.Name + toolStrip.TabIndex.ToString(CultureInfo.CurrentCulture));
}
}
if (nextControl != null
&& Math.Abs(nextControl.TabIndex - startTabIndex) <= 1) {
// if we've found a valid candidate AND it's within 1
// then bail, we've found something close enough.
break;
}
}
if (nextControl != null) {
Debug.WriteLineIf(ToolStrip.ControlTabDebug.TraceVerbose, "SELECTING " + nextControl.Name);
return ChangeSelection(start, nextControl);
}
else if (wrappedControl != null) {
Debug.WriteLineIf(ToolStrip.ControlTabDebug.TraceVerbose, "WRAPPING " + wrappedControl.Name);
return ChangeSelection(start, wrappedControl);
}
return false;
}
/// ============================================================================
/// BEGIN task specific functions. Since ToolStripManager is used
/// for Painting, Merging and Rafting, and who knows what else in the future
/// the following properties/methods/events are organized in regions
/// alphabetically by task
/// ----------------------------------------------------------------------------
///
/// ToolStripManager Default Renderer
///
#region DefaultRenderer
/// These are thread static because we want separate instances
/// for each thread. We dont want to guarantee thread safety
/// and dont want to have to take locks in painting code.
[ThreadStatic]
private static ToolStripRenderer defaultRenderer;
// types cached for perf.
internal static Type SystemRendererType = typeof(ToolStripSystemRenderer);
internal static Type ProfessionalRendererType = typeof(ToolStripProfessionalRenderer);
private static bool visualStylesEnabledIfPossible = true;
[ThreadStatic]
private static Type currentRendererType;
private static Type CurrentRendererType {
get {
InitalizeThread();
return currentRendererType;
}
set {
currentRendererType = value;
}
}
private static Type DefaultRendererType {
get {
return ProfessionalRendererType;
}
}
/// <include file='doc\ToolStripManager.uex' path='docs/doc[@for="ToolStripManager.Renderer"]/*' />
/// <devdoc> the default renderer for the thread. When ToolStrip.RenderMode is set to manager - this
/// is the property used.
/// </devdoc>
public static ToolStripRenderer Renderer {
get {
if (defaultRenderer == null) {
defaultRenderer = CreateRenderer(RenderMode);
}
return defaultRenderer;
}
[UIPermission(SecurityAction.Demand, Window = UIPermissionWindow.AllWindows)]
set {
/// SECREVIEW in InternetZone, use individual ToolStrip.Renderer property rather than global one.
if (defaultRenderer != value) {
CurrentRendererType = (value == null) ? DefaultRendererType : value.GetType();
defaultRenderer = value;
EventHandler handler = (EventHandler)GetEventHandler(staticEventDefaultRendererChanged);
if (handler != null) {
handler(null, EventArgs.Empty);
}
}
}
}
// <devdoc>
// occurs when toolstripmanager.Renderer property has changed
//
// WARNING: When subscribing to static event handlers - make sure you unhook from them
// otherwise you can leak USER objects on process shutdown.
// </devdoc>
// PM team has reviewed and decided on naming changes already
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")]
public static event EventHandler RendererChanged {
add {
AddEventHandler(staticEventDefaultRendererChanged, value);
}
remove {
RemoveEventHandler(staticEventDefaultRendererChanged, value);
}
}
/// <include file='doc\ToolStripManager.uex' path='docs/doc[@for="ToolStripManager.RenderMode"]/*' />
/// <devdoc> returns the default toolstrip RenderMode for the thread </devdoc>
public static ToolStripManagerRenderMode RenderMode {
get {
Type currentType = CurrentRendererType;
if (defaultRenderer != null && !defaultRenderer.IsAutoGenerated) {
return ToolStripManagerRenderMode.Custom;
}
// check the type of the currently set renderer.
// types are cached as this may be called frequently.
if (currentType == ProfessionalRendererType) {
return ToolStripManagerRenderMode.Professional;
}
if (currentType == SystemRendererType) {
return ToolStripManagerRenderMode.System;
}
return ToolStripManagerRenderMode.Custom;
}
[UIPermission(SecurityAction.Demand, Window = UIPermissionWindow.AllWindows)]
set {
/// SECREVIEW in InternetZone, use individual ToolStrip.RenderMode property rather than global one.
//valid values are 0x0 to 0x2
if (!ClientUtils.IsEnumValid(value, (int)value, (int)ToolStripManagerRenderMode.Custom, (int)ToolStripManagerRenderMode.Professional)) {
throw new InvalidEnumArgumentException("value", (int)value, typeof(ToolStripManagerRenderMode));
}
switch (value) {
case ToolStripManagerRenderMode.System:
case ToolStripManagerRenderMode.Professional:
Renderer = CreateRenderer(value);
break;
case ToolStripManagerRenderMode.Custom:
throw new NotSupportedException(SR.GetString(SR.ToolStripRenderModeUseRendererPropertyInstead));
}
}
}
/// <include file='doc\ToolStripManager.uex' path='docs/doc[@for="ToolStripManager.VisualStylesEnabled"]/*' />
/// <devdoc> an additional layering of control. this lets you pick whether your toolbars
/// should use visual style information (theming) to render itself.
/// potentially you could want a themed app but an unthemed toolstrip. (e.g. Whidbey VS).
/// </devdoc>
public static bool VisualStylesEnabled {
get {
return visualStylesEnabledIfPossible && Application.RenderWithVisualStyles;
}
[UIPermission(SecurityAction.Demand, Window = UIPermissionWindow.AllWindows)]
set {
bool oldVis = VisualStylesEnabled;
visualStylesEnabledIfPossible = value;
if (oldVis != VisualStylesEnabled) {
EventHandler handler = (EventHandler)GetEventHandler(staticEventDefaultRendererChanged);
if (handler != null) {
handler(null, EventArgs.Empty);
}
}
}
}
internal static ToolStripRenderer CreateRenderer(ToolStripManagerRenderMode renderMode) {
switch (renderMode) {
case ToolStripManagerRenderMode.System:
return new ToolStripSystemRenderer(/*isAutoGenerated=*/true);
case ToolStripManagerRenderMode.Professional:
return new ToolStripProfessionalRenderer(/*isAutoGenerated=*/true);
case ToolStripManagerRenderMode.Custom:
default:
return new ToolStripSystemRenderer(/*isAutoGenerated=*/true);
}
}
internal static ToolStripRenderer CreateRenderer(ToolStripRenderMode renderMode) {
switch (renderMode) {
case ToolStripRenderMode.System:
return new ToolStripSystemRenderer(/*isAutoGenerated=*/true);
case ToolStripRenderMode.Professional:
return new ToolStripProfessionalRenderer(/*isAutoGenerated=*/true);
case ToolStripRenderMode.Custom:
default:
return new ToolStripSystemRenderer(/*isAutoGenerated=*/true);
}
}
#endregion DefaultRenderer
#region ToolStripPanel
internal static ClientUtils.WeakRefCollection ToolStripPanels {
get {
if (toolStripPanelWeakArrayList == null) {
toolStripPanelWeakArrayList = new ClientUtils.WeakRefCollection();
}
return toolStripPanelWeakArrayList;
}
}
internal static ToolStripPanel ToolStripPanelFromPoint(Control draggedControl, Point screenLocation) {
if (toolStripPanelWeakArrayList != null) {
ISupportToolStripPanel draggedItem = draggedControl as ISupportToolStripPanel;
bool rootWindowCheck = draggedItem.IsCurrentlyDragging;
for (int i = 0; i < toolStripPanelWeakArrayList.Count; i++) {
ToolStripPanel toolStripPanel = toolStripPanelWeakArrayList[i] as ToolStripPanel;
if (toolStripPanel != null && toolStripPanel.IsHandleCreated && toolStripPanel.Visible &&
toolStripPanel.DragBounds.Contains(toolStripPanel.PointToClient(screenLocation))) {
// VSWhidbey 342496 - ensure that we cant drag off one window to another.
if (rootWindowCheck) {
if (IsOnSameWindow(draggedControl, toolStripPanel)) {
return toolStripPanel;
}
}
else {
return toolStripPanel;
}
}
}
}
return null;
}
#endregion
#region ToolStripSettings
/// <devdoc>
/// Loads settings for the given Form using the form type's fullname as settings key.
/// </devdoc>
public static void LoadSettings(Form targetForm) {
if (targetForm == null) {
throw new ArgumentNullException("targetForm");
}
LoadSettings(targetForm, targetForm.GetType().FullName);
}
/// <devdoc>
/// Loads settings for the given Form with the given settings key.
/// </devdoc>
public static void LoadSettings(Form targetForm, string key) {
if (targetForm == null) {
throw new ArgumentNullException("targetForm");
}
if (String.IsNullOrEmpty(key)) {
throw new ArgumentNullException("key");
}
ToolStripSettingsManager settingsManager = new ToolStripSettingsManager(targetForm, key);
settingsManager.Load();
}
/// <devdoc>
/// Saves settings for the given form using the form type's fullname as settings key.
/// </devdoc>
public static void SaveSettings(Form sourceForm) {
if (sourceForm == null) {
throw new ArgumentNullException("sourceForm");
}
SaveSettings(sourceForm, sourceForm.GetType().FullName);
}
/// <devdoc>
/// Saves settings for the given form with the given settings key.
/// </devdoc>
public static void SaveSettings(Form sourceForm, string key) {
if (sourceForm == null) {
throw new ArgumentNullException("sourceForm");
}
if (String.IsNullOrEmpty(key)) {
throw new ArgumentNullException("key");
}
ToolStripSettingsManager settingsManager = new ToolStripSettingsManager(sourceForm, key); ;
settingsManager.Save();
}
#endregion
///
/// ToolStripManager ALT key PreProcessing
///
#region MenuKeyAndShortcutProcessing
/// ModalMenuFilter
/// - this installs a message filter when a dropdown becomes active.
/// - the message filter
/// a. eats WM_MOUSEMOVEs so that the window that's underneath
/// doesnt get highlight processing/tooltips
/// b. detects mouse clicks. if the click is outside the dropdown, it
/// dismisses it.
/// c. detects when the active window has changed. If the active window
/// is unexpected, it dismisses all dropdowns.
/// d. detects keyboard messages, and redirects them to the active dropdown.
///
/// - There should be 1 Message Filter per thread and it should be uninstalled once
/// the last dropdown has gone away
/// This is not part of ToolStripManager because it's DropDown specific and
/// we dont want to publicly expose this message filter.
internal class ModalMenuFilter : IMessageModifyAndFilter {
private HandleRef _activeHwnd = NativeMethods.NullHandleRef; // the window that was active when we showed the dropdown
private HandleRef _lastActiveWindow = NativeMethods.NullHandleRef; // the window that was last known to be active
private List<ToolStrip> _inputFilterQueue;
private bool _inMenuMode = false;
private bool _caretHidden = false;
private bool _showUnderlines = false;
private bool menuKeyToggle = false;
private bool _suspendMenuMode = false;
private HostedWindowsFormsMessageHook messageHook;
private System.Windows.Forms.Timer _ensureMessageProcessingTimer = null;
private const int MESSAGE_PROCESSING_INTERVAL = 500;
private ToolStrip _toplevelToolStrip = null;
private readonly WeakReference<IKeyboardToolTip> lastFocusedTool = new WeakReference<IKeyboardToolTip>(null);
#if DEBUG
bool _justEnteredMenuMode = false;
#endif
[ThreadStatic]
private static ModalMenuFilter _instance;
internal static ModalMenuFilter Instance {
get {
if (_instance == null) {
_instance = new ModalMenuFilter();
}
return _instance;
}
}
private ModalMenuFilter() {
}
/// this is the HWnd that was active when we popped the first dropdown.
internal static HandleRef ActiveHwnd {
get { return Instance.ActiveHwndInternal; }
}
// returns whether or not we should show focus cues for mnemonics.
public bool ShowUnderlines {
get {
return _showUnderlines;
}
set {
if (_showUnderlines != value) {
_showUnderlines = value;
ToolStripManager.NotifyMenuModeChange(/*textStyleChanged*/true, /*activationChanged*/false);
}
}
}
private HandleRef ActiveHwndInternal {
get {
return _activeHwnd;
}
set {
if (_activeHwnd.Handle != value.Handle) {
Control control = null;
// unsubscribe from handle recreate.
if (_activeHwnd.Handle != IntPtr.Zero) {
control = Control.FromHandleInternal(_activeHwnd.Handle);
if (control != null) {
control.HandleCreated -= new EventHandler(OnActiveHwndHandleCreated);
}
}
_activeHwnd = value;
// make sure we watch out for handle recreates.
control = Control.FromHandleInternal(_activeHwnd.Handle);
if (control != null) {
control.HandleCreated += new EventHandler(OnActiveHwndHandleCreated);
}
}
}
}
// returns whether or not someone has called EnterMenuMode.
internal static bool InMenuMode {
get { return Instance._inMenuMode; }
}
internal static bool MenuKeyToggle {
get {
return Instance.menuKeyToggle;
}
set {
if (Instance.menuKeyToggle != value) {
Instance.menuKeyToggle = value;
}
}
}
/// This is used in scenarios where windows forms
/// does not own the message pump, but needs access
/// to the message queue.
private HostedWindowsFormsMessageHook MessageHook {
get {
if (messageHook == null) {
messageHook = new HostedWindowsFormsMessageHook();
}
return messageHook;
}
}
// ToolStrip analog to WM_ENTERMENULOOP
private void EnterMenuModeCore() {
Debug.Assert(!InMenuMode, "How did we get here if we're already in menu mode?");
if (!InMenuMode) {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "___________Entering MenuMode....");
#if DEBUG
_justEnteredMenuMode = true;
#endif
IntPtr hwndActive = UnsafeNativeMethods.GetActiveWindow();
if (hwndActive != IntPtr.Zero) {
ActiveHwndInternal = new HandleRef(this, hwndActive);
}
// PERF, SECREVIEW: dont call Application.AddMessageFilter as this could
// get called a lot and we want to have to assert AWP.
Application.ThreadContext.FromCurrent().AddMessageFilter(this);
Application.ThreadContext.FromCurrent().TrackInput(true);
if (!Application.ThreadContext.FromCurrent().GetMessageLoop(true)) {
// message filter isnt going to help as we dont own the message pump
// switch over to a MessageHook
MessageHook.HookMessages = true;
}
_inMenuMode = true;
if (!AccessibilityImprovements.UseLegacyToolTipDisplay) {
NotifyLastLastFocusedToolAboutFocusLoss();
}
// fire timer messages to force our filter to get evaluated.
ProcessMessages(true);
}
}
internal void NotifyLastLastFocusedToolAboutFocusLoss() {
IKeyboardToolTip lastFocusedTool = KeyboardToolTipStateMachine.Instance.LastFocusedTool;
if (lastFocusedTool != null) {
this.lastFocusedTool.SetTarget(lastFocusedTool);
KeyboardToolTipStateMachine.Instance.NotifyAboutLostFocus(lastFocusedTool);
}
}
internal static void ExitMenuMode() {
Instance.ExitMenuModeCore();
}
// ToolStrip analog to WM_EXITMENULOOP
private void ExitMenuModeCore() {
// ensure we've cleaned up the timer.
ProcessMessages(false);
if (InMenuMode) {
try {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "___________Exiting MenuMode....");
if (messageHook != null) {
// message filter isnt going to help as we dont own the message pump
// switch over to a MessageHook
messageHook.HookMessages = false;
}
// PERF, SECREVIEW: dont call Application.RemoveMessageFilter as this could
// get called a lot and we want to have to assert AWP.
Application.ThreadContext.FromCurrent().RemoveMessageFilter(this);
Application.ThreadContext.FromCurrent().TrackInput(false);
#if DEBUG
_justEnteredMenuMode = false;
#endif
if (ActiveHwnd.Handle != IntPtr.Zero) {
// unsubscribe from handle creates
Control control = Control.FromHandleInternal(ActiveHwnd.Handle);
if (control != null) {
control.HandleCreated -= new EventHandler(OnActiveHwndHandleCreated);
}
ActiveHwndInternal = NativeMethods.NullHandleRef;
}
if (_inputFilterQueue != null) {
_inputFilterQueue.Clear();
}
if (_caretHidden) {
_caretHidden = false;
SafeNativeMethods.ShowCaret(NativeMethods.NullHandleRef);
}
if (!AccessibilityImprovements.UseLegacyToolTipDisplay) {
IKeyboardToolTip tool;
if(this.lastFocusedTool.TryGetTarget(out tool) && tool != null) {
KeyboardToolTipStateMachine.Instance.NotifyAboutGotFocus(tool);
}
}
}
finally {
_inMenuMode = false;
// skip the setter here so we only iterate through the toolstrips once.
bool textStyleChanged = _showUnderlines;
_showUnderlines = false;
ToolStripManager.NotifyMenuModeChange(/*textStyleChanged*/textStyleChanged, /*activationChanged*/true);
}
}
}
internal static ToolStrip GetActiveToolStrip() {
return Instance.GetActiveToolStripInternal();
}
internal ToolStrip GetActiveToolStripInternal() {
if (_inputFilterQueue != null && _inputFilterQueue.Count > 0) {
return _inputFilterQueue[_inputFilterQueue.Count - 1];
}
return null;
}
// return the toolstrip that is at the root.
private ToolStrip GetCurrentToplevelToolStrip() {
if (_toplevelToolStrip == null) {
ToolStrip activeToolStrip = GetActiveToolStripInternal();
if (activeToolStrip != null) {
_toplevelToolStrip = activeToolStrip.GetToplevelOwnerToolStrip();
}
}
return _toplevelToolStrip;
}
private void OnActiveHwndHandleCreated(object sender, EventArgs e) {
Control topLevel = sender as Control;
ActiveHwndInternal = new HandleRef(this, topLevel.Handle);
}
internal static void ProcessMenuKeyDown(ref Message m) {
Keys keyData = (Keys)(int)m.WParam;
ToolStrip toolStrip = Control.FromHandleInternal(m.HWnd) as ToolStrip;
if (toolStrip != null && !toolStrip.IsDropDown) {
return;
}
// VSW 423760: handle the case where the ALT key has been pressed down while a dropdown
// was open. We need to clear off the MenuKeyToggle so the next ALT will activate
// the menu.
if (ToolStripManager.IsMenuKey(keyData)) {
if (!InMenuMode && MenuKeyToggle) {
MenuKeyToggle = false;
}
else if (!MenuKeyToggle) {
ModalMenuFilter.Instance.ShowUnderlines = true;
}
}
}
internal static void CloseActiveDropDown(ToolStripDropDown activeToolStripDropDown, ToolStripDropDownCloseReason reason) {
activeToolStripDropDown.SetCloseReason(reason);
activeToolStripDropDown.Visible = false;
// there's no more dropdowns left in the chain
if (GetActiveToolStrip() == null) {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter.CloseActiveDropDown] Calling exit because there are no more dropdowns left to activate.");
ExitMenuMode();
// make sure we roll selection off the toplevel toolstrip.
if (activeToolStripDropDown.OwnerItem != null) {
activeToolStripDropDown.OwnerItem.Unselect();
}
}
}
// fire a timer event to ensure we have a message in the queue every 500ms
private void ProcessMessages(bool process) {
if (process) {
if (_ensureMessageProcessingTimer == null) {
_ensureMessageProcessingTimer = new System.Windows.Forms.Timer();
}
_ensureMessageProcessingTimer.Interval = MESSAGE_PROCESSING_INTERVAL;
_ensureMessageProcessingTimer.Enabled = true;
}
else if (_ensureMessageProcessingTimer != null) {
_ensureMessageProcessingTimer.Enabled = false;
_ensureMessageProcessingTimer.Dispose();
_ensureMessageProcessingTimer = null;
}
}
private void ProcessMouseButtonPressed(IntPtr hwndMouseMessageIsFrom, int x, int y) {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter.ProcessMouseButtonPressed] Found a mouse down.");
int countDropDowns = _inputFilterQueue.Count;
for (int i = 0; i < countDropDowns; i++) {
ToolStrip activeToolStrip = GetActiveToolStripInternal();
if (activeToolStrip != null) {
NativeMethods.POINT pt = new NativeMethods.POINT();
pt.x = x;
pt.y = y;
UnsafeNativeMethods.MapWindowPoints(new HandleRef(activeToolStrip, hwndMouseMessageIsFrom), new HandleRef(activeToolStrip, activeToolStrip.Handle), pt, 1);
if (!activeToolStrip.ClientRectangle.Contains(pt.x, pt.y)) {
ToolStripDropDown activeToolStripDropDown = activeToolStrip as ToolStripDropDown;
if (activeToolStripDropDown != null) {
if (!(activeToolStripDropDown.OwnerToolStrip != null
&& activeToolStripDropDown.OwnerToolStrip.Handle == hwndMouseMessageIsFrom
&& activeToolStripDropDown.OwnerDropDownItem != null
&& activeToolStripDropDown.OwnerDropDownItem.DropDownButtonArea.Contains(x, y))) {
// the owner item should handle closing the dropdown
// this allows code such as if (DropDown.Visible) { Hide, Show } etc.
CloseActiveDropDown(activeToolStripDropDown, ToolStripDropDownCloseReason.AppClicked);
}
}
else {
// make sure we clear the selection.
activeToolStrip.NotifySelectionChange(/*selectedItem=*/null);
// we're a toplevel toolstrip and we've clicked somewhere else.
// Exit menu mode
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter.ProcessMouseButtonPressed] Calling exit because we're a toplevel toolstrip and we've clicked somewhere else.");
ExitMenuModeCore();
}
}
else {
// we've found a dropdown that intersects with the mouse message
break;
}
}
else {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter.ProcessMouseButtonPressed] active toolstrip is null.");
break;
}
}
}
private bool ProcessActivationChange() {
int countDropDowns = _inputFilterQueue.Count;
for (int i = 0; i < countDropDowns; i++) {
ToolStripDropDown activeDropDown = this.GetActiveToolStripInternal() as ToolStripDropDown;
if (activeDropDown != null && activeDropDown.AutoClose) {
activeDropDown.Visible = false;
}
}
// if (_inputFilterQueue.Count == 0) {
ExitMenuModeCore();
return true;
//}
//return false;
}
internal static void SetActiveToolStrip(ToolStrip toolStrip, bool menuKeyPressed) {
if (!InMenuMode && menuKeyPressed) {
Instance.ShowUnderlines = true;
}
Instance.SetActiveToolStripCore(toolStrip);
}
internal static void SetActiveToolStrip(ToolStrip toolStrip) {
Instance.SetActiveToolStripCore(toolStrip);
}
private void SetActiveToolStripCore(ToolStrip toolStrip) {
if (toolStrip == null) {
return;
}
if (toolStrip.IsDropDown) {
// for something that never closes, dont use menu mode.
ToolStripDropDown dropDown = toolStrip as ToolStripDropDown;
if (dropDown.AutoClose == false) {
// store off the current active hwnd
IntPtr hwndActive = UnsafeNativeMethods.GetActiveWindow();
if (hwndActive != IntPtr.Zero) {
ActiveHwndInternal = new HandleRef(this, hwndActive);
}
// dont actually enter menu mode...
return;
}
}
toolStrip.KeyboardActive = true;
if (_inputFilterQueue == null) {
// use list because we want to be able to remove at any point
_inputFilterQueue = new List<ToolStrip>();
}
else {
ToolStrip currentActiveToolStrip = GetActiveToolStripInternal();
// toolstrip dropdowns push/pull their activation based on visibility.
// we have to account for the toolstrips that arent dropdowns
if (currentActiveToolStrip != null) {
if (!currentActiveToolStrip.IsDropDown) {
_inputFilterQueue.Remove(currentActiveToolStrip);
}
else if ((toolStrip.IsDropDown)
&& (ToolStripDropDown.GetFirstDropDown(toolStrip)
!= ToolStripDropDown.GetFirstDropDown(currentActiveToolStrip))) {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter.SetActiveToolStripCore] Detected a new dropdown not in this chain opened, Dismissing everything in the old chain. ");
_inputFilterQueue.Remove(currentActiveToolStrip);
ToolStripDropDown currentActiveToolStripDropDown = currentActiveToolStrip as ToolStripDropDown;
currentActiveToolStripDropDown.DismissAll();
}
}
}
// reset the toplevel toolstrip
_toplevelToolStrip = null;
if (!_inputFilterQueue.Contains(toolStrip))
_inputFilterQueue.Add(toolStrip);
if (!InMenuMode && _inputFilterQueue.Count > 0) {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter.SetActiveToolStripCore] Setting " + WindowsFormsUtils.GetControlInformation(toolStrip.Handle) + " active.");
EnterMenuModeCore();
}
// hide the caret if we're showing a toolstrip dropdown
if (!_caretHidden && toolStrip.IsDropDown && InMenuMode) {
_caretHidden = true;
SafeNativeMethods.HideCaret(NativeMethods.NullHandleRef);
}
}
internal static void SuspendMenuMode() {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter] SuspendMenuMode");
Instance._suspendMenuMode = true;
}
internal static void ResumeMenuMode() {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter] ResumeMenuMode");
Instance._suspendMenuMode = false;
}
internal static void RemoveActiveToolStrip(ToolStrip toolStrip) {
Instance.RemoveActiveToolStripCore(toolStrip);
}
private void RemoveActiveToolStripCore(ToolStrip toolStrip) {
// precautionary - remove the active toplevel toolstrip.
_toplevelToolStrip = null;
if (_inputFilterQueue != null) {
_inputFilterQueue.Remove(toolStrip);
}
}
private static bool IsChildOrSameWindow(HandleRef hwndParent, HandleRef hwndChild) {
if (hwndParent.Handle == hwndChild.Handle) {
return true;
}
if (UnsafeNativeMethods.IsChild(hwndParent, hwndChild)) {
return true;
}
return false;
}
private static bool IsKeyOrMouseMessage(Message m) {
bool filterMessage = false;
if (m.Msg >= NativeMethods.WM_MOUSEFIRST && m.Msg <= NativeMethods.WM_MOUSELAST) {
filterMessage = true;
}
else if (m.Msg >= NativeMethods.WM_NCLBUTTONDOWN && m.Msg <= NativeMethods.WM_NCMBUTTONDBLCLK) {
filterMessage = true;
}
else if (m.Msg >= NativeMethods.WM_KEYFIRST && m.Msg <= NativeMethods.WM_KEYLAST) {
filterMessage = true;
}
return filterMessage;
}
public bool PreFilterMessage(ref Message m) {
#if DEBUG
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose && _justEnteredMenuMode, "[ModalMenuFilter.PreFilterMessage] MenuMode MessageFilter installed and working.");
_justEnteredMenuMode = false;
#endif
if (_suspendMenuMode) {
return false;
}
ToolStrip activeToolStrip = GetActiveToolStrip();
if (activeToolStrip == null) {
return false;
}
if (activeToolStrip.IsDisposed) {
RemoveActiveToolStripCore(activeToolStrip);
return false;
}
HandleRef hwndActiveToolStrip = new HandleRef(activeToolStrip, activeToolStrip.Handle);
HandleRef hwndCurrentActiveWindow = new HandleRef(null, UnsafeNativeMethods.GetActiveWindow());
// if the active window has changed...
if (hwndCurrentActiveWindow.Handle != _lastActiveWindow.Handle) {
// if another window has gotten activation - we should dismiss.
if (hwndCurrentActiveWindow.Handle == IntPtr.Zero) {
// we dont know what it was cause it's on another thread or doesnt exist
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter.PreFilterMessage] Dismissing because: " + WindowsFormsUtils.GetControlInformation(hwndCurrentActiveWindow.Handle) + " has gotten activation. ");
ProcessActivationChange();
}
else if (!(Control.FromChildHandleInternal(hwndCurrentActiveWindow.Handle) is ToolStripDropDown) // its NOT a dropdown
&& !IsChildOrSameWindow(hwndCurrentActiveWindow, hwndActiveToolStrip) // and NOT a child of the active toolstrip
&& !IsChildOrSameWindow(hwndCurrentActiveWindow, ActiveHwnd)) { // and NOT a child of the active hwnd
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter.PreFilterMessage] Calling ProcessActivationChange because: " + WindowsFormsUtils.GetControlInformation(hwndCurrentActiveWindow.Handle) + " has gotten activation. ");
ProcessActivationChange();
}
}
// store this off so we dont have to do activation processing next time
_lastActiveWindow = hwndCurrentActiveWindow;
// PERF: skip over things like PAINT...
if (!IsKeyOrMouseMessage(m)) {
return false;
}
DpiAwarenessContext context = CommonUnsafeNativeMethods.TryGetDpiAwarenessContextForWindow(m.HWnd);
using (DpiHelper.EnterDpiAwarenessScope(context)) {
switch (m.Msg) {
case NativeMethods.WM_MOUSEMOVE:
case NativeMethods.WM_NCMOUSEMOVE:
// Mouse move messages should be eaten if they arent for a dropdown.
// this prevents things like ToolTips and mouse over highlights from
// being processed.
Control control = Control.FromChildHandleInternal(m.HWnd);
if (control == null || !(control.TopLevelControlInternal is ToolStripDropDown)) {
// double check it's not a child control of the active toolstrip.
if (!IsChildOrSameWindow(hwndActiveToolStrip, new HandleRef(null, m.HWnd))) {
// it is NOT a child of the current active toolstrip.
ToolStrip toplevelToolStrip = GetCurrentToplevelToolStrip();
if (toplevelToolStrip != null
&& (IsChildOrSameWindow(new HandleRef(toplevelToolStrip, toplevelToolStrip.Handle),
new HandleRef(null, m.HWnd)))) {
// DONT EAT mouse message.
// The mouse message is from an HWND that is part of the toplevel toolstrip - let the mosue move through so
// when you have something like the file menu open and mouse over the edit menu
// the file menu will dismiss.
return false;
}
else if (!IsChildOrSameWindow(ActiveHwnd, new HandleRef(null, m.HWnd))) {
// DONT EAT mouse message.
// the mouse message is from another toplevel HWND.
return false;
}
// EAT mouse message
// the HWND is
// not part of the active toolstrip
// not the toplevel toolstrip (e.g. MenuStrip).
// not parented to the toplevel toolstrip (e.g a combo box on a menu strip).
return true;
}
}
break;
case NativeMethods.WM_LBUTTONDOWN:
case NativeMethods.WM_RBUTTONDOWN:
case NativeMethods.WM_MBUTTONDOWN:
//
// When a mouse button is pressed, we should determine if it is within the client coordinates
// of the active dropdown. If not, we should dismiss it.
//
ProcessMouseButtonPressed(m.HWnd,
/*x=*/NativeMethods.Util.SignedLOWORD(m.LParam),
/*y=*/NativeMethods.Util.SignedHIWORD(m.LParam));
break;
case NativeMethods.WM_NCLBUTTONDOWN:
case NativeMethods.WM_NCRBUTTONDOWN:
case NativeMethods.WM_NCMBUTTONDOWN:
//
// When a mouse button is pressed, we should determine if it is within the client coordinates
// of the active dropdown. If not, we should dismiss it.
//
ProcessMouseButtonPressed(/*nc messages are in screen coords*/IntPtr.Zero,
/*x=*/NativeMethods.Util.SignedLOWORD(m.LParam),
/*y=*/NativeMethods.Util.SignedHIWORD(m.LParam));
break;
case NativeMethods.WM_KEYDOWN:
case NativeMethods.WM_KEYUP:
case NativeMethods.WM_CHAR:
case NativeMethods.WM_DEADCHAR:
case NativeMethods.WM_SYSKEYDOWN:
case NativeMethods.WM_SYSKEYUP:
case NativeMethods.WM_SYSCHAR:
case NativeMethods.WM_SYSDEADCHAR:
if (!activeToolStrip.ContainsFocus) {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter.PreFilterMessage] MODIFYING Keyboard message " + m.ToString());
// route all keyboard messages to the active dropdown.
m.HWnd = activeToolStrip.Handle;
}
else {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ModalMenuFilter.PreFilterMessage] got Keyboard message " + m.ToString());
}
break;
}
}
return false;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable")] // since this has the lifetime of the thread, theres no great way to dispose.
private class HostedWindowsFormsMessageHook {
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private IntPtr messageHookHandle = IntPtr.Zero;
private bool isHooked = false; //VSWHIDBEY # 474112
private NativeMethods.HookProc hookProc;
public HostedWindowsFormsMessageHook() {
#if DEBUG
try {
callingStack = Environment.StackTrace;
}
catch (SecurityException) {
}
#endif
}
#if DEBUG
string callingStack;
~HostedWindowsFormsMessageHook() {
Debug.Assert(messageHookHandle == IntPtr.Zero, "Finalizing an active mouse hook. This will crash the process. Calling stack: " + callingStack);
}
#endif
public bool HookMessages {
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get {
return messageHookHandle != IntPtr.Zero;
}
set {
if (value) {
InstallMessageHook();
}
else {
UninstallMessageHook();
}
}
}
private void InstallMessageHook() {
lock (this) {
if (messageHookHandle != IntPtr.Zero) {
return;
}
hookProc = new NativeMethods.HookProc(this.MessageHookProc);
messageHookHandle = UnsafeNativeMethods.SetWindowsHookEx(NativeMethods.WH_GETMESSAGE,
hookProc,
new HandleRef(null, IntPtr.Zero),
SafeNativeMethods.GetCurrentThreadId());
if (messageHookHandle != IntPtr.Zero) {
isHooked = true;
}
Debug.Assert(messageHookHandle != IntPtr.Zero, "Failed to install mouse hook");
}
}
private unsafe IntPtr MessageHookProc(int nCode, IntPtr wparam, IntPtr lparam) {
if (nCode == NativeMethods.HC_ACTION) {
if (isHooked && (int)wparam == NativeMethods.PM_REMOVE /*only process GetMessage, not PeekMessage*/) {
// only process messages we've pulled off the queue
NativeMethods.MSG* msg = (NativeMethods.MSG*)lparam;
if (msg != null) {
//Debug.WriteLine("Got " + Message.Create(msg->hwnd, msg->message, wparam, lparam).ToString());
// call pretranslate on the message - this should execute
// the message filters and preprocess message.
if (Application.ThreadContext.FromCurrent().PreTranslateMessage(ref *msg)) {
msg->message = NativeMethods.WM_NULL;
}
}
}
}
return UnsafeNativeMethods.CallNextHookEx(new HandleRef(this, messageHookHandle), nCode, wparam, lparam);
}
private void UninstallMessageHook() {
lock (this) {
if (messageHookHandle != IntPtr.Zero) {
UnsafeNativeMethods.UnhookWindowsHookEx(new HandleRef(this, messageHookHandle));
hookProc = null;
messageHookHandle = IntPtr.Zero;
isHooked = false;
}
}
}
}
}
internal static bool ShowMenuFocusCues {
get {
if (!DisplayInformation.MenuAccessKeysUnderlined) {
return ModalMenuFilter.Instance.ShowUnderlines;
}
return true;
}
}
/// <devdoc> determines if the key combination is valid for a shortcut.
/// must have a modifier key + a regular key.
/// </devdoc>
public static bool IsValidShortcut(Keys shortcut) {
// should have a key and one or more modifiers.
Keys keyCode = (Keys)(shortcut & Keys.KeyCode);
Keys modifiers = (Keys)(shortcut & Keys.Modifiers);
if (shortcut == Keys.None) {
return false;
}
else if ((keyCode == Keys.Delete) || (keyCode == Keys.Insert)) {
return true;
}
else if (((int)keyCode >= (int)Keys.F1) && ((int)keyCode <= (int)Keys.F24)) {
// function keys by themselves are valid
return true;
}
else if ((keyCode != Keys.None) && (modifiers != Keys.None)) {
switch (keyCode) {
case Keys.Menu:
case Keys.ControlKey:
case Keys.ShiftKey:
// shift, control and alt arent valid on their own.
return false;
default:
if (modifiers == Keys.Shift) {
// shift + somekey isnt a valid modifier either
return false;
}
return true;
}
}
// has to have a valid keycode and valid modifier.
return false;
}
internal static bool IsMenuKey(Keys keyData) {
Keys keyCode = keyData & Keys.KeyCode;
return (Keys.Menu == keyCode || Keys.F10 == keyCode);
}
public static bool IsShortcutDefined(Keys shortcut) {
for (int i = 0; i < ToolStrips.Count; i++) {
ToolStrip t = ToolStrips[i] as ToolStrip;
if ((t != null) && t.Shortcuts.Contains(shortcut)) {
return true;
}
}
return false;
}
/// <devdoc> this function is called for toplevel controls to process shortcuts.
/// this function should be called from the topmost container control only.
/// </devdoc>
internal static bool ProcessCmdKey(ref Message m, Keys keyData) {
Debug.WriteLineIf(Control.ControlKeyboardRouting.TraceVerbose, "ToolStripManager.ProcessCmdKey - processing: [" + keyData.ToString() + "]");
if (ToolStripManager.IsValidShortcut(keyData)) {
// if we're at the toplevel, check the toolstrips for matching shortcuts.
// Win32 menus are handled in Form.ProcessCmdKey, but we cant guarantee that
// toolstrips will be hosted in a form. ToolStrips have a hash of shortcuts
// per container, so this should hopefully be a quick search.
Debug.WriteLineIf(Control.ControlKeyboardRouting.TraceVerbose, "ToolStripManager.ProcessCmdKey - IsValidShortcut: [" + keyData.ToString() + "]");
return ToolStripManager.ProcessShortcut(ref m, keyData);
}
if (m.Msg == NativeMethods.WM_SYSKEYDOWN) {
Debug.WriteLineIf(Control.ControlKeyboardRouting.TraceVerbose, "ToolStripManager.ProcessCmdKey - Checking if it's a menu key: [" + keyData.ToString() + "]");
ToolStripManager.ModalMenuFilter.ProcessMenuKeyDown(ref m);
}
return false;
}
/// <devdoc> we're halfway to an accellerator table system here.
/// each toolstrip maintains a hash of the current shortcuts its using.
/// this way the search only takes O(number of toolstrips in the thread)
/// ToolStripMenuItem pushes itself into this table as the owner is set or the shortcut changes.
/// </devdoc>
internal static bool ProcessShortcut(ref Message m, Keys shortcut) {
if (!IsThreadUsingToolStrips()) {
return false;
}
Control activeControl = Control.FromChildHandleInternal(m.HWnd);
Control activeControlInChain = activeControl;
if (activeControlInChain != null && IsValidShortcut(shortcut)) {
Debug.WriteLineIf(Control.ControlKeyboardRouting.TraceVerbose, "ToolStripManager.ProcessShortcut - processing: [" + shortcut.ToString() + "]");
// start from the focused control and work your way up the parent chain
do {
// check the context menu strip first.
if (activeControlInChain.ContextMenuStrip != null) {
if (activeControlInChain.ContextMenuStrip.Shortcuts.ContainsKey(shortcut)) {
ToolStripMenuItem item = activeControlInChain.ContextMenuStrip.Shortcuts[shortcut] as ToolStripMenuItem;
if (item.ProcessCmdKey(ref m, shortcut)) {
Debug.WriteLineIf(Control.ControlKeyboardRouting.TraceVerbose, "ToolStripManager.ProcessShortcut - found item on context menu: [" + item.ToString() + "]");
return true;
}
}
}
activeControlInChain = activeControlInChain.ParentInternal;
} while (activeControlInChain != null);
if (activeControlInChain != null) {
// the keystroke may applies to one of our parents...
// a WM_CONTEXTMENU message bubbles up to the parent control
activeControl = activeControlInChain;
}
bool retVal = false;
bool needsPrune = false;
// now search the toolstrips
for (int i = 0; i < ToolStrips.Count; i++) {
ToolStrip toolStrip = ToolStrips[i] as ToolStrip;
bool isAssociatedContextMenu = false;
bool isDoublyAssignedContextMenuStrip = false;
if (toolStrip == null) {
// consider prune tree...
needsPrune = true;
continue;
}
else if (activeControl != null && toolStrip == activeControl.ContextMenuStrip) {
continue;
}
else if (toolStrip.Shortcuts.ContainsKey(shortcut)) {
if (toolStrip.IsDropDown) {
// we dont want to process someone else's context menu (e.g. button1 and button2 have context menus)
// button2's context menu should not be processed if button1 is the one we're processing.
ToolStripDropDown dropDown = toolStrip as ToolStripDropDown;
ContextMenuStrip toplevelContextMenu = dropDown.GetFirstDropDown() as ContextMenuStrip;
// VSWhidbey 433886: if a context menu is re-used between the main menu and the
// and some other control's context menu, we should go ahead and evaluate it.
if (toplevelContextMenu != null) {
isDoublyAssignedContextMenuStrip = toplevelContextMenu.IsAssignedToDropDownItem;
if (!isDoublyAssignedContextMenuStrip) {
if (toplevelContextMenu != activeControl.ContextMenuStrip) {
// the toplevel context menu is NOT the same as the active control's context menu.
continue;
}
else {
isAssociatedContextMenu = true;
}
}
}
// else it's not a child of a context menu
}
bool rootWindowsMatch = false;
if (!isAssociatedContextMenu) {
// make sure that were processing shortcuts for the correct window.
// since the shortcut lookup is faster than this check we've postponed this to the last
// possible moment.
ToolStrip topMostToolStrip = toolStrip.GetToplevelOwnerToolStrip();
if (topMostToolStrip != null && activeControl != null) {
HandleRef rootWindowOfToolStrip = WindowsFormsUtils.GetRootHWnd(topMostToolStrip);
HandleRef rootWindowOfControl = WindowsFormsUtils.GetRootHWnd(activeControl);
rootWindowsMatch = (rootWindowOfToolStrip.Handle == rootWindowOfControl.Handle);
if (rootWindowsMatch) {
// Double check this is not an MDIContainer type situation...
Form mainForm = Control.FromHandleInternal(rootWindowOfControl.Handle) as Form;
if (mainForm != null && mainForm.IsMdiContainer) {
Form toolStripForm = topMostToolStrip.FindFormInternal();
if (toolStripForm != mainForm && toolStripForm != null) {
// VSWhidbey 530569
// we should only process shortcuts of the ActiveMDIChild or the Main Form.
rootWindowsMatch = (toolStripForm == mainForm.ActiveMdiChildInternal);
}
}
}
}
}
if (isAssociatedContextMenu || rootWindowsMatch || isDoublyAssignedContextMenuStrip) {
ToolStripMenuItem item = toolStrip.Shortcuts[shortcut] as ToolStripMenuItem;
if (item != null) {
if (item.ProcessCmdKey(ref m, shortcut)) {
Debug.WriteLineIf(Control.ControlKeyboardRouting.TraceVerbose, "ToolStripManager.ProcessShortcut - found item on toolstrip: [" + item.ToString() + "]");
retVal = true;
break;
}
}
}
}
}
if (needsPrune) {
PruneToolStripList();
}
return retVal;
}
return false;
}
/// <devdoc> this function handles when Alt is pressed.
/// if it finds a menustrip to select, it returns true,
/// if it doesnt it returns false.
/// if it finds a win32 menu is already associated with the control it bails, returning false.
/// </devdoc>
internal static bool ProcessMenuKey(ref Message m) {
Debug.WriteLineIf(Control.ControlKeyboardRouting.TraceVerbose, "ToolStripManager.ProcessMenuKey: [" + m.ToString() + "]");
if (!IsThreadUsingToolStrips()) {
return false;
}
// recievedMenuKeyUp = true;
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ProcessMenuKey] Determining whether we should send focus to MenuStrip");
Keys keyData = (Keys)(int)m.LParam;
// search for our menu to work with
Control intendedControl = Control.FromHandleInternal(m.HWnd);
Control toplevelControl = null;
MenuStrip menuStripToActivate = null;
if (intendedControl != null) {
// search for a menustrip to select.
toplevelControl = intendedControl.TopLevelControlInternal;
if (toplevelControl != null) {
IntPtr hMenu = UnsafeNativeMethods.GetMenu(new HandleRef(toplevelControl, toplevelControl.Handle));
if (hMenu == IntPtr.Zero) {
// only activate the menu if there's no win32 menu. Win32 menus trump menustrips.
menuStripToActivate = GetMainMenuStrip(toplevelControl);
}
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, String.Format(CultureInfo.CurrentCulture, "[ProcessMenuKey] MenuStripToActivate is: {0}", menuStripToActivate));
}
}
// the data that comes into the LParam is the ASCII code, not the VK_* code.
// we need to compare against char instead.
if ((char)keyData == ' ') { // dont process system menu
ModalMenuFilter.MenuKeyToggle = false;
}
else if ((char)keyData == '-') {
// deal with MDI system menu
Form mdiChild = toplevelControl as Form;
if (mdiChild != null && mdiChild.IsMdiChild) {
if (mdiChild.WindowState == FormWindowState.Maximized) {
ModalMenuFilter.MenuKeyToggle = false;
}
}
}
else {
// this is the same as Control.ModifierKeys - but we save two p/invokes.
if (UnsafeNativeMethods.GetKeyState((int)Keys.ShiftKey) < 0 && (keyData == Keys.None)) {
// VSWhidbey 381933 if it's Shift+F10 and we're already InMenuMode, then we
// need to cancel this message, otherwise we'll enter the native modal menu loop.
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ProcessMenuKey] DETECTED SHIFT+F10" + keyData.ToString());
return ToolStripManager.ModalMenuFilter.InMenuMode;
}
else {
if (menuStripToActivate != null && !ModalMenuFilter.MenuKeyToggle) {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ProcessMenuKey] attempting to set focus to menustrip");
// if we've alt-tabbed away dont snap/restore focus.
HandleRef topmostParentOfMenu = WindowsFormsUtils.GetRootHWnd(menuStripToActivate);
IntPtr foregroundWindow = UnsafeNativeMethods.GetForegroundWindow();
if (topmostParentOfMenu.Handle == foregroundWindow) {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ProcessMenuKey] ToolStripManager call MenuStrip.OnMenuKey");
return menuStripToActivate.OnMenuKey();
}
}
else if (menuStripToActivate != null) {
Debug.WriteLineIf(ToolStrip.SnapFocusDebug.TraceVerbose, "[ProcessMenuKey] Resetting MenuKeyToggle");
ModalMenuFilter.MenuKeyToggle = false;
return true;
}
}
}
return false;
}
internal static MenuStrip GetMainMenuStrip(Control control) {
if (control == null) {
Debug.Fail("why are we passing null to GetMainMenuStrip?");
return null;
}
// look for a particular main menu strip to be set.
Form mainForm = control.FindFormInternal();
if (mainForm != null && mainForm.MainMenuStrip != null) {
return mainForm.MainMenuStrip;
}
// if not found go through the entire collection.
return GetFirstMenuStripRecursive(control.Controls);
}
private static MenuStrip GetFirstMenuStripRecursive(Control.ControlCollection controlsToLookIn) {
try {
// Perform breadth first search - as it's likely people will want controls belonging
// to the same parent close to each other.
for (int i = 0; i < controlsToLookIn.Count; i++) {
if (controlsToLookIn[i] == null) {
continue;
}
if (controlsToLookIn[i] is MenuStrip) {
return controlsToLookIn[i] as MenuStrip;
}
}
// Recursive search for controls in child collections.
for (int i = 0; i < controlsToLookIn.Count; i++) {
if (controlsToLookIn[i] == null) {
continue;
}
if ((controlsToLookIn[i].Controls != null) && controlsToLookIn[i].Controls.Count > 0) {
// if it has a valid child collecion, append those results to our collection
MenuStrip menuStrip = GetFirstMenuStripRecursive(controlsToLookIn[i].Controls);
if (menuStrip != null) {
return menuStrip;
}
}
}
}
catch (Exception e) {
// VSWHIDBEY 80122 make sure we deal with non-critical failures gracefully.
if (ClientUtils.IsCriticalException(e)) {
throw;
}
}
return null;
}
#endregion MenuKeyAndShortcutProcessing
///
/// ToolStripManager MenuMerging functions
///
#region MenuMerging
private static ToolStripItem FindMatch(ToolStripItem source, ToolStripItemCollection destinationItems) {
// based on MergeAction:
// Append, return the last sibling
ToolStripItem result = null;
if (source != null) {
for (int i = 0; i < destinationItems.Count; i++) {
ToolStripItem candidateItem = destinationItems[i];
// using SafeCompareKeys so we use the same heuristics as keyed collections.
if (WindowsFormsUtils.SafeCompareStrings(source.Text, candidateItem.Text, true)) {
result = candidateItem;
break; // we found it
}
}
if (result == null && source.MergeIndex > -1 && source.MergeIndex < destinationItems.Count) {
result = destinationItems[source.MergeIndex];
}
}
return result;
}
/// <devdoc>
/// </devdoc>
internal static ArrayList FindMergeableToolStrips(ContainerControl container) {
ArrayList result = new ArrayList();
if (container != null) {
for (int i = 0; i < ToolStrips.Count; i++) {
ToolStrip candidateTS = (ToolStrip)ToolStrips[i];
//if(candidateTS != null) {
// Debug.WriteLine("candidate TS: " + candidateTS.Name + " | " + candidateTS.AllowMerge + " | " + (candidateTS.Parent == null ? "null" : candidateTS.Parent.Name) +" | " + container.Name);
//}
//Debug.WriteLine(candidateTS == null ? "null" : "not null");
if (candidateTS != null && candidateTS.AllowMerge && container == candidateTS.FindFormInternal()) {
//Debug.WriteLine("adding");
result.Add(candidateTS);
}
}
}
result.Sort(new ToolStripCustomIComparer()); //we sort them from more specific to less specific
return result;
}
private static bool IsSpecialMDIStrip(ToolStrip toolStrip) {
return (toolStrip is MdiControlStrip || toolStrip is MdiWindowListStrip);
}
/// <include file='doc\ToolStripManager.uex' path='docs/doc[@for="ToolStripManager.Merge"]/*' />
/// <devdoc>
/// merge two toolstrips
/// </devdoc>
public static bool Merge(ToolStrip sourceToolStrip, ToolStrip targetToolStrip) {
// check arguments
if (sourceToolStrip == null) {
throw new ArgumentNullException("sourceToolStrip");
}
if (targetToolStrip == null) {
throw new ArgumentNullException("targetToolStrip");
}
if (targetToolStrip == sourceToolStrip) {
throw new ArgumentException(SR.GetString(SR.ToolStripMergeImpossibleIdentical));
}
// we only do this if the source and target toolstrips are the same
bool canMerge = IsSpecialMDIStrip(sourceToolStrip);
canMerge = (canMerge || (sourceToolStrip.AllowMerge &&
targetToolStrip.AllowMerge &&
(sourceToolStrip.GetType().IsAssignableFrom(targetToolStrip.GetType()) || targetToolStrip.GetType().IsAssignableFrom(sourceToolStrip.GetType()))
)
);
MergeHistory mergeHistory = null;
if (canMerge) {
//Debug.WriteLine("Begin merge between src: " + sourceToolStrip.Name + " and target: " + targetToolStrip.Name);
Debug.Indent();
mergeHistory = new MergeHistory(sourceToolStrip);
int originalCount = sourceToolStrip.Items.Count;
if (originalCount > 0) {
sourceToolStrip.SuspendLayout();
targetToolStrip.SuspendLayout();
try {
int lastCount = originalCount;
// 2. do the actual merging logic
for (int i = 0, itemToLookAt = 0; i < originalCount; i++) {
ToolStripItem item = sourceToolStrip.Items[itemToLookAt];
//Debug.WriteLine("doing the recursive merge for item " + item.Text);
MergeRecursive(item, targetToolStrip.Items, mergeHistory.MergeHistoryItemsStack);
int numberOfItemsMerged = lastCount - sourceToolStrip.Items.Count;
itemToLookAt = (numberOfItemsMerged > 0) ? itemToLookAt : itemToLookAt + 1;
lastCount = sourceToolStrip.Items.Count;
}
}
finally {
Debug.Unindent();
sourceToolStrip.ResumeLayout();
targetToolStrip.ResumeLayout();
}
//Debug.WriteLine("pusing mergehistory for toolstrip " + sourceToolStrip.Name + " in target toolstrip MergeHistoryStack property");
if (mergeHistory.MergeHistoryItemsStack.Count > 0) {
// only push this on the stack if we actually did something
targetToolStrip.MergeHistoryStack.Push(mergeHistory);
}
}
}
bool result = false;
if (mergeHistory != null && mergeHistory.MergeHistoryItemsStack.Count > 0) {
result = true; // we did merge something
}
return result;
}
private static void MergeRecursive(ToolStripItem source, ToolStripItemCollection destinationItems, Stack<MergeHistoryItem> history) {
Debug.Indent();
MergeHistoryItem maction;
switch (source.MergeAction) {
case MergeAction.MatchOnly:
case MergeAction.Replace:
case MergeAction.Remove:
ToolStripItem item = FindMatch(source, destinationItems);
if (item != null) {
switch (source.MergeAction) {
case MergeAction.MatchOnly:
//Debug.WriteLine("matchonly");
ToolStripDropDownItem tsddownDest = item as ToolStripDropDownItem;
ToolStripDropDownItem tsddownSrc = source as ToolStripDropDownItem;
if (tsddownDest != null && tsddownSrc != null && tsddownSrc.DropDownItems.Count != 0) {
int originalCount = tsddownSrc.DropDownItems.Count;
if (originalCount > 0) {
int lastCount = originalCount;
tsddownSrc.DropDown.SuspendLayout();
try {
// the act of walking through this collection removes items from
// the dropdown.
for (int i = 0, itemToLookAt = 0; i < originalCount; i++) {
MergeRecursive(tsddownSrc.DropDownItems[itemToLookAt], tsddownDest.DropDownItems, history);
int numberOfItemsMerged = lastCount - tsddownSrc.DropDownItems.Count;
itemToLookAt = (numberOfItemsMerged > 0) ? itemToLookAt : itemToLookAt + 1;
lastCount = tsddownSrc.DropDownItems.Count;
}
}
finally {
tsddownSrc.DropDown.ResumeLayout();
}
}
}
break;
case MergeAction.Replace:
case MergeAction.Remove:
//Debug.WriteLine("remove");
maction = new MergeHistoryItem(MergeAction.Insert);
maction.TargetItem = item;
int indexOfDestinationItem = destinationItems.IndexOf(item);
destinationItems.RemoveAt(indexOfDestinationItem);
maction.Index = indexOfDestinationItem;
maction.IndexCollection = destinationItems;
maction.TargetItem = item;
history.Push(maction);
//Debug.WriteLine(maction.ToString());
if (source.MergeAction == MergeAction.Replace) {
//Debug.WriteLine("replace");
//ToolStripItem clonedItem = source.Clone();
maction = new MergeHistoryItem(MergeAction.Remove);
maction.PreviousIndexCollection = source.Owner.Items;
maction.PreviousIndex = maction.PreviousIndexCollection.IndexOf(source);
maction.TargetItem = source;
destinationItems.Insert(indexOfDestinationItem, source);
maction.Index = indexOfDestinationItem;
maction.IndexCollection = destinationItems;
history.Push(maction);
//Debug.WriteLine(maction.ToString());
}
break;
}
}
break;
case MergeAction.Insert:
if (source.MergeIndex > -1) {
maction = new MergeHistoryItem(MergeAction.Remove);
maction.PreviousIndexCollection = source.Owner.Items;
maction.PreviousIndex = maction.PreviousIndexCollection.IndexOf(source);
maction.TargetItem = source;
int insertIndex = Math.Min(destinationItems.Count, source.MergeIndex);
destinationItems.Insert(insertIndex, source);
maction.IndexCollection = destinationItems;
maction.Index = insertIndex;
history.Push(maction);
//Debug.WriteLine(maction.ToString());
}
break;
case MergeAction.Append:
maction = new MergeHistoryItem(MergeAction.Remove);
maction.PreviousIndexCollection = source.Owner.Items;
maction.PreviousIndex = maction.PreviousIndexCollection.IndexOf(source);
maction.TargetItem = source;
int index = destinationItems.Add(source);
maction.Index = index;
maction.IndexCollection = destinationItems;
history.Push(maction);
//Debug.WriteLine(maction.ToString());
break;
}
Debug.Unindent();
}
/// <include file='doc\ToolStripManager.uex' path='docs/doc[@for="ToolStripManager.Merge2"]/*' />
/// <devdoc>
/// merge two toolstrips
/// </devdoc>
public static bool Merge(ToolStrip sourceToolStrip, string targetName) {
if (sourceToolStrip == null) {
throw new ArgumentNullException("sourceToolStrip");
}
if (targetName == null) {
throw new ArgumentNullException("targetName");
}
ToolStrip target = FindToolStrip(targetName);
if (target == null) {
return false;
}
else {
return Merge(sourceToolStrip, target);
}
}
/// <devdoc>
/// doesn't do a null check on source... if it's null we unmerge everything
/// </devdoc>
internal static bool RevertMergeInternal(ToolStrip targetToolStrip, ToolStrip sourceToolStrip, bool revertMDIControls) {
bool result = false;
if (targetToolStrip == null) {
throw new ArgumentNullException("targetToolStrip");
}
if (targetToolStrip == sourceToolStrip) {
throw new ArgumentException(SR.GetString(SR.ToolStripMergeImpossibleIdentical));
}
bool foundToolStrip = false;
if (sourceToolStrip != null) {
// we have a specific toolstrip to pull out.
// make sure the sourceToolStrip is even merged into the targetToolStrip
foreach (MergeHistory history in targetToolStrip.MergeHistoryStack) {
foundToolStrip = (history.MergedToolStrip == sourceToolStrip);
if (foundToolStrip) {
break;
}
}
// PERF: if we dont have the toolstrip in our merge history, bail.
if (!foundToolStrip) {
//Debug.WriteLine("source toolstrip not contained within target " + history.MergedToolStrip.Name);
return false;
}
}
if( sourceToolStrip != null ) {
sourceToolStrip.SuspendLayout();
}
targetToolStrip.SuspendLayout();
try {
//Debug.WriteLine("Reverting merge, playing back history for all merged toolstrip ");
Stack<ToolStrip> reApply = new Stack<ToolStrip>();
foundToolStrip = false;
while (targetToolStrip.MergeHistoryStack.Count > 0 && !foundToolStrip) {
result = true; // we unmerge something...
MergeHistory history = targetToolStrip.MergeHistoryStack.Pop();
if (history.MergedToolStrip == sourceToolStrip) {
foundToolStrip = true;
}
else if (!revertMDIControls && sourceToolStrip == null) {
// VSWhidbey 352431: Calling ToolStripManager.RevertMerge should not pull out MDIControlStrip && MDIWindowListStrip.
if (IsSpecialMDIStrip(history.MergedToolStrip)) {
reApply.Push(history.MergedToolStrip);
}
}
else {
reApply.Push(history.MergedToolStrip);
}
//Debug.WriteLine("unmerging " + history.MergedToolStrip.Name);
Debug.Indent();
while (history.MergeHistoryItemsStack.Count > 0) {
MergeHistoryItem historyItem = history.MergeHistoryItemsStack.Pop();
switch (historyItem.MergeAction) {
case MergeAction.Remove:
historyItem.IndexCollection.Remove(historyItem.TargetItem);
// put it back
historyItem.PreviousIndexCollection.Insert(Math.Min(historyItem.PreviousIndex, historyItem.PreviousIndexCollection.Count), historyItem.TargetItem);
break;
case MergeAction.Insert:
historyItem.IndexCollection.Insert(Math.Min(historyItem.Index, historyItem.IndexCollection.Count), historyItem.TargetItem);
// no need to put it back, inserting it in a new collection, moved it at the correct location
break;
}
}
Debug.Unindent();
}
// re-apply the merges of the toolstrips we had to unmerge first.
while (reApply.Count > 0) {
ToolStrip mergeAgain = reApply.Pop();
Merge(mergeAgain, targetToolStrip);
}
}
finally {
if( sourceToolStrip != null ) {
sourceToolStrip.ResumeLayout();
}
targetToolStrip.ResumeLayout();
}
return result;
//ToolStripMergeNode.SynchronizeFromToolStripMergeNode(targetToolStrip.Items, targetToolStrip.MergeItems);
}
/// <include file='doc\ToolStripManager.uex' path='docs/doc[@for="ToolStripManager.RevertMerge"]/*' />
/// <devdoc>
/// unmerge two toolstrips
/// </devdoc>
public static bool RevertMerge(ToolStrip targetToolStrip) {
return RevertMergeInternal(targetToolStrip, null, /*revertMDIControls*/false);
}
/// <include file='doc\ToolStripManager.uex' path='docs/doc[@for="ToolStripManager.RevertMerge"]/*' />
/// <devdoc>
/// unmerge two toolstrips
/// </devdoc>
public static bool RevertMerge(ToolStrip targetToolStrip, ToolStrip sourceToolStrip) {
if (sourceToolStrip == null) {
throw new ArgumentNullException("sourceToolStrip");
}
return RevertMergeInternal(targetToolStrip, sourceToolStrip, /*revertMDIControls*/false);
}
/// <include file='doc\ToolStripManager.uex' path='docs/doc[@for="ToolStripManager.RevertMerge2"]/*' />
/// <devdoc>
/// unmerge two toolstrips
/// </devdoc>
public static bool RevertMerge(string targetName) {
ToolStrip target = FindToolStrip(targetName);
if (target == null) {
return false;
}
else {
return RevertMerge(target);
}
}
#endregion MenuMerging
}
internal class ToolStripCustomIComparer : IComparer {
int IComparer.Compare(object x, object y) {
if (x.GetType() == y.GetType()) {
return 0; // same type
}
if (x.GetType().IsAssignableFrom(y.GetType())) {
return 1;
}
if (y.GetType().IsAssignableFrom(x.GetType())) {
return -1;
}
return 0; // not the same type, not in each other inheritance chain
}
}
internal class MergeHistory {
private Stack<MergeHistoryItem> mergeHistoryItemsStack;
private ToolStrip mergedToolStrip;
public MergeHistory(ToolStrip mergedToolStrip) {
this.mergedToolStrip = mergedToolStrip;
}
public Stack<MergeHistoryItem> MergeHistoryItemsStack {
get {
if (mergeHistoryItemsStack == null) {
mergeHistoryItemsStack = new Stack<MergeHistoryItem>();
}
return mergeHistoryItemsStack;
}
}
public ToolStrip MergedToolStrip {
get {
return mergedToolStrip;
}
}
}
internal class MergeHistoryItem {
private MergeAction mergeAction;
private ToolStripItem targetItem;
private int index = -1;
private int previousIndex = -1;
private ToolStripItemCollection previousIndexCollection;
private ToolStripItemCollection indexCollection;
public MergeHistoryItem(MergeAction mergeAction) {
this.mergeAction = mergeAction;
}
public MergeAction MergeAction {
get {
return mergeAction;
}
}
public ToolStripItem TargetItem {
get {
return targetItem;
}
set {
targetItem = value;
}
}
public int Index {
get {
return index;
}
set {
index = value;
}
}
public int PreviousIndex {
get {
return previousIndex;
}
set {
previousIndex = value;
}
}
public ToolStripItemCollection PreviousIndexCollection {
get {
return previousIndexCollection;
}
set {
previousIndexCollection = value;
}
}
public ToolStripItemCollection IndexCollection {
get {
return indexCollection;
}
set {
indexCollection = value;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
public override string ToString() {
return "MergeAction: " + mergeAction.ToString() + " | TargetItem: " + (TargetItem == null ? "null" : TargetItem.Text) + " Index: " + index.ToString(CultureInfo.CurrentCulture);
}
}
}
|