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)
31 using System.Runtime.InteropServices;
32 using System.ComponentModel;
33 using System.Collections.Generic;
35 namespace System.Windows.Forms
37 public sealed class ToolStripManager
39 private static ToolStripRenderer renderer = new ToolStripProfessionalRenderer ();
40 private static ToolStripManagerRenderMode render_mode = ToolStripManagerRenderMode.Professional;
41 private static bool visual_styles_enabled = Application.RenderWithVisualStyles;
42 private static List<WeakReference> toolstrips = new List<WeakReference> ();
43 private static List<ToolStripMenuItem> menu_items = new List<ToolStripMenuItem> ();
44 private static bool activated_by_keyboard;
46 #region Private Constructor
47 private ToolStripManager ()
52 #region Public Properties
53 public static ToolStripRenderer Renderer {
54 get { return ToolStripManager.renderer; }
56 if (ToolStripManager.Renderer != value) {
57 ToolStripManager.renderer = value;
58 ToolStripManager.OnRendererChanged (EventArgs.Empty);
63 public static ToolStripManagerRenderMode RenderMode {
64 get { return ToolStripManager.render_mode; }
66 if (!Enum.IsDefined (typeof (ToolStripManagerRenderMode), value))
67 throw new InvalidEnumArgumentException (string.Format ("Enum argument value '{0}' is not valid for ToolStripManagerRenderMode", value));
69 if (ToolStripManager.render_mode != value) {
70 ToolStripManager.render_mode = value;
73 case ToolStripManagerRenderMode.Custom:
74 throw new NotSupportedException ();
75 case ToolStripManagerRenderMode.System:
76 ToolStripManager.Renderer = new ToolStripSystemRenderer ();
78 case ToolStripManagerRenderMode.Professional:
79 ToolStripManager.Renderer = new ToolStripProfessionalRenderer ();
86 public static bool VisualStylesEnabled {
87 get { return ToolStripManager.visual_styles_enabled; }
89 if (ToolStripManager.visual_styles_enabled != value) {
90 ToolStripManager.visual_styles_enabled = value;
92 if (ToolStripManager.render_mode == ToolStripManagerRenderMode.Professional) {
93 (ToolStripManager.renderer as ToolStripProfessionalRenderer).ColorTable.UseSystemColors = !value;
94 ToolStripManager.OnRendererChanged (EventArgs.Empty);
101 #region Public Methods
102 public static ToolStrip FindToolStrip (string toolStripName)
105 foreach (WeakReference wr in toolstrips) {
106 ToolStrip ts = (ToolStrip)wr.Target;
111 if (ts.Name == toolStripName)
118 public static bool IsShortcutDefined (Keys shortcut)
121 foreach (ToolStripMenuItem tsmi in menu_items)
122 if (tsmi.ShortcutKeys == shortcut)
128 public static bool IsValidShortcut (Keys shortcut)
130 // Anything with an F1 - F12 is a shortcut
131 if ((shortcut & Keys.F1) == Keys.F1)
133 else if ((shortcut & Keys.F2) == Keys.F2)
135 else if ((shortcut & Keys.F3) == Keys.F3)
137 else if ((shortcut & Keys.F4) == Keys.F4)
139 else if ((shortcut & Keys.F5) == Keys.F5)
141 else if ((shortcut & Keys.F6) == Keys.F6)
143 else if ((shortcut & Keys.F7) == Keys.F7)
145 else if ((shortcut & Keys.F8) == Keys.F8)
147 else if ((shortcut & Keys.F9) == Keys.F9)
149 else if ((shortcut & Keys.F10) == Keys.F10)
151 else if ((shortcut & Keys.F11) == Keys.F11)
153 else if ((shortcut & Keys.F12) == Keys.F12)
156 // Modifier keys alone are not shortcuts
161 case Keys.Alt | Keys.Control:
162 case Keys.Alt | Keys.Shift:
163 case Keys.Control | Keys.Shift:
164 case Keys.Alt | Keys.Control | Keys.Shift:
168 // Anything else with a modifier key is a shortcut
169 if ((shortcut & Keys.Alt) == Keys.Alt)
171 else if ((shortcut & Keys.Control) == Keys.Control)
173 else if ((shortcut & Keys.Shift) == Keys.Shift)
176 // Anything else is not a shortcut
180 [MonoTODO ("Stub, does nothing")]
181 public static void LoadSettings (Form targetForm)
183 if (targetForm == null)
184 throw new ArgumentNullException ("targetForm");
187 [MonoTODO ("Stub, does nothing")]
188 public static void LoadSettings (Form targetForm, string key)
190 if (targetForm == null)
191 throw new ArgumentNullException ("targetForm");
192 if (string.IsNullOrEmpty (key))
193 throw new ArgumentNullException ("key");
196 [MonoLimitation ("Only supports one level of merging, cannot merge the same ToolStrip multiple times")]
197 public static bool Merge (ToolStrip sourceToolStrip, string targetName)
199 if (string.IsNullOrEmpty (targetName))
200 throw new ArgumentNullException ("targetName");
202 return Merge (sourceToolStrip, FindToolStrip (targetName));
205 [MonoLimitation ("Only supports one level of merging, cannot merge the same ToolStrip multiple times")]
206 public static bool Merge (ToolStrip sourceToolStrip, ToolStrip targetToolStrip)
208 // Check for exceptions
209 if (sourceToolStrip == null)
210 throw new ArgumentNullException ("sourceToolStrip");
212 if (targetToolStrip == null)
213 throw new ArgumentNullException ("targetName");
215 if (targetToolStrip == sourceToolStrip)
216 throw new ArgumentException ("Source and target ToolStrip must be different.");
218 // If the toolstrips don't allow merging, don't merge them
219 if (!sourceToolStrip.AllowMerge || !targetToolStrip.AllowMerge)
222 // We currently can't support merging multiple times
223 if (sourceToolStrip.IsCurrentlyMerged || targetToolStrip.IsCurrentlyMerged)
226 // What I wouldn't give to be able to modify a collection
227 // while enumerating through it...
229 List<ToolStripItem> items_to_move = new List<ToolStripItem> ();
231 // Create a list of every ToolStripItem we plan on moving
232 foreach (ToolStripItem tsi in sourceToolStrip.Items) {
233 switch (tsi.MergeAction) {
234 case MergeAction.Append:
236 items_to_move.Add (tsi);
238 case MergeAction.Insert:
239 if (tsi.MergeIndex >= 0)
240 items_to_move.Add (tsi);
242 case MergeAction.Replace:
243 case MergeAction.Remove:
244 case MergeAction.MatchOnly:
245 foreach (ToolStripItem target_tsi in targetToolStrip.Items)
246 if (tsi.Text == target_tsi.Text) {
247 items_to_move.Add (tsi);
254 // If there was nothing valid to merge, return false
255 if (items_to_move.Count == 0)
258 // Set some state so we can unmerge later
259 sourceToolStrip.BeginMerge ();
260 targetToolStrip.BeginMerge ();
262 sourceToolStrip.SuspendLayout ();
263 targetToolStrip.SuspendLayout ();
265 while (items_to_move.Count > 0) {
266 ToolStripItem tsi = items_to_move[0];
267 items_to_move.Remove (tsi);
269 switch (tsi.MergeAction) {
270 case MergeAction.Append:
272 // Just changing the parent will append it to the target
273 // and remove it from the source
274 ToolStrip.SetItemParent (tsi, targetToolStrip);
277 case MergeAction.Insert:
278 // Do the same work as Append, except Insert it into the
279 // location specified by the MergeIndex
280 RemoveItemFromParentToolStrip (tsi);
282 if (tsi.MergeIndex == -1)
284 else if (tsi.MergeIndex >= CountRealToolStripItems (targetToolStrip))
285 targetToolStrip.Items.AddNoOwnerOrLayout (tsi);
287 targetToolStrip.Items.InsertNoOwnerOrLayout (AdjustItemMergeIndex (targetToolStrip, tsi), tsi);
289 tsi.Parent = targetToolStrip;
292 case MergeAction.Replace:
293 // Find a target ToolStripItem with the same Text, remove it
294 // and replace it with the source one
295 foreach (ToolStripItem target_tsi in targetToolStrip.Items)
296 if (tsi.Text == target_tsi.Text) {
297 RemoveItemFromParentToolStrip (tsi);
299 // Insert where the old one is, then remove the old one
300 targetToolStrip.Items.InsertNoOwnerOrLayout (targetToolStrip.Items.IndexOf (target_tsi), tsi);
301 targetToolStrip.Items.RemoveNoOwnerOrLayout (target_tsi);
303 // Store the replaced one so we can get it back in unmerge
304 targetToolStrip.HiddenMergedItems.Add (target_tsi);
309 case MergeAction.Remove:
310 // Find a target ToolStripItem with the same Text, and remove
311 // it from the target, nothing else
312 foreach (ToolStripItem target_tsi in targetToolStrip.Items)
313 if (tsi.Text == target_tsi.Text) {
314 targetToolStrip.Items.RemoveNoOwnerOrLayout (target_tsi);
316 // Store the removed one so we can get it back in unmerge
317 targetToolStrip.HiddenMergedItems.Add (target_tsi);
322 case MergeAction.MatchOnly:
323 // Ugh, find the target ToolStripItem with the same Text, and take
324 // all the subitems from the source one, and append it to the target one
325 foreach (ToolStripItem target_tsi in targetToolStrip.Items)
326 if (tsi.Text == target_tsi.Text) {
327 if (target_tsi is ToolStripMenuItem && tsi is ToolStripMenuItem) {
328 ToolStripMenuItem source = (ToolStripMenuItem)tsi;
329 ToolStripMenuItem target = (ToolStripMenuItem)target_tsi;
331 ToolStripManager.Merge (source.DropDown, target.DropDown);
341 sourceToolStrip.ResumeLayout ();
342 targetToolStrip.ResumeLayout ();
344 // Store who we merged with, so we can unmerge when only given the target toolstrip
345 sourceToolStrip.CurrentlyMergedWith = targetToolStrip;
346 targetToolStrip.CurrentlyMergedWith = sourceToolStrip;
351 public static bool RevertMerge (string targetName)
353 return RevertMerge (FindToolStrip (targetName));
356 public static bool RevertMerge (ToolStrip targetToolStrip)
358 return RevertMerge (targetToolStrip, targetToolStrip.CurrentlyMergedWith);
361 public static bool RevertMerge (ToolStrip targetToolStrip, ToolStrip sourceToolStrip)
363 if (sourceToolStrip == null)
364 throw new ArgumentNullException ("sourceToolStrip");
366 List<ToolStripItem> items_to_move = new List<ToolStripItem> ();
368 // Find every ToolStripItem who's Owner is the source toolstrip
369 // - If it's a TSMI, see if any of the subitems need to be moved back
370 foreach (ToolStripItem tsi in targetToolStrip.Items) {
371 if (tsi.Owner == sourceToolStrip)
372 items_to_move.Add (tsi);
373 else if (tsi is ToolStripMenuItem)
374 foreach (ToolStripItem menuitem in (tsi as ToolStripMenuItem).DropDownItems)
375 foreach (ToolStripMenuItem tsmi in sourceToolStrip.Items)
376 if (menuitem.Owner == tsmi.DropDown)
377 items_to_move.Add (menuitem);
380 // If we didn't find anything, return false
381 if (items_to_move.Count == 0 && targetToolStrip.HiddenMergedItems.Count == 0)
384 // Put back all the target's items removed in the merge
385 while (targetToolStrip.HiddenMergedItems.Count > 0) {
386 targetToolStrip.RevertMergeItem (targetToolStrip.HiddenMergedItems[0]);
387 targetToolStrip.HiddenMergedItems.RemoveAt (0);
390 sourceToolStrip.SuspendLayout ();
391 targetToolStrip.SuspendLayout ();
394 while (items_to_move.Count > 0) {
395 sourceToolStrip.RevertMergeItem (items_to_move[0]);
396 items_to_move.Remove (items_to_move[0]);
399 sourceToolStrip.ResumeLayout ();
400 targetToolStrip.ResumeLayout ();
402 sourceToolStrip.IsCurrentlyMerged = false;
403 targetToolStrip.IsCurrentlyMerged = false;
405 sourceToolStrip.CurrentlyMergedWith = null;
406 targetToolStrip.CurrentlyMergedWith = null;
411 public static void SaveSettings (Form sourceForm)
413 if (sourceForm == null)
414 throw new ArgumentNullException ("sourceForm");
417 public static void SaveSettings (Form sourceForm, string key)
419 if (sourceForm == null)
420 throw new ArgumentNullException ("sourceForm");
421 if (string.IsNullOrEmpty (key))
422 throw new ArgumentNullException ("key");
426 #region Public Events
427 public static event EventHandler RendererChanged;
430 #region Private/Internal Methods
431 internal static bool ActivatedByKeyboard {
432 get { return activated_by_keyboard; }
433 set { activated_by_keyboard = value; }
436 internal static void AddToolStrip (ToolStrip ts)
439 toolstrips.Add (new WeakReference (ts));
442 // When we have merged in MDI items like the min/max/close buttons, we
443 // can't count them for the sake of menu merging or it will mess up
444 // where people are trying to put them
445 private static int AdjustItemMergeIndex (ToolStrip ts, ToolStripItem tsi)
447 if (ts.Items[0] is MdiControlStrip.SystemMenuItem)
448 return tsi.MergeIndex + 1;
450 return tsi.MergeIndex;
453 private static int CountRealToolStripItems (ToolStrip ts)
457 foreach (ToolStripItem tsi in ts.Items)
458 if (!(tsi is MdiControlStrip.ControlBoxMenuItem) && !(tsi is MdiControlStrip.SystemMenuItem))
464 internal static ToolStrip GetNextToolStrip (ToolStrip ts, bool forward)
467 List<ToolStrip> tools = new List<ToolStrip> ();
469 foreach (WeakReference wr in toolstrips) {
470 ToolStrip t = (ToolStrip)wr.Target;
476 int index = tools.IndexOf (ts);
479 // Look for any toolstrip after this one in the collection
480 for (int i = index + 1; i < tools.Count; i++)
481 if (tools[i].TopLevelControl == ts.TopLevelControl && !(tools[i] is StatusStrip))
484 // Look for any toolstrip before this one in the collection
485 for (int i = 0; i < index; i++)
486 if (tools[i].TopLevelControl == ts.TopLevelControl && !(tools[i] is StatusStrip))
489 // Look for any toolstrip before this one in the collection
490 for (int i = index - 1; i >= 0; i--)
491 if (tools[i].TopLevelControl == ts.TopLevelControl && !(tools[i] is StatusStrip))
494 // Look for any toolstrip after this one in the collection
495 for (int i = tools.Count - 1; i > index; i--)
496 if (tools[i].TopLevelControl == ts.TopLevelControl && !(tools[i] is StatusStrip))
504 internal static bool ProcessCmdKey (ref Message m, Keys keyData)
507 foreach (ToolStripMenuItem tsmi in menu_items)
508 if (tsmi.ProcessCmdKey (ref m, keyData) == true)
514 internal static bool ProcessMenuKey (ref Message m)
516 // If we have a currently active menu, deactivate it
517 if (Application.KeyboardCapture != null) {
518 if (Application.KeyboardCapture.OnMenuKey ())
522 // Get the parent form of this message
523 Form f = (Form)Control.FromHandle (m.HWnd).TopLevelControl;
525 // If there isn't a Form with this, there isn't much we can do
529 // Check the MainMenuStrip property first
530 if (f.MainMenuStrip != null)
531 if (f.MainMenuStrip.OnMenuKey ())
534 // Look for any MenuStrip in the form
536 foreach (WeakReference wr in toolstrips) {
537 ToolStrip ts = (ToolStrip)wr.Target;
542 if (ts.TopLevelControl == f)
550 internal static void SetActiveToolStrip (ToolStrip toolStrip, bool keyboard)
552 if (Application.KeyboardCapture != null)
553 Application.KeyboardCapture.KeyboardActive = false;
555 if (toolStrip == null) {
556 activated_by_keyboard = false;
560 activated_by_keyboard = keyboard;
562 toolStrip.KeyboardActive = true;
565 internal static void AddToolStripMenuItem (ToolStripMenuItem tsmi)
568 menu_items.Add (tsmi);
571 internal static void RemoveToolStrip (ToolStrip ts)
574 foreach (WeakReference wr in toolstrips)
575 if (wr.Target == ts) {
576 toolstrips.Remove (wr);
582 internal static void RemoveToolStripMenuItem (ToolStripMenuItem tsmi)
585 menu_items.Remove (tsmi);
588 internal static void FireAppClicked ()
590 if (AppClicked != null) AppClicked (null, EventArgs.Empty);
592 if (Application.KeyboardCapture != null)
593 Application.KeyboardCapture.Dismiss (ToolStripDropDownCloseReason.AppClicked);
596 internal static void FireAppFocusChanged (Form form)
598 if (AppFocusChange != null) AppFocusChange (form, EventArgs.Empty);
600 if (Application.KeyboardCapture != null)
601 Application.KeyboardCapture.Dismiss (ToolStripDropDownCloseReason.AppFocusChange);
604 internal static void FireAppFocusChanged (object sender)
606 if (AppFocusChange != null) AppFocusChange (sender, EventArgs.Empty);
608 if (Application.KeyboardCapture != null)
609 Application.KeyboardCapture.Dismiss (ToolStripDropDownCloseReason.AppFocusChange);
612 private static void OnRendererChanged (EventArgs e)
614 if (RendererChanged != null) RendererChanged (null, e);
617 private static void RemoveItemFromParentToolStrip (ToolStripItem tsi)
619 if (tsi.Owner != null) {
620 tsi.Owner.Items.RemoveNoOwnerOrLayout (tsi);
622 if (tsi.Owner is ToolStripOverflow)
623 (tsi.Owner as ToolStripOverflow).ParentToolStrip.Items.RemoveNoOwnerOrLayout (tsi);
627 internal static event EventHandler AppClicked;
628 internal static event EventHandler AppFocusChange;