4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 // Copyright (c) 2006 Jonathan Pobst
26 // Jonathan Pobst (monkey@jpobst.com)
30 using System.Runtime.InteropServices;
31 using System.ComponentModel;
32 using System.Collections.Generic;
34 namespace System.Windows.Forms
36 public sealed class ToolStripManager
38 private static ToolStripRenderer renderer = new ToolStripProfessionalRenderer ();
39 private static ToolStripManagerRenderMode render_mode = ToolStripManagerRenderMode.Professional;
40 private static bool visual_styles_enabled = Application.RenderWithVisualStyles;
41 private static List<WeakReference> toolstrips = new List<WeakReference> ();
42 private static List<ToolStripMenuItem> menu_items = new List<ToolStripMenuItem> ();
43 private static bool activated_by_keyboard;
45 #region Private Constructor
46 private ToolStripManager ()
51 #region Public Properties
52 public static ToolStripRenderer Renderer {
53 get { return ToolStripManager.renderer; }
55 if (ToolStripManager.Renderer != value) {
56 ToolStripManager.renderer = value;
57 ToolStripManager.OnRendererChanged (EventArgs.Empty);
62 public static ToolStripManagerRenderMode RenderMode {
63 get { return ToolStripManager.render_mode; }
65 if (!Enum.IsDefined (typeof (ToolStripManagerRenderMode), value))
66 throw new InvalidEnumArgumentException (string.Format ("Enum argument value '{0}' is not valid for ToolStripManagerRenderMode", value));
68 if (ToolStripManager.render_mode != value) {
69 ToolStripManager.render_mode = value;
72 case ToolStripManagerRenderMode.Custom:
73 throw new NotSupportedException ();
74 case ToolStripManagerRenderMode.System:
75 ToolStripManager.Renderer = new ToolStripSystemRenderer ();
77 case ToolStripManagerRenderMode.Professional:
78 ToolStripManager.Renderer = new ToolStripProfessionalRenderer ();
85 public static bool VisualStylesEnabled {
86 get { return ToolStripManager.visual_styles_enabled; }
88 if (ToolStripManager.visual_styles_enabled != value) {
89 ToolStripManager.visual_styles_enabled = value;
91 if (ToolStripManager.render_mode == ToolStripManagerRenderMode.Professional) {
92 (ToolStripManager.renderer as ToolStripProfessionalRenderer).ColorTable.UseSystemColors = !value;
93 ToolStripManager.OnRendererChanged (EventArgs.Empty);
100 #region Public Methods
101 public static ToolStrip FindToolStrip (string toolStripName)
104 foreach (WeakReference wr in toolstrips) {
105 ToolStrip ts = (ToolStrip)wr.Target;
110 if (ts.Name == toolStripName)
117 public static bool IsShortcutDefined (Keys shortcut)
120 foreach (ToolStripMenuItem tsmi in menu_items)
121 if (tsmi.ShortcutKeys == shortcut)
127 public static bool IsValidShortcut (Keys shortcut)
129 // Anything with an F1 - F12 is a shortcut
130 if ((shortcut & Keys.F1) == Keys.F1)
132 else if ((shortcut & Keys.F2) == Keys.F2)
134 else if ((shortcut & Keys.F3) == Keys.F3)
136 else if ((shortcut & Keys.F4) == Keys.F4)
138 else if ((shortcut & Keys.F5) == Keys.F5)
140 else if ((shortcut & Keys.F6) == Keys.F6)
142 else if ((shortcut & Keys.F7) == Keys.F7)
144 else if ((shortcut & Keys.F8) == Keys.F8)
146 else if ((shortcut & Keys.F9) == Keys.F9)
148 else if ((shortcut & Keys.F10) == Keys.F10)
150 else if ((shortcut & Keys.F11) == Keys.F11)
152 else if ((shortcut & Keys.F12) == Keys.F12)
155 // Modifier keys alone are not shortcuts
160 case Keys.Alt | Keys.Control:
161 case Keys.Alt | Keys.Shift:
162 case Keys.Control | Keys.Shift:
163 case Keys.Alt | Keys.Control | Keys.Shift:
167 // Anything else with a modifier key is a shortcut
168 if ((shortcut & Keys.Alt) == Keys.Alt)
170 else if ((shortcut & Keys.Control) == Keys.Control)
172 else if ((shortcut & Keys.Shift) == Keys.Shift)
175 // Anything else is not a shortcut
179 [MonoTODO ("Stub, does nothing")]
180 public static void LoadSettings (Form targetForm)
182 if (targetForm == null)
183 throw new ArgumentNullException ("targetForm");
186 [MonoTODO ("Stub, does nothing")]
187 public static void LoadSettings (Form targetForm, string key)
189 if (targetForm == null)
190 throw new ArgumentNullException ("targetForm");
191 if (string.IsNullOrEmpty (key))
192 throw new ArgumentNullException ("key");
195 [MonoLimitation ("Only supports one level of merging, cannot merge the same ToolStrip multiple times")]
196 public static bool Merge (ToolStrip sourceToolStrip, string targetName)
198 if (string.IsNullOrEmpty (targetName))
199 throw new ArgumentNullException ("targetName");
201 return Merge (sourceToolStrip, FindToolStrip (targetName));
204 [MonoLimitation ("Only supports one level of merging, cannot merge the same ToolStrip multiple times")]
205 public static bool Merge (ToolStrip sourceToolStrip, ToolStrip targetToolStrip)
207 // Check for exceptions
208 if (sourceToolStrip == null)
209 throw new ArgumentNullException ("sourceToolStrip");
211 if (targetToolStrip == null)
212 throw new ArgumentNullException ("targetName");
214 if (targetToolStrip == sourceToolStrip)
215 throw new ArgumentException ("Source and target ToolStrip must be different.");
217 // If the toolstrips don't allow merging, don't merge them
218 if (!sourceToolStrip.AllowMerge || !targetToolStrip.AllowMerge)
221 // We currently can't support merging multiple times
222 if (sourceToolStrip.IsCurrentlyMerged || targetToolStrip.IsCurrentlyMerged)
225 // What I wouldn't give to be able to modify a collection
226 // while enumerating through it...
228 List<ToolStripItem> items_to_move = new List<ToolStripItem> ();
230 // Create a list of every ToolStripItem we plan on moving
231 foreach (ToolStripItem tsi in sourceToolStrip.Items) {
232 switch (tsi.MergeAction) {
233 case MergeAction.Append:
235 items_to_move.Add (tsi);
237 case MergeAction.Insert:
238 if (tsi.MergeIndex >= 0)
239 items_to_move.Add (tsi);
241 case MergeAction.Replace:
242 case MergeAction.Remove:
243 case MergeAction.MatchOnly:
244 foreach (ToolStripItem target_tsi in targetToolStrip.Items)
245 if (tsi.Text == target_tsi.Text) {
246 items_to_move.Add (tsi);
253 // If there was nothing valid to merge, return false
254 if (items_to_move.Count == 0)
257 // Set some state so we can unmerge later
258 sourceToolStrip.BeginMerge ();
259 targetToolStrip.BeginMerge ();
261 sourceToolStrip.SuspendLayout ();
262 targetToolStrip.SuspendLayout ();
264 while (items_to_move.Count > 0) {
265 ToolStripItem tsi = items_to_move[0];
266 items_to_move.Remove (tsi);
268 switch (tsi.MergeAction) {
269 case MergeAction.Append:
271 // Just changing the parent will append it to the target
272 // and remove it from the source
273 ToolStrip.SetItemParent (tsi, targetToolStrip);
276 case MergeAction.Insert:
277 // Do the same work as Append, except Insert it into the
278 // location specified by the MergeIndex
279 RemoveItemFromParentToolStrip (tsi);
281 if (tsi.MergeIndex == -1)
283 else if (tsi.MergeIndex >= CountRealToolStripItems (targetToolStrip))
284 targetToolStrip.Items.AddNoOwnerOrLayout (tsi);
286 targetToolStrip.Items.InsertNoOwnerOrLayout (AdjustItemMergeIndex (targetToolStrip, tsi), tsi);
288 tsi.Parent = targetToolStrip;
291 case MergeAction.Replace:
292 // Find a target ToolStripItem with the same Text, remove it
293 // and replace it with the source one
294 foreach (ToolStripItem target_tsi in targetToolStrip.Items)
295 if (tsi.Text == target_tsi.Text) {
296 RemoveItemFromParentToolStrip (tsi);
298 // Insert where the old one is, then remove the old one
299 targetToolStrip.Items.InsertNoOwnerOrLayout (targetToolStrip.Items.IndexOf (target_tsi), tsi);
300 targetToolStrip.Items.RemoveNoOwnerOrLayout (target_tsi);
302 // Store the replaced one so we can get it back in unmerge
303 targetToolStrip.HiddenMergedItems.Add (target_tsi);
308 case MergeAction.Remove:
309 // Find a target ToolStripItem with the same Text, and remove
310 // it from the target, nothing else
311 foreach (ToolStripItem target_tsi in targetToolStrip.Items)
312 if (tsi.Text == target_tsi.Text) {
313 targetToolStrip.Items.RemoveNoOwnerOrLayout (target_tsi);
315 // Store the removed one so we can get it back in unmerge
316 targetToolStrip.HiddenMergedItems.Add (target_tsi);
321 case MergeAction.MatchOnly:
322 // Ugh, find the target ToolStripItem with the same Text, and take
323 // all the subitems from the source one, and append it to the target one
324 foreach (ToolStripItem target_tsi in targetToolStrip.Items)
325 if (tsi.Text == target_tsi.Text) {
326 if (target_tsi is ToolStripMenuItem && tsi is ToolStripMenuItem) {
327 ToolStripMenuItem source = (ToolStripMenuItem)tsi;
328 ToolStripMenuItem target = (ToolStripMenuItem)target_tsi;
330 ToolStripManager.Merge (source.DropDown, target.DropDown);
340 sourceToolStrip.ResumeLayout ();
341 targetToolStrip.ResumeLayout ();
343 // Store who we merged with, so we can unmerge when only given the target toolstrip
344 sourceToolStrip.CurrentlyMergedWith = targetToolStrip;
345 targetToolStrip.CurrentlyMergedWith = sourceToolStrip;
350 public static bool RevertMerge (string targetName)
352 return RevertMerge (FindToolStrip (targetName));
355 public static bool RevertMerge (ToolStrip targetToolStrip)
357 return RevertMerge (targetToolStrip, targetToolStrip.CurrentlyMergedWith);
360 public static bool RevertMerge (ToolStrip targetToolStrip, ToolStrip sourceToolStrip)
362 if (sourceToolStrip == null)
363 throw new ArgumentNullException ("sourceToolStrip");
365 List<ToolStripItem> items_to_move = new List<ToolStripItem> ();
367 // Find every ToolStripItem who's Owner is the source toolstrip
368 // - If it's a TSMI, see if any of the subitems need to be moved back
369 foreach (ToolStripItem tsi in targetToolStrip.Items) {
370 if (tsi.Owner == sourceToolStrip)
371 items_to_move.Add (tsi);
372 else if (tsi is ToolStripMenuItem)
373 foreach (ToolStripItem menuitem in (tsi as ToolStripMenuItem).DropDownItems)
374 foreach (ToolStripMenuItem tsmi in sourceToolStrip.Items)
375 if (menuitem.Owner == tsmi.DropDown)
376 items_to_move.Add (menuitem);
379 // If we didn't find anything, return false
380 if (items_to_move.Count == 0 && targetToolStrip.HiddenMergedItems.Count == 0)
383 // Put back all the target's items removed in the merge
384 while (targetToolStrip.HiddenMergedItems.Count > 0) {
385 targetToolStrip.RevertMergeItem (targetToolStrip.HiddenMergedItems[0]);
386 targetToolStrip.HiddenMergedItems.RemoveAt (0);
389 sourceToolStrip.SuspendLayout ();
390 targetToolStrip.SuspendLayout ();
393 while (items_to_move.Count > 0) {
394 sourceToolStrip.RevertMergeItem (items_to_move[0]);
395 items_to_move.Remove (items_to_move[0]);
398 sourceToolStrip.ResumeLayout ();
399 targetToolStrip.ResumeLayout ();
401 sourceToolStrip.IsCurrentlyMerged = false;
402 targetToolStrip.IsCurrentlyMerged = false;
404 sourceToolStrip.CurrentlyMergedWith = null;
405 targetToolStrip.CurrentlyMergedWith = null;
410 public static void SaveSettings (Form sourceForm)
412 if (sourceForm == null)
413 throw new ArgumentNullException ("sourceForm");
416 public static void SaveSettings (Form sourceForm, string key)
418 if (sourceForm == null)
419 throw new ArgumentNullException ("sourceForm");
420 if (string.IsNullOrEmpty (key))
421 throw new ArgumentNullException ("key");
425 #region Public Events
426 public static event EventHandler RendererChanged;
429 #region Private/Internal Methods
430 internal static bool ActivatedByKeyboard {
431 get { return activated_by_keyboard; }
432 set { activated_by_keyboard = value; }
435 internal static void AddToolStrip (ToolStrip ts)
438 toolstrips.Add (new WeakReference (ts));
441 // When we have merged in MDI items like the min/max/close buttons, we
442 // can't count them for the sake of menu merging or it will mess up
443 // where people are trying to put them
444 private static int AdjustItemMergeIndex (ToolStrip ts, ToolStripItem tsi)
446 if (ts.Items[0] is MdiControlStrip.SystemMenuItem)
447 return tsi.MergeIndex + 1;
449 return tsi.MergeIndex;
452 private static int CountRealToolStripItems (ToolStrip ts)
456 foreach (ToolStripItem tsi in ts.Items)
457 if (!(tsi is MdiControlStrip.ControlBoxMenuItem) && !(tsi is MdiControlStrip.SystemMenuItem))
463 internal static ToolStrip GetNextToolStrip (ToolStrip ts, bool forward)
466 List<ToolStrip> tools = new List<ToolStrip> ();
468 foreach (WeakReference wr in toolstrips) {
469 ToolStrip t = (ToolStrip)wr.Target;
475 int index = tools.IndexOf (ts);
478 // Look for any toolstrip after this one in the collection
479 for (int i = index + 1; i < tools.Count; i++)
480 if (tools[i].TopLevelControl == ts.TopLevelControl && !(tools[i] is StatusStrip))
483 // Look for any toolstrip before this one in the collection
484 for (int i = 0; i < index; i++)
485 if (tools[i].TopLevelControl == ts.TopLevelControl && !(tools[i] is StatusStrip))
488 // Look for any toolstrip before this one in the collection
489 for (int i = index - 1; i >= 0; i--)
490 if (tools[i].TopLevelControl == ts.TopLevelControl && !(tools[i] is StatusStrip))
493 // Look for any toolstrip after this one in the collection
494 for (int i = tools.Count - 1; i > index; i--)
495 if (tools[i].TopLevelControl == ts.TopLevelControl && !(tools[i] is StatusStrip))
503 internal static bool ProcessCmdKey (ref Message m, Keys keyData)
506 foreach (ToolStripMenuItem tsmi in menu_items)
507 if (tsmi.ProcessCmdKey (ref m, keyData) == true)
513 internal static bool ProcessMenuKey (ref Message m)
515 // If we have a currently active menu, deactivate it
516 if (Application.KeyboardCapture != null) {
517 if (Application.KeyboardCapture.OnMenuKey ())
521 // Get the parent form of this message
522 Form f = (Form)Control.FromHandle (m.HWnd).TopLevelControl;
524 // If there isn't a Form with this, there isn't much we can do
528 // Check the MainMenuStrip property first
529 if (f.MainMenuStrip != null)
530 if (f.MainMenuStrip.OnMenuKey ())
533 // Look for any MenuStrip in the form
535 foreach (WeakReference wr in toolstrips) {
536 ToolStrip ts = (ToolStrip)wr.Target;
541 if (ts.TopLevelControl == f)
549 internal static void SetActiveToolStrip (ToolStrip toolStrip, bool keyboard)
551 if (Application.KeyboardCapture != null)
552 Application.KeyboardCapture.KeyboardActive = false;
554 if (toolStrip == null) {
555 activated_by_keyboard = false;
559 activated_by_keyboard = keyboard;
561 toolStrip.KeyboardActive = true;
564 internal static void AddToolStripMenuItem (ToolStripMenuItem tsmi)
567 menu_items.Add (tsmi);
570 internal static void RemoveToolStrip (ToolStrip ts)
573 foreach (WeakReference wr in toolstrips)
574 if (wr.Target == ts) {
575 toolstrips.Remove (wr);
581 internal static void RemoveToolStripMenuItem (ToolStripMenuItem tsmi)
584 menu_items.Remove (tsmi);
587 internal static void FireAppClicked ()
589 if (AppClicked != null) AppClicked (null, EventArgs.Empty);
591 if (Application.KeyboardCapture != null)
592 Application.KeyboardCapture.Dismiss (ToolStripDropDownCloseReason.AppClicked);
595 internal static void FireAppFocusChanged (Form form)
597 if (AppFocusChange != null) AppFocusChange (form, EventArgs.Empty);
599 if (Application.KeyboardCapture != null)
600 Application.KeyboardCapture.Dismiss (ToolStripDropDownCloseReason.AppFocusChange);
603 internal static void FireAppFocusChanged (object sender)
605 if (AppFocusChange != null) AppFocusChange (sender, EventArgs.Empty);
607 if (Application.KeyboardCapture != null)
608 Application.KeyboardCapture.Dismiss (ToolStripDropDownCloseReason.AppFocusChange);
611 private static void OnRendererChanged (EventArgs e)
613 if (RendererChanged != null) RendererChanged (null, e);
616 private static void RemoveItemFromParentToolStrip (ToolStripItem tsi)
618 if (tsi.Owner != null) {
619 tsi.Owner.Items.RemoveNoOwnerOrLayout (tsi);
621 if (tsi.Owner is ToolStripOverflow)
622 (tsi.Owner as ToolStripOverflow).ParentToolStrip.Items.RemoveNoOwnerOrLayout (tsi);
626 internal static event EventHandler AppClicked;
627 internal static event EventHandler AppFocusChange;