Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / whidbey / NetFXspW7 / ndp / fx / src / WinForms / Managed / System / WinForms / ToolStripManager.cs / 1 / ToolStripManager.cs
//------------------------------------------------------------------------------ //// Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- namespace System.Windows.Forms { using System; using System.Windows.Forms; using System.Windows.Forms.VisualStyles; using System.ComponentModel; using System.Collections; 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; ///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; // 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 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; 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 ClientUtils.WeakRefCollection ToolStrips { get { if (toolStripWeakArrayList == null) { toolStripWeakArrayList = new ClientUtils.WeakRefCollection(); } return toolStripWeakArrayList; } } /// Static events only!!! private static void AddEventHandler(int key, Delegate value) { lock (internalSyncObject) { if (staticEventHandlers == null) { staticEventHandlers = new Delegate[staticEventCount]; } staticEventHandlers[key] = Delegate.Combine(staticEventHandlers[key], value); } } ////// /// Find a toolstrip in the weak ref arraylist, return null if nothing was found /// [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; } ////// /// Find a toolstrip in the weak ref arraylist, return null if nothing was found /// [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) { 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(); } } ///removes dead entries from the toolstrip weak reference collection. 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); } } } } } ///static events only!!! 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; } } ////// the default renderer for the thread. When ToolStrip.RenderMode is set to manager - this /// is the property used. /// 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); } } } } //// 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. // // 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); } } ////// returns the default toolstrip RenderMode for the thread 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)); } } } ////// 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). /// 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 ////// Loads settings for the given Form using the form type's fullname as settings key. /// public static void LoadSettings(Form targetForm) { if (targetForm == null) { throw new ArgumentNullException("targetForm"); } LoadSettings(targetForm, targetForm.GetType().FullName); } ////// Loads settings for the given Form with the given settings key. /// 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(); } ////// Saves settings for the given form using the form type's fullname as settings key. /// public static void SaveSettings(Form sourceForm) { if (sourceForm == null) { throw new ArgumentNullException("sourceForm"); } SaveSettings(sourceForm, sourceForm.GetType().FullName); } ////// Saves settings for the given form with the given settings key. /// 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_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; #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; // fire timer messages to force our filter to get evaluated. ProcessMessages(true); } } 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); } } 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 (); } 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; } 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; } } /// determines if the key combination is valid for a shortcut. /// must have a modifier key + a regular key. /// 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; } ///this function is called for toplevel controls to process shortcuts. /// this function should be called from the topmost container control only. /// 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; } ///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. /// 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; } ///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. /// 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; } ////// 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); } ////// /// merge two toolstrips /// 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, Stackhistory) { 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(); } /// /// /// merge two toolstrips /// 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); } } ////// doesn't do a null check on source... if it's null we unmerge everything /// 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 "); StackreApply = new Stack (); 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); } /// /// /// unmerge two toolstrips /// public static bool RevertMerge(ToolStrip targetToolStrip) { return RevertMergeInternal(targetToolStrip, null, /*revertMDIControls*/false); } ////// /// unmerge two toolstrips /// public static bool RevertMerge(ToolStrip targetToolStrip, ToolStrip sourceToolStrip) { if (sourceToolStrip == null) { throw new ArgumentNullException("sourceToolStrip"); } return RevertMergeInternal(targetToolStrip, sourceToolStrip, /*revertMDIControls*/false); } ////// /// unmerge two toolstrips /// 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 StackmergeHistoryItemsStack; private ToolStrip mergedToolStrip; public MergeHistory(ToolStrip mergedToolStrip) { this.mergedToolStrip = mergedToolStrip; } public Stack MergeHistoryItemsStack { get { if (mergeHistoryItemsStack == null) { mergeHistoryItemsStack = new Stack (); } 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); } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //----------------------------------------------------------------------------- namespace System.Windows.Forms { using System; using System.Windows.Forms; using System.Windows.Forms.VisualStyles; using System.ComponentModel; using System.Collections; 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; ///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; // 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 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; 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 ClientUtils.WeakRefCollection ToolStrips { get { if (toolStripWeakArrayList == null) { toolStripWeakArrayList = new ClientUtils.WeakRefCollection(); } return toolStripWeakArrayList; } } /// Static events only!!! private static void AddEventHandler(int key, Delegate value) { lock (internalSyncObject) { if (staticEventHandlers == null) { staticEventHandlers = new Delegate[staticEventCount]; } staticEventHandlers[key] = Delegate.Combine(staticEventHandlers[key], value); } } ////// /// Find a toolstrip in the weak ref arraylist, return null if nothing was found /// [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; } ////// /// Find a toolstrip in the weak ref arraylist, return null if nothing was found /// [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) { 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(); } } ///removes dead entries from the toolstrip weak reference collection. 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); } } } } } ///static events only!!! 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; } } ////// the default renderer for the thread. When ToolStrip.RenderMode is set to manager - this /// is the property used. /// 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); } } } } //// 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. // // 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); } } ////// returns the default toolstrip RenderMode for the thread 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)); } } } ////// 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). /// 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 ////// Loads settings for the given Form using the form type's fullname as settings key. /// public static void LoadSettings(Form targetForm) { if (targetForm == null) { throw new ArgumentNullException("targetForm"); } LoadSettings(targetForm, targetForm.GetType().FullName); } ////// Loads settings for the given Form with the given settings key. /// 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(); } ////// Saves settings for the given form using the form type's fullname as settings key. /// public static void SaveSettings(Form sourceForm) { if (sourceForm == null) { throw new ArgumentNullException("sourceForm"); } SaveSettings(sourceForm, sourceForm.GetType().FullName); } ////// Saves settings for the given form with the given settings key. /// 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_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; #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; // fire timer messages to force our filter to get evaluated. ProcessMessages(true); } } 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); } } 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 (); } 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; } 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; } } /// determines if the key combination is valid for a shortcut. /// must have a modifier key + a regular key. /// 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; } ///this function is called for toplevel controls to process shortcuts. /// this function should be called from the topmost container control only. /// 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; } ///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. /// 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; } ///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. /// 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; } ////// 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); } ////// /// merge two toolstrips /// 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, Stackhistory) { 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(); } /// /// /// merge two toolstrips /// 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); } } ////// doesn't do a null check on source... if it's null we unmerge everything /// 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 "); StackreApply = new Stack (); 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); } /// /// /// unmerge two toolstrips /// public static bool RevertMerge(ToolStrip targetToolStrip) { return RevertMergeInternal(targetToolStrip, null, /*revertMDIControls*/false); } ////// /// unmerge two toolstrips /// public static bool RevertMerge(ToolStrip targetToolStrip, ToolStrip sourceToolStrip) { if (sourceToolStrip == null) { throw new ArgumentNullException("sourceToolStrip"); } return RevertMergeInternal(targetToolStrip, sourceToolStrip, /*revertMDIControls*/false); } ////// /// unmerge two toolstrips /// 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 StackmergeHistoryItemsStack; private ToolStrip mergedToolStrip; public MergeHistory(ToolStrip mergedToolStrip) { this.mergedToolStrip = mergedToolStrip; } public Stack MergeHistoryItemsStack { get { if (mergeHistoryItemsStack == null) { mergeHistoryItemsStack = new Stack (); } 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); } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- IISMapPath.cs
- GridViewRowCollection.cs
- Animatable.cs
- PrivateUnsafeNativeCompoundFileMethods.cs
- RowCache.cs
- ConfigXmlCDataSection.cs
- CleanUpVirtualizedItemEventArgs.cs
- FileDialog.cs
- PublisherMembershipCondition.cs
- XmlSchemaException.cs
- WorkflowMarkupSerializationException.cs
- DetailsViewUpdateEventArgs.cs
- InternalBase.cs
- SimpleLine.cs
- MatchingStyle.cs
- ConnectionPool.cs
- WebPartVerbsEventArgs.cs
- WebBrowserSiteBase.cs
- WebControlsSection.cs
- PrintPageEvent.cs
- InheritablePropertyChangeInfo.cs
- Component.cs
- ToolStripPanelDesigner.cs
- DelegatingMessage.cs
- HealthMonitoringSection.cs
- StringComparer.cs
- TextTreeUndo.cs
- EffectiveValueEntry.cs
- RequiredFieldValidator.cs
- ObjectItemCollection.cs
- TextRangeEditLists.cs
- XmlSchemaComplexType.cs
- TextTreeNode.cs
- LineMetrics.cs
- XmlSchemaSimpleContent.cs
- HwndTarget.cs
- SeverityFilter.cs
- HandlerFactoryCache.cs
- StringFunctions.cs
- _SecureChannel.cs
- Mouse.cs
- userdatakeys.cs
- CancellationState.cs
- InternalConfigEventArgs.cs
- XmlCollation.cs
- ReturnValue.cs
- NavigationHelper.cs
- LayoutDump.cs
- ExpressionEditorAttribute.cs
- TextEffect.cs
- ListViewAutomationPeer.cs
- CssClassPropertyAttribute.cs
- CounterCreationData.cs
- XDeferredAxisSource.cs
- TiffBitmapEncoder.cs
- BindingListCollectionView.cs
- CounterSet.cs
- RestHandler.cs
- sqlstateclientmanager.cs
- MdiWindowListItemConverter.cs
- WebControl.cs
- RadioButtonBaseAdapter.cs
- PtsPage.cs
- AnnotationAuthorChangedEventArgs.cs
- AssemblyNameProxy.cs
- TakeQueryOptionExpression.cs
- FlagsAttribute.cs
- ArrayWithOffset.cs
- SqlRetyper.cs
- ObjectParameter.cs
- FixedBufferAttribute.cs
- MessageVersion.cs
- ConfigPathUtility.cs
- DataGridViewCellPaintingEventArgs.cs
- CompensationDesigner.cs
- HttpApplicationStateWrapper.cs
- StorageAssociationSetMapping.cs
- GridViewPageEventArgs.cs
- ToolboxItemFilterAttribute.cs
- RootProfilePropertySettingsCollection.cs
- DiscoveryMessageSequence11.cs
- SerializationException.cs
- SplitterDesigner.cs
- WriteTimeStream.cs
- EndpointAddressMessageFilter.cs
- OneOf.cs
- DataIdProcessor.cs
- WebPartHelpVerb.cs
- Vector.cs
- Latin1Encoding.cs
- DateTimeConverter.cs
- DataGridViewSelectedCellsAccessibleObject.cs
- HatchBrush.cs
- CursorConverter.cs
- RelatedImageListAttribute.cs
- ControlCachePolicy.cs
- sqlstateclientmanager.cs
- TypeUtil.cs
- ConnectionManagementElementCollection.cs
- ReadOnlyMetadataCollection.cs