PopupMenu.cs
Upload User: nnpulika
Upload Date: 2013-02-15
Package Size: 597k
Code Size: 83k
Category:

StatusBar

Development Platform:

C#

  1. using System;
  2. using System.IO;
  3. using System.Drawing;
  4. using System.Reflection;
  5. using System.Drawing.Text;
  6. using System.Collections;
  7. using System.Windows.Forms;
  8. using System.ComponentModel;
  9. using System.Drawing.Imaging;
  10. using System.Runtime.InteropServices;
  11. using System.Diagnostics;
  12. using System.Text;
  13. using UtilityLibrary.Menus;
  14. using UtilityLibrary.Win32;
  15. using UtilityLibrary.Collections;
  16. using UtilityLibrary.General;
  17. namespace UtilityLibrary.Menus
  18. {
  19. #region Helper Classes
  20. internal class FocusCatcher : NativeWindow
  21. {
  22. public FocusCatcher(IntPtr hParent)
  23. {
  24. CreateParams cp = new CreateParams();
  25. // Any old title will do as it will not be shown
  26. cp.Caption = "NativeFocusCatcher";
  27. // Set the position off the screen so it will not be seen
  28. cp.X = -1;
  29. cp.Y = -1;
  30. cp.Height = 0;
  31. cp.Width = 0;
  32. // As a top-level window it has no parent
  33. cp.Parent = hParent;
  34. // Create as a child of the specified parent
  35. cp.Style = unchecked((int)(uint)Win32.WindowStyles.WS_CHILD +
  36. (int)(uint)Win32.WindowStyles.WS_VISIBLE);
  37. // Create the actual window
  38. CreateHandle(cp);
  39. }
  40. }
  41. #endregion
  42. [ToolboxItem(false)]
  43. [DefaultProperty("MenuCommands")]
  44. public class PopupMenu : NativeWindow
  45. {
  46. #region Enumerations
  47. // Enumeration of Indexes into positioning constants array
  48. protected enum PI
  49. {
  50. BorderTop = 0,
  51. BorderLeft = 1,
  52. BorderBottom = 2, 
  53. BorderRight = 3,
  54. ImageGapTop = 4,
  55. ImageGapLeft = 5,
  56. ImageGapBottom = 6,
  57. ImageGapRight = 7,
  58. TextGapLeft = 8,
  59. TextGapRight = 9,
  60. SubMenuGapLeft = 10,
  61. SubMenuWidth = 11,
  62. SubMenuGapRight = 12,
  63. SeparatorHeight = 13,
  64. SeparatorWidth = 14,
  65. ShortcutGap = 15,
  66. ShadowWidth = 16,
  67. ShadowHeight = 17,
  68. ExtraWidthGap = 18,
  69. ExtraHeightGap = 19,
  70. ExtraRightGap = 20,
  71. ExtraReduce = 21
  72. }
  73. // Indexes into the menu images strip
  74. protected enum ImageIndex
  75. {
  76. Check = 0,
  77. Radio = 1,
  78. SubMenu = 2,
  79. CheckSelected = 3,
  80. RadioSelected = 4,
  81. SubMenuSelected = 5,
  82. Expansion = 6,
  83. ImageError = 7
  84. }
  85. #endregion
  86. #region Class Variables
  87. // Class constants for sizing/positioning each style
  88. protected static readonly int[,] position = { 
  89. {2, 1, 0, 1, 4, 3, 4, 5, 4, 4, 2, 6, 5, 3, 1, 10, 3, 3, 2, 2, 0, 0}, // IDE
  90. {1, 0, 1, 2, 2, 1, 3, 4, 3, 3, 2, 8, 5, 4, 5, 10, 0, 0, 2, 2, 2, 5} // Plain
  91.  };
  92. // Other class constants
  93. protected static readonly int selectionDelay = 400;
  94. // Class fields
  95. protected static ImageList menuImages = null;
  96. protected static bool supportsLayered = false;
  97. // Class constants that are marked as 'readonly' are allowed computed initialization
  98. protected readonly int WM_DISMISS = (int)Msg.WM_USER + 1;
  99. protected readonly int imageWidth = SystemInformation.SmallIconSize.Width;
  100. protected readonly int imageHeight = SystemInformation.SmallIconSize.Height;
  101. // Instance fields
  102. protected Timer timer;
  103. protected bool layered;
  104. protected Font textFont;
  105. protected int  popupItem;
  106. protected int  trackItem;
  107. protected int  borderGap;
  108. protected int  returnDir;
  109. protected int  extraSize;
  110. protected bool  exitLoop;
  111. protected bool  mouseOver;
  112. protected bool  grabFocus;
  113. protected bool  popupDown;
  114. protected bool  popupRight;
  115. protected bool  excludeTop;
  116. protected Point  screenPos;
  117. protected IntPtr  oldFocus;
  118. protected VisualStyle  style;
  119. protected Size  currentSize;
  120. protected int  excludeOffset;
  121. protected Point  lastMousePos;
  122. protected Point  currentPoint;
  123. protected bool  showInfrequent;
  124. protected PopupMenu  childMenu;
  125. protected Point  leftScreenPos;
  126. protected Direction  direction;
  127. protected Point  aboveScreenPos;
  128. protected PopupMenu  parentMenu;
  129. protected ArrayList  drawCommands;
  130. internal FocusCatcher  focusCatcher;
  131. protected MenuControl  parentControl;
  132. protected MenuCommand  returnCommand;
  133. protected MenuCommandCollection  menuCommands;
  134. #endregion
  135. #region Constructors
  136. static PopupMenu()
  137. {
  138. // Create a strip of images by loading an embedded bitmap resource
  139. menuImages = ResourceUtil.LoadImageListResource(Type.GetType("UtilityLibrary.Menus.PopupMenu"),
  140.  "UtilityLibrary.Resources.ImagesMenu",
  141.  "MenuControlImages",
  142.  new Size(16,16),
  143.  true,
  144.  new Point(0,0));
  145. // We need to know if the OS supports layered windows
  146. supportsLayered = (OSFeature.Feature.GetVersionPresent(OSFeature.LayeredWindows) != null);
  147. }
  148. public PopupMenu()
  149. {
  150. // Create collection objects
  151.  drawCommands = new ArrayList();
  152.  menuCommands = new MenuCommandCollection(); 
  153. // Default the properties
  154.  returnDir = 0;
  155.  extraSize = 0;
  156.  popupItem = -1;
  157.  trackItem = -1;
  158.  childMenu = null;
  159.  exitLoop = false;
  160.  popupDown = true;
  161.  mouseOver = false;
  162.  grabFocus = false;
  163.  excludeTop = true;
  164.  popupRight = true;
  165.  parentMenu = null;
  166.  excludeOffset = 0;
  167.  focusCatcher = null;
  168.  parentControl = null;
  169.  returnCommand = null;
  170.  oldFocus = IntPtr.Zero;
  171.  showInfrequent = false;
  172.  style = VisualStyle.IDE;
  173.  lastMousePos = new Point(-1,-1);
  174.  direction = Direction.Horizontal;
  175. textFont = SystemInformation.MenuFont;
  176. // Create and initialise the timer object (but do not start it running!)
  177. timer = new Timer();
  178. timer.Interval = selectionDelay;
  179. timer.Tick += new EventHandler(OnTimerExpire);
  180. }
  181. #endregion
  182. #region Overrides
  183. protected override void WndProc(ref Message m)
  184. {
  185. // WM DISMISS is not a constant and so cannot be in a switch
  186. if (m.Msg == WM_DISMISS)
  187. OnWM_DISMISS();
  188. else
  189. {
  190. // Want to notice when the window is maximized
  191. Msg cmsg = (Msg)m.Msg;
  192. Debug.WriteLine(cmsg.ToString());
  193. // Want to notice when the window is maximized
  194. switch(m.Msg)
  195. {
  196. case (int)Msg.WM_PAINT:
  197. OnWM_PAINT(ref m);
  198. break;
  199. case (int)Msg.WM_ACTIVATEAPP:
  200. OnWM_ACTIVATEAPP(ref m);
  201. break;
  202. case (int)Msg.WM_MOUSEMOVE:
  203. OnWM_MOUSEMOVE(ref m);
  204. break;
  205. case (int)Msg.WM_MOUSELEAVE:
  206. OnWM_MOUSELEAVE();
  207. break;
  208. case (int)Msg.WM_LBUTTONUP:
  209. case (int)Msg.WM_MBUTTONUP:
  210. case (int)Msg.WM_RBUTTONUP:
  211. OnWM_XBUTTONUP(ref m);
  212. break;
  213. case (int)Msg.WM_MOUSEACTIVATE:
  214. OnWM_MOUSEACTIVATE(ref m);
  215. break;
  216. case (int)Msg.WM_SETCURSOR:
  217. OnWM_SETCURSOR(ref m);
  218. break;
  219. default:
  220. base.WndProc(ref m);
  221. break;
  222. }
  223. }
  224. }
  225. #endregion
  226. #region Properties
  227. [Category("Appearance")]
  228. [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
  229. public MenuCommandCollection MenuCommands
  230. {
  231. get { return  menuCommands; }
  232. set
  233. {
  234.  menuCommands.Clear();
  235.  menuCommands = value;
  236. }
  237. [Category("Appearance")]
  238. [DefaultValue(VisualStyle.IDE)]
  239. public VisualStyle Style
  240. {
  241. get { return  style; }
  242. set
  243. {
  244. if ( style != value)
  245.  style = value;
  246. }
  247. }
  248. [Category("Appearance")]
  249. public Font Font
  250. {
  251. get { return textFont; }
  252. set
  253. {
  254. if (textFont != value)
  255. textFont = value;
  256. }
  257. }
  258. [Category("Behaviour")]
  259. [DefaultValue(false)]
  260. public bool ShowInfrequent
  261. {
  262. get { return  showInfrequent; }
  263. set
  264. {
  265. if ( showInfrequent != value)
  266.  showInfrequent = value;
  267. }
  268. }
  269. #endregion
  270. #region Methods
  271. public MenuCommand TrackPopup(Point screenPos)
  272. {
  273. return TrackPopup(screenPos, false);
  274. }
  275. public MenuCommand TrackPopup(Point screenPos, bool selectFirst)
  276. {
  277. // No point in showing PopupMenu if there are no entries
  278. if ( menuCommands.VisibleItems())
  279. {
  280. // We are being called as a popup window and not from a MenuControl instance
  281. // and so we need to the focus during the lifetime of the popup and then return
  282. // focus back again when we are dismissed. Otherwise keyboard input will still go
  283. // to the control that has it when the popup is created.
  284.  grabFocus = true;
  285. // Default the drawing direction
  286.  direction = Direction.Horizontal;
  287. // Remember screen positions
  288.  this.screenPos = screenPos;
  289.  aboveScreenPos = screenPos;
  290.  leftScreenPos = screenPos;
  291. return InternalTrackPopup(selectFirst);
  292. }
  293. else
  294. return null;
  295. }
  296. public void Dismiss()
  297. {
  298. if (this.Handle != IntPtr.Zero)
  299. {
  300. // Prevent the timer from expiring
  301. timer.Stop();
  302. // Kill any child menu
  303. if ( childMenu != null)
  304.  childMenu.Dismiss();
  305. // Finish processing messages
  306.  exitLoop = true;
  307. // Hide ourself
  308. WindowsAPI.ShowWindow(this.Handle, Win32.ShowWindowStyles.SW_HIDE);
  309. // Cause our own message loop to exit
  310. WindowsAPI.PostMessage(this.Handle, WM_DISMISS, 0, 0); 
  311. }
  312. }
  313. #endregion
  314. #region Implementation
  315. internal MenuCommand TrackPopup(Point screenPos, Point aboveScreenPos, 
  316. Direction direction,
  317. MenuCommandCollection menuCollection, 
  318. int borderGap, 
  319. bool selectFirst, 
  320. MenuControl parentControl,
  321.     ref int returnDir)
  322. {
  323. // Remember which direction the MenuControl is drawing in
  324.  direction = direction;
  325. // Remember the MenuControl that initiated us
  326.  parentControl = parentControl;
  327. // Remember the gap in drawing the top border
  328.  borderGap = borderGap;
  329. // Remember any currect menu item collection
  330. MenuCommandCollection oldCollection =  menuCommands;
  331. // Use the passed in collection of menu commands
  332.  menuCommands = menuCollection;
  333. // Remember screen positions
  334.  screenPos = screenPos;
  335.  aboveScreenPos = aboveScreenPos;
  336.  leftScreenPos = screenPos;
  337. MenuCommand ret = InternalTrackPopup(selectFirst);
  338. // Restore to original collection
  339.  menuCommands = oldCollection;
  340. // Remove reference no longer required
  341.  parentControl = null;
  342. // Return the direction key that caused dismissal
  343. returnDir =  returnDir;
  344. return ret;
  345. }
  346. protected MenuCommand InternalTrackPopup(Point screenPosTR, Point screenPosTL, 
  347.  MenuCommandCollection menuCollection, 
  348.  PopupMenu parentMenu, bool selectFirst, 
  349.  MenuControl parentControl, bool popupRight,
  350.  bool popupDown, ref int returnDir)
  351. {
  352. // Default the drawing direction
  353.  direction = Direction.Horizontal;
  354. // Remember the MenuControl that initiated us
  355.  parentControl = parentControl;
  356. // We have a parent popup menu that should be consulted about operation
  357.  parentMenu = parentMenu;
  358. // Remember any currect menu item collection
  359. MenuCommandCollection oldCollection =  menuCommands;
  360. // Use the passed in collection of menu commands
  361.  menuCommands = menuCollection;
  362. // Remember screen positions
  363.  screenPos = screenPosTR;
  364.  aboveScreenPos = screenPosTR;
  365.  leftScreenPos = screenPosTL;
  366. // Remember display directions
  367.  popupRight = popupRight;
  368.  popupDown = popupDown;
  369. MenuCommand ret = InternalTrackPopup(selectFirst);
  370. // Restore to original collection
  371.  menuCommands = oldCollection;
  372. // Remove references no longer required
  373.  parentControl = null;
  374.  parentMenu = null;
  375. // Return the direction key that caused dismissal
  376. returnDir =  returnDir;
  377. return ret;
  378. }
  379. protected MenuCommand InternalTrackPopup(bool selectFirst)
  380. {
  381. // MenuCommand to return as method result
  382.  returnCommand = null;
  383. // No item is being tracked
  384.  trackItem = -1;
  385. // Flag to indicate when to exit the message loop
  386.  exitLoop = false;
  387. // Assume the mouse does not start over our window
  388.  mouseOver = false;
  389. // Direction of key press if this caused dismissal
  390.  returnDir = 0;
  391. // Flag to indicate if the message should be dispatched
  392. bool leaveMsg = false;
  393. // Create and show the popup window (without taking the focus)
  394. CreateAndShowWindow();
  395. // Create an object for storing windows message information
  396. Win32.MSG msg = new Win32.MSG();
  397. // Draw everything now...
  398. //RefreshAllCommands();
  399. // Pretend user pressed key down to get the first valid item selected
  400. if (selectFirst)
  401. ProcessKeyDown();
  402. // Process messages until exit condition recognised
  403. while(! exitLoop)
  404. {
  405. // Suspend thread until a windows message has arrived
  406. if (WindowsAPI.WaitMessage())
  407. {
  408. // Take a peek at the message details without removing from queue
  409. while(! exitLoop && WindowsAPI.PeekMessage(ref msg, 0, 0, 0, Win32.PeekMessageFlags.PM_NOREMOVE))
  410. {
  411. //Console.WriteLine("Track {0} {1}", this.Handle, ((Msg)msg.message).ToString());
  412. //Console.WriteLine("Message is for {0 }", msg.hwnd); 
  413. // Leave messages for children
  414. IntPtr hParent = WindowsAPI.GetParent(msg.hwnd);
  415. bool child = hParent == Handle;
  416. bool combolist = IsComboBoxList(msg.hwnd);
  417. // Mouse was pressed in a window of this application
  418. if ((msg.message == (int)Msg.WM_LBUTTONDOWN) ||
  419. (msg.message == (int)Msg.WM_MBUTTONDOWN) ||
  420. (msg.message == (int)Msg.WM_RBUTTONDOWN) ||
  421. (msg.message == (int)Msg.WM_NCLBUTTONDOWN) ||
  422. (msg.message == (int)Msg.WM_NCMBUTTONDOWN) ||
  423. (msg.message == (int)Msg.WM_NCRBUTTONDOWN))
  424. {
  425. // Is the mouse event for this popup window?
  426. if (msg.hwnd != this.Handle)
  427. {
  428. // Let the parent chain of PopupMenu's decide if they want it
  429. if (!ParentWantsMouseMessage(ref msg)&& !child && !combolist)
  430. {
  431. // No, then we need to exit the popup menu tracking
  432.  exitLoop = true;
  433. // DO NOT process the message, leave it on the queue
  434. // and let the real destination window handle it.
  435. leaveMsg = true;
  436. // Is a parent control specified?
  437. if ( parentControl != null)
  438. {
  439. // Is the mouse event destination the parent control?
  440. if (msg.hwnd ==  parentControl.Handle)
  441. {
  442. // Then we want to consume the message so it does not get processed 
  443. // by the parent control. Otherwise, pressing down will cause this 
  444. // popup to disappear but the message will then get processed by 
  445. // the parent and cause a popup to reappear again. When we actually
  446. // want the popup to disappear and nothing more.
  447. leaveMsg = false;
  448. }
  449. }
  450. }
  451. }
  452. }
  453. else
  454. {
  455. // Mouse move occured
  456. if (msg.message == (int)Msg.WM_MOUSEMOVE) 
  457. {
  458. // Is the mouse event for this popup window?
  459. if (msg.hwnd != this.Handle)
  460. {
  461. // Do we still think the mouse is over our window?
  462. if ( mouseOver)
  463. {
  464. // Process mouse leaving situation
  465. OnWM_MOUSELEAVE();
  466. }
  467. // Let the parent chain of PopupMenu's decide if they want it
  468. if (!ParentWantsMouseMessage(ref msg) && !child && !combolist)
  469. {
  470. // Eat the message to prevent the destination getting it
  471. Win32.MSG eat = new Win32.MSG();
  472. WindowsAPI.GetMessage(ref eat, 0, 0, 0);
  473. // Do not attempt to pull a message off the queue as it has already
  474. // been eaten by us in the above code
  475. leaveMsg = true;
  476. }
  477. }
  478. }
  479. else
  480. {
  481. // Was the alt key pressed?
  482. if (msg.message == (int)Msg.WM_SYSKEYDOWN)
  483. {
  484. // Alt key pressed on its own
  485. if((int)msg.wParam == (int)Win32.VirtualKeys.VK_MENU) // ALT key
  486. {
  487. // Then we should dimiss ourself
  488.  exitLoop = true;
  489. }
  490. }
  491. // Was a key pressed?
  492. if (msg.message == (int)Msg.WM_KEYDOWN)
  493. {
  494. switch((int)msg.wParam)
  495. {
  496. case (int)Win32.VirtualKeys.VK_UP:
  497. ProcessKeyUp();
  498. break;
  499. case (int)Win32.VirtualKeys.VK_DOWN:
  500. ProcessKeyDown();
  501. break;
  502. case (int)Win32.VirtualKeys.VK_LEFT:
  503. ProcessKeyLeft();
  504. break;
  505. case (int)Win32.VirtualKeys.VK_RIGHT:
  506. if(ProcessKeyRight())
  507. {
  508. // Do not attempt to pull a message off the queue as the
  509. // ProcessKeyRight has eaten the message for us
  510. leaveMsg = true;
  511. }
  512. break;
  513. case (int)Win32.VirtualKeys.VK_RETURN:
  514. // Is an item currently selected
  515. if ( trackItem != -1)
  516. {
  517. DrawCommand dc =  drawCommands[ trackItem] as DrawCommand;
  518. // Does this item have a submenu?
  519. if (dc.SubMenu)
  520. {
  521. // Consume the keyboard message to prevent the submenu immediately
  522. // processing the same message again. Remember this routine is called
  523. // after PeekMessage but the message is still on the queue at this point
  524. Win32.MSG eat = new Win32.MSG();
  525. WindowsAPI.GetMessage(ref eat, 0, 0, 0);
  526. // Handle the submenu
  527. OperateSubMenu( trackItem, false);
  528. // Do not attempt to pull a message off the queue as it has already
  529. // been eaten by us in the above code
  530. leaveMsg = true;
  531. }
  532. else
  533. {
  534. // Is this item the expansion command?
  535. if (dc.Expansion)
  536. {
  537. RegenerateExpansion();
  538. }
  539. else
  540. {
  541. // Define the selection to return to caller
  542.  returnCommand = dc.MenuCommand;
  543. // Finish processing messages
  544.  exitLoop = true;
  545. }
  546. }
  547. }
  548. break;
  549. case (int)Win32.VirtualKeys.VK_ESCAPE:
  550. // User wants to exit the menu, so set the flag to exit the message loop but 
  551. // let the message get processed. This way the key press is thrown away.
  552.  exitLoop = true;
  553. break;
  554. default:
  555. // Any other key is treated as a possible mnemonic
  556. int selectItem = ProcessMnemonicKey((char)msg.wParam);
  557. if (selectItem != -1)
  558. {
  559. DrawCommand dc =  drawCommands[selectItem] as DrawCommand;
  560. // Define the selection to return to caller
  561.  returnCommand = dc.MenuCommand;
  562. // Finish processing messages
  563.  exitLoop = true;
  564. }
  565. break;
  566. }
  567. }
  568. }
  569. }
  570. // Should the message we pulled from the queue?
  571. if (!leaveMsg)
  572. {
  573. if (WindowsAPI.GetMessage(ref msg, 0, 0, 0))
  574. {
  575. WindowsAPI.TranslateMessage(ref msg);
  576. WindowsAPI.DispatchMessage(ref msg);
  577. }
  578. }
  579. else
  580. leaveMsg = false;
  581. }
  582. }
  583. }
  584. // Do we have a focus we need to restore?
  585. if ( oldFocus != IntPtr.Zero)
  586. ReturnTheFocus();
  587. // Need to unset this window as the parent of the comboboxes
  588. // -- if any -- otherwise the combobox use in an toolbar would get "sick"
  589. UnsetComboBoxesParent();
  590. // Hide the window from view before killing it, as sometimes there is a
  591. // short delay between killing it and it disappearing because of the time
  592. // it takes for the destroy messages to get processed
  593. WindowsAPI.ShowWindow(this.Handle, Win32.ShowWindowStyles.SW_HIDE);
  594. // Commit suicide
  595. DestroyHandle();
  596. // Was a command actually selected?
  597. if (( parentMenu == null) && ( returnCommand != null))
  598. {
  599. // Pulse the selected event for the command
  600.  returnCommand.OnClick(EventArgs.Empty);
  601. }
  602. return  returnCommand;
  603. }
  604. protected void CreateAndShowWindow()
  605. {
  606. // Decide if we need layered windows
  607. layered = ( supportsLayered && ( style == VisualStyle.IDE));
  608. // Don't use layered windows because we would need to do more work
  609. // to paint the comboboxes
  610. layered = false;
  611. // Process the menu commands to determine where each one needs to be
  612. // drawn and return the size of the window needed to display it.
  613. Size winSize = GenerateDrawPositions();
  614. Point screenPos = CorrectPositionForScreen(winSize);
  615. CreateParams cp = new CreateParams();
  616. // Any old title will do as it will not be shown
  617. cp.Caption = "NativePopupMenu";
  618. // Define the screen position/size
  619. cp.X = screenPos.X;
  620. cp.Y = screenPos.Y;
  621. cp.Height = winSize.Height;
  622. cp.Width = winSize.Width;
  623. // As a top-level window it has no parent
  624. cp.Parent = IntPtr.Zero;
  625. // Appear as a top-level window
  626. cp.Style = unchecked((int)(uint)Win32.WindowStyles.WS_POPUP |
  627. (int)(uint)Win32.WindowStyles.WS_CLIPCHILDREN);
  628. // Set styles so that it does not have a caption bar and is above all other 
  629. // windows in the ZOrder, i.e. TOPMOST
  630. cp.ExStyle = (int)Win32.WindowExStyles.WS_EX_TOPMOST + (int)Win32.WindowExStyles.WS_EX_TOOLWINDOW;
  631. // OS specific style
  632. if (layered)
  633. {
  634. // If not on NT then we are going to use alpha blending on the shadow border
  635. // and so we need to specify the layered window style so the OS can handle it
  636. cp.ExStyle += (int)Win32.WindowExStyles.WS_EX_LAYERED;
  637. }
  638. // Is this the plain style of appearance?
  639. if ( style == VisualStyle.Plain)
  640. {
  641. // We want the tradiditonal 3D border
  642. cp.Style += unchecked((int)(uint)Win32.WindowStyles.WS_DLGFRAME);
  643. }
  644. // Create the actual window
  645. this.CreateHandle(cp);
  646. // Update the window clipping region
  647. if (!layered)
  648. SetWindowRegion(winSize);
  649.             // Make sure comboboxes are the children of this window
  650.     SetComboBoxesParent();
  651. // Show the window without activating it (i.e. do not take focus)
  652. WindowsAPI.ShowWindow(this.Handle, Win32.ShowWindowStyles.SW_SHOWNOACTIVATE);
  653. if (layered)
  654. {
  655. // Remember the correct screen drawing details
  656.  currentPoint = screenPos;
  657.  currentSize = winSize;
  658. // Update the image for display
  659. UpdateLayeredWindow();
  660. // Must grab the focus immediately
  661. if ( grabFocus)
  662. GrabTheFocus();
  663. }
  664. }
  665. void SetComboBoxesParent()
  666. {
  667. foreach(MenuCommand command in  menuCommands)
  668. {
  669.                 // no need for recursion, comboboxes are only 
  670. // on the first level
  671. if ( command.ComboBox != null)
  672. {
  673. WindowsAPI.SetParent(command.ComboBox.Handle, Handle);
  674. }
  675. }
  676. }
  677. void UnsetComboBoxesParent()
  678. {
  679. foreach(MenuCommand command in  menuCommands)
  680. {
  681. // no need for recursion, comboboxes are only 
  682. // on the first level
  683. if ( command.ComboBox != null)
  684. {
  685. command.ComboBox.Visible = false;
  686. WindowsAPI.SetParent(command.ComboBox.Handle, IntPtr.Zero);
  687. }
  688. }
  689. }
  690. bool IsComboBoxList(IntPtr hWnd)
  691. {
  692. StringBuilder className = new StringBuilder(80);
  693. WindowsAPI.GetClassName(hWnd, className, 80);
  694. if ( className.ToString() == "ComboLBox" )
  695. return true;
  696. return false;
  697. }
  698. protected void UpdateLayeredWindow()
  699. {
  700. UpdateLayeredWindow( currentPoint,  currentSize);
  701. }
  702. protected void UpdateLayeredWindow(Point point, Size size)
  703. {
  704. // Create bitmap for drawing onto
  705. Bitmap memoryBitmap = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppArgb);
  706. using(Graphics g = Graphics.FromImage(memoryBitmap))
  707. {
  708. Rectangle area = new Rectangle(0, 0, size.Width, size.Height);
  709. // Draw the background area
  710. DrawBackground(g, area);
  711. // Draw the actual menu items
  712. DrawAllCommands(g);
  713. // Get hold of the screen DC
  714. IntPtr hDC = WindowsAPI.GetDC(IntPtr.Zero); // Create a memory based DC compatible with the screen DC IntPtr memoryDC = WindowsAPI.CreateCompatibleDC(hDC); // Get access to the bitmap handle contained in the Bitmap object IntPtr hBitmap = memoryBitmap.GetHbitmap(Color.FromArgb(0)); // Select this bitmap for updating the window presentation IntPtr oldBitmap = WindowsAPI.SelectObject(memoryDC, hBitmap); // New window size Win32.SIZE ulwsize; ulwsize.cx = size.Width; ulwsize.cy = size.Height; // New window position Win32.POINT topPos; topPos.x = point.X; topPos.y = point.Y; // Offset into memory bitmap is always zero Win32.POINT pointSource; pointSource.x = 0; pointSource.y = 0; // We want to make the entire bitmap opaque  Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION(); blend.BlendOp             = (byte)BlendFlags.AC_SRC_OVER; blend.BlendFlags          = 0; blend.SourceConstantAlpha = 255; blend.AlphaFormat         = (byte)BlendFlags.AC_SRC_ALPHA; // Tell operating system to use our bitmap for painting WindowsAPI.UpdateLayeredWindow(Handle, hDC, ref topPos, ref ulwsize,     memoryDC, ref pointSource, 0, ref blend,     Win32.UpdateLayeredWindowFlags.ULW_ALPHA);
  715. // Put back the old bitmap handle
  716. WindowsAPI.SelectObject(memoryDC, oldBitmap); // Cleanup resources WindowsAPI.ReleaseDC(IntPtr.Zero, hDC); WindowsAPI.DeleteObject(hBitmap); WindowsAPI.DeleteDC(memoryDC); } }
  717. protected void SetWindowRegion(Size winSize)
  718. {
  719. // Style specific handling
  720. if ( style == VisualStyle.IDE)
  721. {
  722. int shadowHeight = position[(int) style, (int)PI.ShadowHeight];
  723. int shadowWidth = position[(int) style, (int)PI.ShadowWidth];
  724. // Create a new region object
  725. Region drawRegion = new Region();
  726. // Can draw anywhere
  727. drawRegion.MakeInfinite();
  728. // Remove the area above the right hand shadow
  729. drawRegion.Xor(new Rectangle(winSize.Width - shadowWidth, 0, shadowWidth, shadowHeight));
  730. // When drawing upwards from a vertical menu we need to allow a connection between the 
  731. // MenuControl selection box and the PopupMenu shadow
  732. if (!(( direction == Direction.Vertical) && ! excludeTop))
  733. {
  734. // Remove the area left of the bottom shadow
  735. drawRegion.Xor(new Rectangle(0, winSize.Height - shadowHeight, shadowWidth, shadowHeight));
  736. }
  737. // Define a region to prevent drawing over exposed corners of shadows
  738. using(Graphics g = Graphics.FromHwnd(this.Handle))
  739. WindowsAPI.SetWindowRgn(this.Handle, drawRegion.GetHrgn(g), false);
  740. }
  741. }
  742. protected Point CorrectPositionForScreen(Size winSize)
  743. {
  744. int screenWidth = SystemInformation.WorkingArea.Width;
  745. int screenHeight = SystemInformation.WorkingArea.Height;
  746. // Default to excluding menu border from top
  747.  excludeTop = true;
  748.  excludeOffset = 0;
  749. // Calculate the downward position first
  750. if ( popupDown)
  751. {
  752. // Ensure the end of the menu is not off the bottom of the screen
  753. if ((screenPos.Y + winSize.Height) > screenHeight)
  754. {
  755. // If the parent control exists then try and position upwards instead
  756. if (( parentControl != null) && ( parentMenu == null))
  757. {
  758. // Is there space above the required position?
  759. if (( aboveScreenPos.Y - winSize.Height) > 0)
  760. {
  761. // Great...do that instead
  762. screenPos.Y =  aboveScreenPos.Y - winSize.Height;
  763. // Reverse direction of drawing this and submenus
  764.  popupDown = false;
  765. // Remember to exclude border from bottom of menu and not the top
  766.  excludeTop = false;
  767. // Inform parent it needs to redraw the selection upwards
  768.  parentControl.DrawSelectionUpwards();
  769. }
  770. }
  771. // Did the above logic still fail?
  772. if ((screenPos.Y + winSize.Height) > screenHeight)
  773. {
  774. // If not a top level PopupMenu then..
  775. if ( parentMenu != null)
  776. {
  777. // Reverse direction of drawing this and submenus
  778.  popupDown = false;
  779. // Is there space above the required position?
  780. if (( aboveScreenPos.Y - winSize.Height) > 0)
  781. screenPos.Y =  aboveScreenPos.Y - winSize.Height;
  782. else
  783. screenPos.Y = 0;
  784. }
  785. else
  786. screenPos.Y = screenHeight - winSize.Height - 1;
  787. }
  788. }
  789. }
  790. else
  791. {
  792. // Ensure the end of the menu is not off the top of the screen
  793. if ((screenPos.Y - winSize.Height) < 0)
  794. {
  795. // Reverse direction
  796.  popupDown = true;
  797. // Is there space below the required position?
  798. if ((screenPos.Y + winSize.Height) > screenHeight)
  799. screenPos.Y = screenHeight - winSize.Height - 1;
  800. }
  801. else
  802. screenPos.Y -= winSize.Height;
  803. }
  804. // Calculate the across position next
  805. if ( popupRight)
  806. {
  807. // Ensure that right edge of menu is not off right edge of screen
  808. if ((screenPos.X + winSize.Width) > screenWidth)
  809. {
  810. // If not a top level PopupMenu then...
  811. if ( parentMenu != null)
  812. {
  813. // Reverse direction
  814.  popupRight = false;
  815. // Adjust across position
  816. screenPos.X =  leftScreenPos.X - winSize.Width;
  817. if (screenPos.X < 0)
  818. screenPos.X = 0;
  819. }
  820. else
  821. {
  822. // Find new position of X coordinate
  823. int newX = screenWidth - winSize.Width - 1;
  824. // Modify the adjust needed when drawing top/bottom border
  825.  excludeOffset = screenPos.X - newX;
  826. // Use new position for popping up menu
  827. screenPos.X = newX;
  828. }
  829. }
  830. }
  831. else
  832. {
  833. // Start by using the left screen pos instead
  834. screenPos.X =  leftScreenPos.X;
  835. // Ensure the left edge of the menu is not off the left of the screen
  836. if ((screenPos.X - winSize.Width) < 0)
  837. {
  838. // Reverse direction
  839.  popupRight = true;
  840. // Is there space below the required position?
  841. if (( screenPos.X + winSize.Width) > screenWidth)
  842. screenPos.X = screenWidth - winSize.Width - 1;
  843. else
  844. screenPos.X =  screenPos.X;
  845. }
  846. else
  847. screenPos.X -= winSize.Width;
  848. }
  849. return screenPos;
  850. }
  851. protected void RegenerateExpansion()
  852. {
  853. // Remove all existing draw commands
  854.  drawCommands.Clear();
  855. // Move into the expanded mode
  856.  showInfrequent = true;
  857. // Generate new ones
  858. Size newSize = GenerateDrawPositions();
  859. // Find the new screen location for the window
  860. Point newPos = CorrectPositionForScreen(newSize);
  861. // Update the window clipping region
  862. if (!layered)
  863. {
  864. SetWindowRegion(newSize);
  865. // Alter size and location of window
  866. WindowsAPI.MoveWindow(this.Handle, newPos.X, newPos.Y, newSize.Width, newSize.Height, true);
  867. }
  868. else
  869. {
  870. // Remember the correct screen drawing details
  871.  currentPoint = newPos;
  872.  currentSize = newSize;
  873. // Update the image for display
  874. UpdateLayeredWindow();
  875. }
  876. // Lets repaint everything
  877. RefreshAllCommands();
  878. }
  879. protected Size GenerateDrawPositions()
  880. {
  881. // Create a collection of drawing objects
  882.  drawCommands = new ArrayList();
  883. // Calculate the minimum cell width and height
  884. int cellMinHeight = position[(int) style, (int)PI.ImageGapTop] +
  885. imageHeight + 
  886. position[(int) style, (int)PI.ImageGapBottom];
  887. int cellMinWidth = position[(int) style, (int)PI.ImageGapLeft] +
  888.    imageWidth + 
  889.    position[(int) style, (int)PI.ImageGapRight] + 
  890.    position[(int) style, (int)PI.TextGapLeft] +
  891.    position[(int) style, (int)PI.TextGapRight] +
  892.    position[(int) style, (int)PI.SubMenuGapLeft] +
  893.    position[(int) style, (int)PI.SubMenuWidth] +
  894.    position[(int) style, (int)PI.SubMenuGapRight];
  895. // Find cell height needed to draw text
  896. int textHeight =  textFont.Height;
  897. // If height needs to be more to handle image then use image height
  898. if (textHeight < cellMinHeight)
  899. textHeight = cellMinHeight;
  900. // Make sure no column in the menu is taller than the screen
  901. int screenHeight = SystemInformation.WorkingArea.Height;
  902. // Define the starting positions for calculating cells
  903. int xStart = position[(int) style, (int)PI.BorderLeft];
  904. int yStart = position[(int) style, (int)PI.BorderTop];
  905. int yPosition = yStart;
  906. // Largest cell for column defaults to minimum cell width
  907. int xColumnMaxWidth = cellMinWidth;
  908. int xPreviousColumnWidths = 0;
  909. int xMaximumColumnHeight = 0;
  910. // Track the row/col of each cell
  911. int row = 0;
  912. int col = 0;
  913. // Are there any infrequent items
  914. bool infrequent = false;
  915. // Get hold of the DC for the desktop
  916. IntPtr hDC = WindowsAPI.GetDC(IntPtr.Zero);
  917. // Contains the collection of items in the current column
  918. ArrayList columnItems = new ArrayList();
  919. using(Graphics g = Graphics.FromHdc(hDC))
  920. {
  921. // Handle any extra text drawing
  922. if ( menuCommands.ExtraText.Length > 0)
  923. {
  924. // Calculate the column width needed to show this text
  925. SizeF dimension = g.MeasureString( menuCommands.ExtraText,  menuCommands.ExtraFont);
  926. // Always add 1 to ensure that rounding is up and not down
  927. int extraHeight = (int)dimension.Height + 1;
  928. // Find the total required as the text requirement plus style specific spacers
  929.  extraSize = extraHeight + 
  930.  position[(int) style, (int)PI.ExtraRightGap] +
  931.  position[(int) style, (int)PI.ExtraWidthGap] * 2;
  932. // Push first column of items across from the extra text
  933. xStart +=  extraSize;
  934. // Add this extra width to the total width of the window
  935. xPreviousColumnWidths =  extraSize;
  936. }
  937. foreach(MenuCommand command in  menuCommands)
  938. {
  939. // Give the command a chance to update its state
  940. command.OnUpdate(EventArgs.Empty);
  941. // Ignore items that are marked as hidden
  942. if (!command.Visible)
  943. continue;
  944. // If this command has menu items (and so it a submenu item) then check
  945. // if any of the submenu items are visible. If none are visible then there
  946. // is no point in showing this submenu item
  947. if ((command.MenuCommands.Count > 0) && (!command.MenuCommands.VisibleItems()))
  948. continue;
  949. // Ignore infrequent items unless flag set to show them
  950. if (command.Infrequent && ! showInfrequent)
  951. {
  952. infrequent = true;
  953. continue;
  954. }
  955. int cellWidth = 0;
  956. int cellHeight = 0;
  957. // Shift across to the next column?
  958. if (command.Break)
  959. {
  960. // Move row/col tracking to the next column
  961. row = 0;
  962. col++;
  963. // Apply cell width to the current column entries
  964. ApplySizeToColumnList(columnItems, xColumnMaxWidth);
  965. // Move cell position across to start of separator position
  966. xStart += xColumnMaxWidth;
  967. // Get width of the separator area
  968. int xSeparator = position[(int) style, (int)PI.SeparatorWidth];
  969. DrawCommand dcSep = new DrawCommand(new Rectangle(xStart, 0, xSeparator, 0), false);
  970. // Add to list of items for drawing
  971.  drawCommands.Add(dcSep);
  972. // Move over the separator
  973. xStart += xSeparator;
  974. // Reset cell position to top of column
  975. yPosition = yStart;
  976. // Accumulate total width of previous columns
  977. xPreviousColumnWidths += xColumnMaxWidth + xSeparator;
  978. // Largest cell for column defaults to minimum cell width
  979. xColumnMaxWidth  = cellMinWidth;
  980. }
  981. // Is this a horizontal separator?
  982. if (command.Text == "-")
  983. {
  984. cellWidth = cellMinWidth;
  985. cellHeight = position[(int) style, (int)PI.SeparatorHeight];
  986. }
  987. else
  988. {
  989. // Use precalculated height
  990. cellHeight = textHeight;
  991. // Calculate the text width portion of the cell
  992. SizeF dimension = g.MeasureString(command.Text,  textFont);
  993. // Always add 1 to ensure that rounding is up and not down
  994. cellWidth = cellMinWidth + (int)dimension.Width + 1;
  995. // Does the menu command have a shortcut defined?
  996. if (command.Shortcut != Shortcut.None)
  997. {
  998. // Find the width of the shortcut text
  999. dimension = g.MeasureString(GetShortcutText(command.Shortcut),  textFont);
  1000. // Add to the width of the cell
  1001. cellWidth += position[(int) style, (int)PI.ShortcutGap] + (int)dimension.Width + 1;
  1002. }
  1003. // If this is a combobox, then add the combobox dimension
  1004. if ( command.ComboBox != null )
  1005. {
  1006. cellWidth += command.ComboBox.Width;
  1007. }
  1008. }
  1009. // If the new cell expands past the end of the screen...
  1010. if ((yPosition + cellHeight) >= screenHeight)
  1011. {
  1012. // .. then need to insert a column break
  1013. // Move row/col tracking to the next column
  1014. row = 0;
  1015. col++;
  1016. // Apply cell width to the current column entries
  1017. ApplySizeToColumnList(columnItems, xColumnMaxWidth);
  1018. // Move cell position across to start of separator position
  1019. xStart += xColumnMaxWidth;
  1020. // Get width of the separator area
  1021. int xSeparator = position[(int) style, (int)PI.SeparatorWidth];
  1022. DrawCommand dcSep = new DrawCommand(new Rectangle(xStart, yStart, xSeparator, 0), false);
  1023. // Add to list of items for drawing
  1024.  drawCommands.Add(dcSep);
  1025. // Move over the separator
  1026. xStart += xSeparator;
  1027. // Reset cell position to top of column
  1028. yPosition = yStart;
  1029. // Accumulate total width of previous columns
  1030. xPreviousColumnWidths += xColumnMaxWidth + xSeparator;
  1031. // Largest cell for column defaults to minimum cell width
  1032. xColumnMaxWidth  = cellMinWidth;
  1033. }
  1034. // Create a new position rectangle (the width will be reset later once the 
  1035. // width of the column has been determined but the other values are correct)
  1036. Rectangle cellRect = new Rectangle(xStart, yPosition, cellWidth, cellHeight);
  1037. // Create a drawing object
  1038. DrawCommand dc = new DrawCommand(command, cellRect, row, col);
  1039. // Add to list of items for drawing
  1040.  drawCommands.Add(dc);
  1041. // Add to list of items in this column
  1042. columnItems.Add(dc);
  1043. // Remember the biggest cell width in this column
  1044. if (cellWidth > xColumnMaxWidth)
  1045. xColumnMaxWidth = cellWidth;
  1046. // Move down to start of next cell in column
  1047. yPosition += cellHeight;
  1048. // Remember the tallest column in the menu
  1049. if (yPosition > xMaximumColumnHeight)
  1050. xMaximumColumnHeight = yPosition;
  1051. row++;
  1052. }
  1053. // Check if we need to add an infrequent expansion item
  1054. if (infrequent)
  1055. {
  1056. // Create a minimum size cell
  1057. Rectangle cellRect = new Rectangle(xStart, yPosition, cellMinWidth, cellMinHeight);
  1058. // Create a draw command to represent the drawing of the expansion item
  1059. DrawCommand dc = new DrawCommand(cellRect, true);
  1060. // Must be last item
  1061.  drawCommands.Add(dc);
  1062. // Add to list of items in this column
  1063. columnItems.Add(dc);
  1064. yPosition += cellMinHeight;
  1065. // Remember the tallest column in the menu
  1066. if (yPosition > xMaximumColumnHeight)
  1067. xMaximumColumnHeight = yPosition;
  1068. }
  1069. // Apply cell width to the current column entries
  1070. ApplySizeToColumnList(columnItems, xColumnMaxWidth);
  1071. }
  1072. // Must remember to release the HDC resource!
  1073. WindowsAPI.ReleaseDC(IntPtr.Zero, hDC);
  1074. // Find width/height of window
  1075. int windowWidth = position[(int) style, (int)PI.BorderLeft] +
  1076.   xPreviousColumnWidths + 
  1077.   xColumnMaxWidth + 
  1078.   position[(int) style, (int)PI.BorderRight];
  1079. int windowHeight = position[(int) style, (int)PI.BorderTop] +
  1080.   xMaximumColumnHeight + 
  1081.   position[(int) style, (int)PI.BorderBottom];
  1082. // Define the height of the vertical separators
  1083. ApplyVerticalSeparators(xMaximumColumnHeight);
  1084. // Style specific modification of window size
  1085. int xAdd = position[(int) style, (int)PI.ShadowHeight];
  1086. int yAdd = position[(int) style, (int)PI.ShadowWidth];
  1087. if ( style == VisualStyle.Plain)
  1088. {
  1089. xAdd += SystemInformation.Border3DSize.Width * 2;
  1090. yAdd += SystemInformation.Border3DSize.Height * 2;
  1091. }
  1092. return new Size(windowWidth + xAdd, windowHeight + yAdd);
  1093. }
  1094. protected void ApplyVerticalSeparators(int sepHeight)
  1095. {
  1096. // Each vertical separator needs to be the same height, this has already 
  1097. // been calculated and passed in from the tallest column in the menu
  1098. foreach(DrawCommand dc in  drawCommands)
  1099. {
  1100. if (dc.VerticalSeparator)
  1101. {
  1102. // Grab the current drawing rectangle
  1103. Rectangle cellRect = dc.DrawRect;
  1104. // Modify the height to that requested
  1105. dc.DrawRect = new Rectangle(cellRect.Left, cellRect.Top, cellRect.Width, sepHeight);
  1106. }
  1107. }
  1108. }
  1109. protected void ApplySizeToColumnList(ArrayList columnList, int cellWidth)
  1110. {
  1111. // Each cell in the same column needs to be the same width, this has already
  1112. // been calculated and passed in as the widest cell in the column
  1113. foreach(DrawCommand dc in columnList)
  1114. {
  1115. // Grab the current drawing rectangle
  1116. Rectangle cellRect = dc.DrawRect;
  1117. // Modify the width to that requested
  1118. dc.DrawRect = new Rectangle(cellRect.Left, cellRect.Top, cellWidth, cellRect.Height);
  1119. }
  1120. // Clear collection out ready for reuse
  1121. columnList.Clear();
  1122. }
  1123. protected void RefreshAllCommands()
  1124. {
  1125. Win32.RECT rectRaw = new Win32.RECT();
  1126. // Grab the screen rectangle of the window
  1127. WindowsAPI.GetWindowRect(this.Handle, ref rectRaw);
  1128. // Convert from screen to client sizing
  1129. Rectangle rectWin = new Rectangle(0, 0, 
  1130.   rectRaw.right - rectRaw.left, 
  1131.   rectRaw.bottom - rectRaw.top);
  1132. using(Graphics g = Graphics.FromHwnd(this.Handle))
  1133. {
  1134. // Draw the background area
  1135. DrawBackground(g, rectWin);
  1136. // Draw the actual menu items
  1137. DrawAllCommands(g);
  1138. }
  1139. }
  1140. protected void DrawBackground(Graphics g, Rectangle rectWin)
  1141. Rectangle main = new Rectangle(0, 0, 
  1142.    rectWin.Width - 1 - position[(int) style, (int)PI.ShadowWidth], 
  1143.    rectWin.Height - 1 - position[(int) style, (int)PI.ShadowHeight]);
  1144. // Style specific drawing
  1145. switch( style)
  1146. {
  1147. case VisualStyle.IDE:
  1148. // Calculate some common values
  1149. int imageColWidth = position[(int) style, (int)PI.ImageGapLeft] + 
  1150. imageWidth + 
  1151. position[(int) style, (int)PI.ImageGapRight];
  1152. int xStart = position[(int) style, (int)PI.BorderLeft];
  1153. int yStart = position[(int) style, (int)PI.BorderTop];
  1154. int yHeight = main.Height - yStart - position[(int) style, (int)PI.BorderBottom] - 1;
  1155. // Paint the main area background
  1156. using(SolidBrush mainBrush = new SolidBrush(SystemColors.ControlLightLight))
  1157. g.FillRectangle(mainBrush, main);
  1158. // Draw single line border around the main area
  1159. using(Pen mainBorder = new Pen(SystemColors.ControlDark))
  1160. {
  1161. g.DrawRectangle(mainBorder, main);
  1162. // Should the border be drawn with part of the border missing?
  1163. if ( borderGap > 0)
  1164. {
  1165. using(Pen mainControl = new Pen(SystemColors.ControlLight))
  1166. {
  1167. // Remove the appropriate section of the border
  1168. if ( direction == Direction.Horizontal)
  1169. {
  1170. if ( excludeTop)
  1171. g.DrawLine(mainControl, main.Left + 1 +  excludeOffset, 
  1172. main.Top, main.Left +  borderGap +  excludeOffset - 1, main.Top);
  1173. else
  1174. g.DrawLine(mainControl, main.Left + 1 +  excludeOffset, 
  1175. main.Bottom, main.Left +  borderGap +  excludeOffset - 1, main.Bottom);
  1176. }
  1177. else
  1178. {
  1179. if ( excludeTop)
  1180. g.DrawLine(mainControl, main.Left, main.Top + 1 +  excludeOffset, 
  1181. main.Left, main.Top +  borderGap +  excludeOffset - 1);
  1182. else
  1183. g.DrawLine(mainControl, main.Left, main.Bottom - 1 -  excludeOffset, 
  1184. main.Left, main.Bottom -  borderGap -  excludeOffset - 1);
  1185. }
  1186. }
  1187. }
  1188. }
  1189. // Draw the first image column
  1190. Rectangle imageRect = new Rectangle(xStart, yStart, imageColWidth, yHeight);
  1191. g.FillRectangle(SystemBrushes.ControlLight, imageRect);
  1192. // Draw image column after each vertical separator
  1193. foreach(DrawCommand dc in  drawCommands)
  1194. {
  1195. if (dc.Separator && dc.VerticalSeparator)
  1196. {
  1197. // Recalculate starting position (but height remains the same)
  1198. imageRect.X = dc.DrawRect.Right;
  1199. g.FillRectangle(SystemBrushes.ControlLight, imageRect);
  1200. }
  1201. }
  1202. // Draw shadow around borders
  1203. int rightLeft = main.Right + 1;
  1204. int rightTop = main.Top + position[(int) style, (int)PI.ShadowHeight];
  1205. int rightBottom = main.Bottom + 1;
  1206. int leftLeft = main.Left + position[(int) style, (int)PI.ShadowWidth];
  1207. int xExcludeStart = main.Left +  excludeOffset;
  1208. int xExcludeEnd = main.Left +  excludeOffset +  borderGap;
  1209. SolidBrush shadowBrush;
  1210. if (layered)
  1211. shadowBrush = new SolidBrush(Color.FromArgb(64, 0, 0, 0));
  1212. else
  1213. shadowBrush = new SolidBrush(SystemColors.ControlDark);
  1214. if (( borderGap > 0) && (! excludeTop) && ( direction == Direction.Horizontal))
  1215. {
  1216. int rightright = rectWin.Width;
  1217. if (xExcludeStart >= leftLeft)
  1218. g.FillRectangle(shadowBrush, leftLeft, rightBottom, xExcludeStart - leftLeft, position[(int) style, (int)PI.ShadowHeight]);
  1219. if (xExcludeEnd <= rightright)
  1220. g.FillRectangle(shadowBrush, xExcludeEnd, rightBottom, rightright - xExcludeEnd, position[(int) style, (int)PI.ShadowHeight]);
  1221. }
  1222. else
  1223. {
  1224. if (( direction == Direction.Vertical) && (! excludeTop))
  1225. leftLeft = 0;
  1226. g.FillRectangle(shadowBrush, leftLeft, rightBottom, rightLeft, position[(int) style, (int)PI.ShadowHeight]);
  1227. }
  1228. g.FillRectangle(shadowBrush, rightLeft, rightTop, position[(int) style, (int)PI.ShadowWidth], rightBottom - rightTop);
  1229. shadowBrush.Dispose();
  1230. if (( borderGap > 0) && (! excludeTop) && ( direction == Direction.Horizontal))
  1231. {
  1232. using(SolidBrush tabBrush = new SolidBrush(SystemColors.ControlLight))
  1233. g.FillRectangle(tabBrush, new Rectangle(xExcludeStart, rightBottom - 1, 
  1234.     xExcludeEnd - xExcludeStart, 
  1235. position[(int) style, (int)PI.ShadowHeight] + 1));
  1236. // Draw lines connecting the selected MenuControl item to the submenu
  1237. using(Pen connectPen = new Pen(SystemColors.ControlDark))
  1238. {
  1239. g.DrawLine(connectPen, xExcludeStart, rightBottom - 1, xExcludeStart, rectWin.Height);
  1240. g.DrawLine(connectPen, xExcludeEnd, rightBottom - 1, xExcludeEnd, rectWin.Height);
  1241. }
  1242.     }
  1243. break;
  1244. case VisualStyle.Plain:
  1245. // Paint the main area background
  1246. using(SolidBrush mainBrush = new SolidBrush(SystemColors.Control))
  1247. g.FillRectangle(mainBrush, rectWin);
  1248. break;
  1249. }
  1250. // Is there an extra title text to be drawn?
  1251. if ( menuCommands.ExtraText.Length > 0)
  1252. DrawColumn(g, main);
  1253. }
  1254. protected void DrawColumn(Graphics g, Rectangle main)
  1255. {
  1256. // Create the rectangle that encloses the drawing
  1257. Rectangle rectText = new Rectangle(main.Left, main.Top, 
  1258.     extraSize - position[(int) style, (int)PI.ExtraRightGap], 
  1259.    main.Height);
  1260. Brush backBrush = null;
  1261. bool disposeBack = true;
  1262. if ( menuCommands.ExtraBackBrush != null)
  1263. {
  1264. backBrush =  menuCommands.ExtraBackBrush;
  1265. disposeBack = false;
  1266. rectText.Width++;
  1267. }
  1268. else
  1269. backBrush = new SolidBrush( menuCommands.ExtraBackColor);
  1270. // Fill background using brush
  1271. g.FillRectangle(backBrush, rectText);
  1272. // Do we need to dispose of the brush?
  1273. if (disposeBack)
  1274. backBrush.Dispose();
  1275. // Adjust rectangle for drawing the text into
  1276. rectText.X += position[(int) style, (int)PI.ExtraWidthGap];
  1277. rectText.Y += position[(int) style, (int)PI.ExtraHeightGap];
  1278. rectText.Width -= position[(int) style, (int)PI.ExtraWidthGap] * 2;
  1279. rectText.Height -= position[(int) style, (int)PI.ExtraHeightGap] * 2;
  1280. // For Plain style we need to take into account the border sizes
  1281. if ( style == VisualStyle.Plain)
  1282. rectText.Height -= SystemInformation.Border3DSize.Height * 2;
  1283. // Draw the text into this rectangle
  1284. StringFormat format = new StringFormat();
  1285. format.FormatFlags = StringFormatFlags.DirectionVertical | 
  1286.  StringFormatFlags.NoClip | 
  1287.  StringFormatFlags.NoWrap;
  1288. format.Alignment = StringAlignment.Near;
  1289. format.LineAlignment = StringAlignment.Center;
  1290. Brush textBrush = null;
  1291. bool disposeText = true;
  1292. if ( menuCommands.ExtraTextBrush != null)
  1293. {
  1294. textBrush =  menuCommands.ExtraTextBrush;
  1295. disposeText = false;
  1296. }
  1297. else
  1298. textBrush = new SolidBrush( menuCommands.ExtraTextColor);
  1299. // Draw string from bottom of area towards the top using the given Font/Brush
  1300. TextUtil.DrawReverseString(g,  menuCommands.ExtraText,  menuCommands.ExtraFont, rectText, textBrush, format);
  1301. // Do we need to dispose of the brush?
  1302. if (disposeText)
  1303. textBrush.Dispose();
  1304. }
  1305. internal void DrawSingleCommand(Graphics g, DrawCommand dc, bool hotCommand)
  1306. {
  1307. Rectangle drawRect = dc.DrawRect;
  1308. MenuCommand mc = dc.MenuCommand;
  1309. // Remember some often used values
  1310. int textGapLeft = position[(int) style, (int)PI.TextGapLeft];
  1311. int imageGapLeft = position[(int) style, (int)PI.ImageGapLeft];
  1312. int imageGapRight = position[(int) style, (int)PI.ImageGapRight];
  1313. int imageLeft = drawRect.Left + imageGapLeft;
  1314. // Calculate some common values
  1315. int imageColWidth = imageGapLeft + imageWidth + imageGapRight;
  1316. int subMenuWidth = position[(int) style, (int)PI.SubMenuGapLeft] +
  1317.    position[(int) style, (int)PI.SubMenuWidth] +
  1318.    position[(int) style, (int)PI.SubMenuGapRight];
  1319. int subMenuX = drawRect.Right - 
  1320.    position[(int) style, (int)PI.SubMenuGapRight] -
  1321.    position[(int) style, (int)PI.SubMenuWidth];
  1322. // Text drawing rectangle needs to know the right most position for drawing
  1323. // to stop. This is the width of the window minus the relevant values
  1324. int shortCutX = subMenuX - 
  1325. position[(int) style, (int)PI.SubMenuGapLeft] -
  1326. position[(int) style, (int)PI.TextGapRight];
  1327. // Is this item an expansion command?
  1328. if (dc.Expansion)
  1329. {
  1330. Rectangle box = drawRect;
  1331. // In IDE style the box is next to the image column
  1332. if ( style == VisualStyle.IDE)
  1333. {
  1334. // Reduce the box to take into account the column
  1335. box.X += imageColWidth;
  1336. box.Width -= imageColWidth;
  1337. }
  1338. // Find centre for drawing the image
  1339. int xPos = box.Left + ((box.Width - imageHeight) / 2);;
  1340. int yPos = box.Top + ((box.Height - imageHeight) / 2);
  1341. switch( style)
  1342. {
  1343. case VisualStyle.IDE:
  1344. g.FillRectangle(SystemBrushes.ControlLightLight, box);
  1345. break;
  1346. case VisualStyle.Plain:
  1347. g.FillRectangle(SystemBrushes.Control, box);
  1348. break;
  1349. }
  1350. // Should the item look selected
  1351. if (hotCommand)
  1352. {
  1353. switch( style)
  1354. {
  1355. case VisualStyle.IDE:
  1356. Rectangle selectArea = new Rectangle(drawRect.Left + 1, drawRect.Top,
  1357.  drawRect.Width - 3, drawRect.Height - 1);
  1358. using (Pen selectPen = new Pen(ColorUtil.VSNetBorderColor))
  1359. {
  1360. // Draw the selection area white, because we are going to use an alpha brush
  1361. using (SolidBrush whiteBrush = new SolidBrush(Color.White))
  1362. g.FillRectangle(whiteBrush, selectArea);
  1363. using (SolidBrush selectBrush = new SolidBrush(Color.FromArgb(70,ColorUtil.VSNetBorderColor)))
  1364. {
  1365. // Draw the selection area
  1366. g.FillRectangle(selectBrush, selectArea);
  1367. // Draw a border around the selection area
  1368. g.DrawRectangle(selectPen, selectArea);
  1369. }
  1370. }
  1371. break;
  1372. case VisualStyle.Plain:
  1373. // Shrink the box to provide a small border
  1374. box.Inflate(-2, -2);
  1375. // Grab common values
  1376. Color baseColor = SystemColors.Control;
  1377. using (Pen lightPen = new Pen(SystemColors.ControlLightLight),
  1378.            darkPen = new Pen(SystemColors.ControlDarkDark))
  1379. {
  1380. g.DrawLine(lightPen, box.Right, box.Top, box.Left, box.Top);
  1381. g.DrawLine(lightPen, box.Left, box.Top, box.Left, box.Bottom);
  1382. g.DrawLine(darkPen, box.Left, box.Bottom, box.Right, box.Bottom);
  1383. g.DrawLine(darkPen, box.Right, box.Bottom, box.Right, box.Top);
  1384. }
  1385. break;
  1386. }
  1387. }
  1388. else
  1389. {
  1390. switch( style)
  1391. {
  1392. case VisualStyle.IDE:
  1393. // Fill the entire drawing area with white
  1394. using (SolidBrush whiteBrush = new SolidBrush(SystemColors.ControlLightLight))
  1395. g.FillRectangle(whiteBrush, new Rectangle(drawRect.Left + 1, drawRect.Top,
  1396.   drawRect.Width - 1, drawRect.Height));
  1397. Rectangle imageCol = new Rectangle(drawRect.Left, drawRect.Top,
  1398.    imageColWidth, drawRect.Height);
  1399. // Draw the image column background
  1400. g.FillRectangle(SystemBrushes.Control, imageCol);
  1401. break;
  1402. case VisualStyle.Plain:
  1403. g.FillRectangle(SystemBrushes.Control, new Rectangle(drawRect.Left, drawRect.Top,
  1404.  drawRect.Width, drawRect.Height));
  1405. break;
  1406. }
  1407. }
  1408. // Always draw the expansion bitmap
  1409. g.DrawImage(menuImages.Images[(int)ImageIndex.Expansion], xPos, yPos);
  1410. }
  1411. else
  1412. {
  1413. // Is this item a separator?
  1414. if (dc.Separator)
  1415. {
  1416. if (dc.VerticalSeparator)
  1417. {
  1418. switch( style)
  1419. {
  1420. case VisualStyle.IDE:
  1421. // Draw the separator as a single line
  1422. using (Pen separatorPen = new Pen(SystemColors.ControlDark))
  1423. g.DrawLine(separatorPen, drawRect.Left, drawRect.Top, drawRect.Left, drawRect.Bottom);
  1424. break;
  1425. case VisualStyle.Plain:
  1426. Color baseColor = SystemColors.Control;
  1427. ButtonBorderStyle bsInset = ButtonBorderStyle.Inset;
  1428. ButtonBorderStyle bsNone = ButtonBorderStyle.Inset;
  1429. Rectangle sepRect = new Rectangle(drawRect.Left + 1, drawRect.Top, 2, drawRect.Height);
  1430. // Draw the separator as two lines using Inset style
  1431. ControlPaint.DrawBorder(g, sepRect, 
  1432. baseColor, 1, bsInset, baseColor, 0, bsNone,
  1433. baseColor, 1, bsInset, baseColor, 0, bsNone);
  1434. break;
  1435. }
  1436. }
  1437. else
  1438. {
  1439. switch( style)
  1440. {
  1441. case VisualStyle.IDE:
  1442. // Draw the image column background
  1443. Rectangle imageCol = new Rectangle(drawRect.Left, drawRect.Top,
  1444.    imageColWidth,
  1445.    drawRect.Height);
  1446. g.FillRectangle(new SolidBrush(ColorUtil.VSNetBackgroundColor), drawRect);
  1447. g.FillRectangle(new SolidBrush(ColorUtil.VSNetControlColor), imageCol);
  1448.                              
  1449. // Draw a separator
  1450. using (Pen separatorPen = new Pen(Color.FromArgb(75, SystemColors.MenuText)))
  1451. {
  1452. // Draw the separator as a single line
  1453. g.DrawLine(separatorPen, drawRect.Left + imageColWidth + textGapLeft, drawRect.Top + 1,
  1454.    drawRect.Right-4, drawRect.Top + 1); 
  1455.    
  1456. }
  1457. break;
  1458. case VisualStyle.Plain:
  1459. Color baseColor = SystemColors.Control;
  1460. ButtonBorderStyle bsInset = ButtonBorderStyle.Inset;
  1461. ButtonBorderStyle bsNone = ButtonBorderStyle.Inset;
  1462. Rectangle sepRect = new Rectangle(drawRect.Left + 2, drawRect.Top + 1, 
  1463.   drawRect.Width - 4, 2);
  1464. // Draw the separator as two lines using Inset style
  1465. ControlPaint.DrawBorder(g, sepRect, 
  1466. baseColor, 0, bsNone, baseColor, 1, bsInset,
  1467. baseColor, 0, bsNone, baseColor, 1, bsInset);
  1468. break;
  1469. }
  1470. }
  1471. }
  1472. else
  1473. {
  1474. // Should the command be drawn selected?
  1475. if (hotCommand && mc.ComboBox == null )
  1476. {
  1477. switch( style)
  1478. {
  1479. case VisualStyle.IDE:
  1480. Rectangle selectArea = new Rectangle(drawRect.Left + 1, drawRect.Top,
  1481.  drawRect.Width - 3, drawRect.Height - 1);
  1482. using (Pen selectPen = new Pen(ColorUtil.VSNetBorderColor))
  1483. {
  1484. // Draw the selection area white, because we are going to use an alpha brush
  1485. using (SolidBrush whiteBrush = new SolidBrush(Color.White))
  1486. g.FillRectangle(whiteBrush, selectArea);
  1487. using (SolidBrush selectBrush = new SolidBrush(Color.FromArgb(70, ColorUtil.VSNetBorderColor)))
  1488. {
  1489. // Draw the selection area
  1490. g.FillRectangle(selectBrush, selectArea);
  1491. // Draw a border around the selection area
  1492. g.DrawRectangle(selectPen, selectArea);
  1493. }
  1494. }
  1495. break;
  1496. case VisualStyle.Plain:
  1497. using (SolidBrush selectBrush = new SolidBrush(ColorUtil.VSNetBorderColor))
  1498. g.FillRectangle(selectBrush, drawRect);
  1499. break;
  1500. }
  1501. }
  1502. else
  1503. {
  1504. switch( style)
  1505. {
  1506. case VisualStyle.IDE:
  1507. // Fill the entire drawing area with white
  1508. using (SolidBrush whiteBrush = new SolidBrush(ColorUtil.VSNetBackgroundColor))
  1509. g.FillRectangle(whiteBrush, new Rectangle(drawRect.Left + 1, drawRect.Top,
  1510.   drawRect.Width - 1, drawRect.Height));
  1511. Rectangle imageCol = new Rectangle(drawRect.Left, drawRect.Top,
  1512.    imageColWidth, drawRect.Height);
  1513. // Draw the image column background
  1514. g.FillRectangle(new SolidBrush(ColorUtil.VSNetControlColor), imageCol);
  1515.                             // If this is a combobox item, make sure to position the combobox a couple
  1516.                             // of pixel after the icon area 
  1517. if ( mc.ComboBox != null )
  1518. {
  1519. // Combobox will paint itself
  1520. mc.ComboBox.Left = drawRect.Left+imageColWidth + 2;
  1521. mc.ComboBox.Top = drawRect.Top + (drawRect.Height - mc.ComboBox.Height)/2;
  1522. }
  1523. break;
  1524. case VisualStyle.Plain:
  1525. g.FillRectangle(SystemBrushes.Control, new Rectangle(drawRect.Left, drawRect.Top,
  1526.  drawRect.Width, drawRect.Height));
  1527. break;
  1528. }
  1529. }
  1530. int leftPos = drawRect.Left + imageColWidth + textGapLeft;
  1531. // Calculate text drawing rectangle
  1532. Rectangle strRect = new Rectangle(leftPos, drawRect.Top, shortCutX - leftPos, drawRect.Height);
  1533. // Left align the text drawing on a single line centered vertically
  1534. // and process the & character to be shown as an underscore on next character
  1535. StringFormat format = new StringFormat();
  1536. format.FormatFlags = StringFormatFlags.NoClip | StringFormatFlags.NoWrap;
  1537. format.Alignment = StringAlignment.Near;
  1538. format.LineAlignment = StringAlignment.Center;
  1539. format.HotkeyPrefix = HotkeyPrefix.Show;
  1540. SolidBrush textBrush;
  1541. // Create brush depending on enabled state
  1542. if (mc.Enabled)
  1543. {
  1544. if (!hotCommand || ( style == VisualStyle.IDE))
  1545. textBrush = new SolidBrush(SystemColors.MenuText);
  1546. else
  1547. textBrush = new SolidBrush(SystemColors.HighlightText);
  1548. }
  1549. else 
  1550. textBrush = new SolidBrush(SystemColors.GrayText);
  1551. // Helper values used when drawing grayed text in plain style
  1552. Rectangle rectDownRight = strRect;
  1553. rectDownRight.Offset(1,1);
  1554. if (mc.Enabled || ( style == VisualStyle.IDE))
  1555. g.DrawString(mc.Text,  textFont, textBrush, strRect, format);
  1556. else
  1557. {
  1558. // Draw grayed text by drawing white string offset down and right
  1559. using (SolidBrush whiteBrush = new SolidBrush(SystemColors.HighlightText))
  1560. g.DrawString(mc.Text,  textFont, whiteBrush, rectDownRight, format);
  1561. // And then draw in corret color offset up and left
  1562. g.DrawString(mc.Text,  textFont, textBrush, strRect, format);
  1563. }
  1564. if (mc.Shortcut != Shortcut.None)
  1565. {
  1566. // Right align the shortcut drawing
  1567. format.Alignment = StringAlignment.Far;
  1568. if (mc.Enabled || ( style == VisualStyle.IDE))
  1569. {
  1570. // Draw the shortcut text 
  1571. g.DrawString(GetShortcutText(mc.Shortcut),  textFont, textBrush, strRect, format);
  1572. }
  1573. else
  1574. {
  1575. // Draw grayed text by drawing white string offset down and right
  1576. using (SolidBrush whiteBrush = new SolidBrush(SystemColors.HighlightText))
  1577. g.DrawString(GetShortcutText(mc.Shortcut),  textFont, whiteBrush, rectDownRight, format);
  1578. // And then draw in corret color offset up and left
  1579. g.DrawString(GetShortcutText(mc.Shortcut),  textFont, textBrush, strRect, format);
  1580. }
  1581. }
  1582. // The image offset from top of cell is half the space left after
  1583. // subtracting the height of the image from the cell height
  1584. int imageTop = drawRect.Top + (drawRect.Height - imageHeight) / 2;
  1585. Image image = null;
  1586. // Should a check mark be drawn?
  1587. if (mc.Checked)
  1588. {
  1589. switch( style)
  1590. {
  1591. case VisualStyle.IDE:
  1592. Pen boxPen;
  1593. if (mc.Enabled)
  1594. boxPen = new Pen(ColorUtil.VSNetBorderColor);
  1595. else
  1596. boxPen = new Pen(SystemColors.GrayText);
  1597. // Draw the box around the checkmark area
  1598. g.DrawRectangle(boxPen, new Rectangle(imageLeft - 1, imageTop - 1, 
  1599.   imageHeight + 2, imageWidth + 2));
  1600. boxPen.Dispose();
  1601. break;
  1602. case VisualStyle.Plain:
  1603. break;
  1604. }
  1605. // Grab either tick or radio button image
  1606. if (mc.RadioCheck)
  1607. {
  1608. if (hotCommand && ( style == VisualStyle.Plain))
  1609. image = menuImages.Images[(int)ImageIndex.RadioSelected];
  1610. else
  1611. image = menuImages.Images[(int)ImageIndex.Radio];
  1612. }
  1613. else
  1614. {
  1615. if (hotCommand && ( style == VisualStyle.Plain))
  1616. image = menuImages.Images[(int)ImageIndex.CheckSelected];
  1617. else
  1618. image = menuImages.Images[(int)ImageIndex.Check];
  1619. }
  1620. }
  1621. else
  1622. {
  1623. try
  1624. {
  1625. // Is there an image available to be drawn?
  1626. if ((mc.ImageList != null) && (mc.ImageIndex >= 0))
  1627. image = mc.ImageList.Images[mc.ImageIndex];
  1628. else if ( mc.Image != null)
  1629. image = mc.Image;
  1630. }
  1631. catch(Exception)
  1632. {
  1633. // User supplied ImageList/ImageIndex are invalid, use an error image instead
  1634. image = menuImages.Images[(int)ImageIndex.ImageError];
  1635. }
  1636. }
  1637. // Is there an image to be drawn?
  1638. if (image != null)
  1639. {
  1640. if (mc.Enabled)
  1641. {
  1642. if ((hotCommand) && (!mc.Checked) && ( style == VisualStyle.IDE))
  1643. {
  1644. // Draw a disabled icon offset down and right
  1645. ControlPaint.DrawImageDisabled(g, image, imageLeft + 1, imageTop + 1, 
  1646.    SystemColors.HighlightText);
  1647. // Draw an enabled icon offset up and left
  1648. g.DrawImage(image, imageLeft - 1, imageTop - 1);
  1649. }
  1650. else
  1651. {
  1652. // Draw an enabled icon
  1653. g.DrawImage(image, imageLeft, imageTop);
  1654. }
  1655. }
  1656. else
  1657. {
  1658. // Draw a image disabled
  1659. ControlPaint.DrawImageDisabled(g, image, imageLeft, imageTop, 
  1660.    SystemColors.HighlightText);
  1661. }
  1662. }
  1663. // Does the menu have a submenu defined?
  1664. if (dc.SubMenu)
  1665. {
  1666. // Is the item enabled?
  1667. if (mc.Enabled)
  1668. {
  1669. int subMenuIndex = (int)ImageIndex.SubMenu;
  1670. if (hotCommand && ( style == VisualStyle.Plain))
  1671. subMenuIndex = (int)ImageIndex.SubMenuSelected;
  1672. // Draw the submenu arrow 
  1673. g.DrawImage(menuImages.Images[subMenuIndex], subMenuX, imageTop);
  1674. }
  1675. else
  1676. {
  1677. // Draw a image disabled
  1678. ControlPaint.DrawImageDisabled(g, menuImages.Images[(int)ImageIndex.SubMenu], 
  1679.    subMenuX, imageTop, SystemColors.HighlightText);
  1680. }
  1681. }
  1682. }
  1683. }
  1684. }
  1685. protected void DrawAllCommands(Graphics g)
  1686. {
  1687. for(int i=0; i< drawCommands.Count; i++)
  1688. {
  1689. // Grab some commonly used values
  1690. DrawCommand dc =  drawCommands[i] as DrawCommand;
  1691. // Draw this command only
  1692. DrawSingleCommand(g, dc, (i ==  trackItem));
  1693. }
  1694. }
  1695. protected string GetShortcutText(Shortcut shortcut)
  1696. {
  1697. // Get the key code
  1698. char keycode = (char)((int)shortcut & 0x0000FFFF);
  1699. // The type converter does not work for numeric values as it returns
  1700. // Alt+D0 instad of Alt+0. So check for numeric keys and construct the
  1701. // return string ourself.
  1702. if ((keycode >= '0') && (keycode <= '9'))
  1703. {
  1704. string display = "";
  1705. // Get the modifier
  1706. int modifier = (int)((int)shortcut & 0xFFFF0000);
  1707. if ((modifier & 0x00010000) != 0)
  1708. display += "Shift+";
  1709. if ((modifier & 0x00020000) != 0)
  1710. display += "Ctrl+";
  1711. if ((modifier & 0x00040000) != 0)
  1712. display += "Alt+";
  1713. display += keycode;
  1714. return display;
  1715. }
  1716. return TypeDescriptor.GetConverter(typeof(Keys)).ConvertToString((Keys)shortcut);
  1717. }
  1718. protected bool ProcessKeyUp()
  1719. {
  1720. int newItem =  trackItem;
  1721. int startItem = newItem;
  1722. for(int i=0; i< drawCommands.Count; i++)
  1723. {
  1724. // Move to previous item
  1725. newItem--;
  1726. // Have we looped all the way around all the choices
  1727. if (newItem == startItem)
  1728. return false;
  1729. // Check limits
  1730. if (newItem < 0)
  1731. newItem =  drawCommands.Count - 1;
  1732. DrawCommand dc =  drawCommands[newItem] as DrawCommand;
  1733. // Can we select this item?
  1734. if (!dc.Separator && dc.Enabled)
  1735. {
  1736. // If a change has occured
  1737. if (newItem !=  trackItem)
  1738. {
  1739. // Modify the display of the two items 
  1740. SwitchSelection( trackItem, newItem, false, false);
  1741. return true;
  1742. }
  1743. }
  1744. }
  1745. return false;
  1746. }
  1747. protected bool ProcessKeyDown()
  1748. {
  1749. int newItem =  trackItem;
  1750. int startItem = newItem;
  1751. for(int i=0; i< drawCommands.Count; i++)
  1752. {
  1753. // Move to previous item
  1754. newItem++;
  1755. // Check limits
  1756. if (newItem >=  drawCommands.Count)
  1757. newItem = 0;
  1758. DrawCommand dc =  drawCommands[newItem] as DrawCommand;
  1759. // Can we select this item?
  1760. if (!dc.Separator && dc.Enabled)
  1761. {
  1762. // If a change has occured
  1763. if (newItem !=  trackItem)
  1764. {
  1765. // Modify the display of the two items 
  1766. SwitchSelection( trackItem, newItem, false, false);
  1767. return true;
  1768. }
  1769. }
  1770. }
  1771. return false;
  1772. }
  1773. protected void ProcessKeyLeft()
  1774. {
  1775. if ( trackItem != -1)
  1776. {
  1777. // Get the col this item is in
  1778. DrawCommand dc =  drawCommands[ trackItem] as DrawCommand;
  1779. // Grab the current column/row values
  1780. int col = dc.Col;
  1781. int row = dc.Row;
  1782. // If not in the first column then move left one
  1783. if (col > 0)
  1784. {
  1785. int newItem = -1;
  1786. int newRow = -1;
  1787. int findCol = col - 1;
  1788. DrawCommand newDc = null;
  1789. for(int i=0; i< drawCommands.Count; i++)
  1790. {
  1791. DrawCommand listDc =  drawCommands[i] as DrawCommand;
  1792. // Interesting in cells in the required column
  1793. if (listDc.Col == findCol)
  1794. {
  1795. // Is this Row nearer to the one required than those found so far?
  1796. if ((listDc.Row <= row) && (listDc.Row > newRow) && 
  1797.     !listDc.Separator && listDc.Enabled)
  1798. {
  1799. // Remember this item
  1800. newRow = listDc.Row;
  1801. newDc = listDc;
  1802. newItem = i;
  1803. }
  1804. }
  1805. }
  1806. if (newDc != null)
  1807. {
  1808. // Track the new item
  1809. // Modify the display of the two items 
  1810. SwitchSelection( trackItem, newItem, false, false);
  1811. return;
  1812. }
  1813. }
  1814. // Are we the first submenu of a parent control?
  1815. bool autoLeft = ( parentMenu == null) && ( parentControl != null);
  1816. // Do we have a parent menu?
  1817. if (( parentMenu != null) || autoLeft)
  1818. {
  1819. // Tell the parent on return that nothing was selected
  1820.  returnCommand = null;
  1821. // Finish processing messages
  1822. timer.Stop();
  1823.  exitLoop = true;
  1824. if (autoLeft)
  1825.  returnDir = -1;
  1826. }
  1827. }
  1828. }
  1829. protected bool ProcessKeyRight()
  1830. {
  1831. // Are we the first submenu of a parent control?
  1832. bool autoRight = ( parentControl != null);
  1833. bool checkKeys = false;
  1834. bool ret = false;
  1835. // Is an item currently selected?
  1836. if ( trackItem != -1)
  1837. {
  1838. DrawCommand dc =  drawCommands[ trackItem] as DrawCommand;
  1839. // Does this item have a submenu?
  1840. if (dc.SubMenu)
  1841. {
  1842. // Consume the keyboard message to prevent the submenu immediately
  1843. // processing the same message again. Remember this routine is called
  1844. // after PeekMessage but the message is still on the queue at this point
  1845. Win32.MSG msg = new Win32.MSG();
  1846. WindowsAPI.GetMessage(ref msg, 0, 0, 0);
  1847. // Handle the submenu
  1848. OperateSubMenu( trackItem, true);
  1849. ret = true;
  1850. }
  1851. else
  1852. {
  1853. // Grab the current column/row values
  1854. int col = dc.Col;
  1855. int row = dc.Row;
  1856. // If not in the first column then move left one
  1857. int newItem = -1;
  1858. int newRow = -1;
  1859. int findCol = col + 1;
  1860. DrawCommand newDc = null;
  1861. for(int i=0; i< drawCommands.Count; i++)
  1862. {
  1863. DrawCommand listDc =  drawCommands[i] as DrawCommand;
  1864. // Interesting in cells in the required column
  1865. if (listDc.Col == findCol)
  1866. {
  1867. // Is this Row nearer to the one required than those found so far?
  1868. if ((listDc.Row <= row) && (listDc.Row > newRow) && 
  1869. !listDc.Separator && listDc.Enabled)
  1870. {
  1871. // Remember this item
  1872. newRow = listDc.Row;
  1873. newDc = listDc;
  1874. newItem = i;
  1875. }
  1876. }
  1877. }
  1878. if (newDc != null)
  1879. {
  1880. // Track the new item
  1881. // Modify the display of the two items 
  1882. SwitchSelection( trackItem, newItem, false, false);
  1883. }
  1884. else
  1885. checkKeys = true;
  1886. }
  1887. }
  1888. else
  1889. {
  1890. if ( parentMenu != null)
  1891. {
  1892. if (!ProcessKeyDown())
  1893. checkKeys = true;
  1894. }
  1895. else
  1896. checkKeys = true;
  1897. }
  1898. // If we have a parent control and nothing to move right into
  1899. if (autoRight && checkKeys)
  1900. {
  1901.  returnCommand = null;
  1902. // Finish processing messages
  1903. timer.Stop();
  1904.  exitLoop = true;
  1905.  returnDir = 1;
  1906. }
  1907. return ret;
  1908. }
  1909. protected int ProcessMnemonicKey(char key)
  1910. {
  1911. // Check against each draw command mnemonic
  1912. for(int i=0; i< drawCommands.Count; i++)
  1913. {
  1914. DrawCommand dc =  drawCommands[i] as DrawCommand;
  1915. if (dc.Enabled)
  1916. {
  1917. // Does the character match?
  1918. if (key == dc.Mnemonic)
  1919. return i;
  1920. }
  1921. }
  1922. // No match found
  1923. return -1;
  1924. }
  1925. protected bool ParentWantsMouseMessage(ref Win32.MSG msg)
  1926. {
  1927. Win32.POINT screenPos;
  1928. screenPos.x = (int)((uint)msg.lParam & 0x0000FFFFU);
  1929. screenPos.y = (int)(((uint)msg.lParam & 0xFFFF0000U) >> 16);
  1930. // Convert the mouse position to screen coordinates
  1931. WindowsAPI.ClientToScreen(msg.hwnd, ref screenPos);
  1932. // Special case the MOUSEMOVE so if we are part of a MenuControl
  1933. // then we should always allow mousemoves to be processed
  1934. if ((msg.message == (int)Msg.WM_MOUSEMOVE) && ( parentControl != null))
  1935. {
  1936. Win32.RECT rectRaw = new Win32.RECT();
  1937. // Grab the screen rectangle of the parent control
  1938. WindowsAPI.GetWindowRect( parentControl.Handle, ref rectRaw);
  1939. if ((screenPos.x >= rectRaw.left) &&
  1940. (screenPos.x <= rectRaw.right) &&
  1941. (screenPos.y >= rectRaw.top) &&
  1942. (screenPos.y <= rectRaw.bottom))
  1943. return true;
  1944. }
  1945. if ( parentMenu != null)
  1946. return  parentMenu.WantMouseMessage(screenPos);
  1947. else
  1948. return false;
  1949. }
  1950. protected bool WantMouseMessage(Win32.POINT screenPos)
  1951. {
  1952. Win32.RECT rectRaw = new Win32.RECT();
  1953. // Grab the screen rectangle of the window
  1954. WindowsAPI.GetWindowRect(this.Handle, ref rectRaw);
  1955. bool want = ((screenPos.x >= rectRaw.left) &&
  1956.  (screenPos.x <= rectRaw.right) &&
  1957.  (screenPos.y >= rectRaw.top) &&
  1958.  (screenPos.y <= rectRaw.bottom));
  1959. if (!want && ( parentMenu != null))
  1960. want =  parentMenu.WantMouseMessage(screenPos);
  1961. return want;
  1962. }
  1963. protected void SwitchSelection(int oldItem, int newItem, bool mouseChange, bool reverting)
  1964. {
  1965. bool updateWindow = false;
  1966. // Create a graphics object for drawing with
  1967. using(Graphics g = Graphics.FromHwnd(this.Handle))
  1968. {
  1969. // Deselect the old draw command
  1970. if (oldItem != -1)
  1971. {
  1972. DrawCommand dc =  drawCommands[oldItem] as DrawCommand;
  1973. // Draw old item not selected
  1974. if (layered)
  1975. updateWindow = true;
  1976. else
  1977. DrawSingleCommand(g,  drawCommands[oldItem] as DrawCommand, false);
  1978. }
  1979. if (newItem != -1)
  1980. {
  1981. // Stop the timer as a new selection has occured
  1982. timer.Stop();
  1983. // Do we have a child menu?
  1984. if (!reverting && ( childMenu != null))
  1985. {
  1986. // Start timer to test if it should be dismissed
  1987. timer.Start();
  1988. }
  1989. DrawCommand dc =  drawCommands[newItem] as DrawCommand;
  1990. // Select the new draw command
  1991. if (!dc.Separator && dc.Enabled)
  1992. {
  1993. // Draw the newly selected item
  1994. if (layered)
  1995. updateWindow = true;
  1996. else
  1997. DrawSingleCommand(g, dc, true);
  1998. // Only is mouse movement caused the selection change...
  1999. if (!reverting && mouseChange)
  2000. {
  2001. //...should we start a timer to test for sub menu displaying
  2002. timer.Start();
  2003. }
  2004. }
  2005. else
  2006. {
  2007. // Cannot become selected
  2008. newItem = -1;
  2009. }
  2010. }
  2011. // Remember the new selection
  2012.  trackItem = newItem;
  2013. if (layered && updateWindow)
  2014. {
  2015. // Update the image for display
  2016. UpdateLayeredWindow();
  2017. }
  2018. }
  2019. }
  2020. protected void OnTimerExpire(object sender, EventArgs e)
  2021. {
  2022. // Prevent it expiring again
  2023. timer.Stop();
  2024. bool showPopup = true;
  2025. // Is a popup menu already being displayed?
  2026. if ( childMenu != null)
  2027. {
  2028. // If the submenu popup is for a different item?
  2029. if ( popupItem !=  trackItem)
  2030. {
  2031. // Then need to kill the submenu
  2032. WindowsAPI.PostMessage( childMenu.Handle, WM_DISMISS, 0, 0); 
  2033. }
  2034. else
  2035. showPopup = false;
  2036. }
  2037. // Should we show the popup for this item
  2038. if (showPopup)
  2039. {
  2040. // Check an item really is selected
  2041. if ( trackItem != -1)
  2042. {
  2043. DrawCommand dc =  drawCommands[ trackItem] as DrawCommand;
  2044. // Does this item have a submenu?
  2045. if (dc.SubMenu)
  2046. OperateSubMenu( trackItem, false);
  2047. else
  2048. {
  2049. if (dc.Expansion)
  2050. RegenerateExpansion();
  2051. }
  2052. }
  2053. }
  2054. }
  2055. protected void OperateSubMenu(int popupItem, bool selectFirst)
  2056. {
  2057.  popupItem = popupItem;
  2058.  childMenu = new PopupMenu();
  2059. DrawCommand dc =  drawCommands[popupItem] as DrawCommand;
  2060. // Find screen coordinate of Top right of item cell
  2061. Win32.POINT screenPosTR;
  2062. screenPosTR.x = dc.DrawRect.Right;
  2063. screenPosTR.y = dc.DrawRect.Top;
  2064. WindowsAPI.ClientToScreen(this.Handle, ref screenPosTR);
  2065. // Find screen coordinate of top left of item cell
  2066. Win32.POINT screenPosTL;
  2067. screenPosTL.x = dc.DrawRect.Left;
  2068. screenPosTL.y = dc.DrawRect.Top;
  2069. WindowsAPI.ClientToScreen(this.Handle, ref screenPosTL);
  2070. // Ensure the child has the same properties as ourself
  2071.  childMenu.Style = this.Style;
  2072.  childMenu.Font = this.Font;
  2073. // Record keyboard direction
  2074. int returnDir = 0;
  2075.  returnCommand =  childMenu.InternalTrackPopup(new Point(screenPosTR.x, screenPosTR.y),
  2076.    new Point(screenPosTL.x, screenPosTL.y), 
  2077.    dc.MenuCommand.MenuCommands, 
  2078.    this, 
  2079.    selectFirst,
  2080.     parentControl,
  2081.     popupRight,
  2082.     popupDown,
  2083.    ref returnDir);
  2084.  popupItem = -1;;
  2085.  childMenu = null;
  2086. if (( returnCommand != null) || (returnDir != 0))
  2087. {
  2088. // Finish processing messages
  2089. timer.Stop();
  2090.  exitLoop = true;
  2091.  returnDir = returnDir;
  2092. }
  2093. }
  2094. protected void GrabTheFocus()
  2095. {
  2096.  oldFocus = WindowsAPI.GetFocus();
  2097. // Is the focus on a control/window?
  2098. if ( oldFocus != IntPtr.Zero)
  2099. {
  2100. IntPtr hParent = WindowsAPI.GetParent( oldFocus);
  2101. // Did we find a parent window?
  2102. if (hParent != IntPtr.Zero)
  2103. {
  2104. // Create a new destination for the focus
  2105.  focusCatcher = new FocusCatcher(hParent);
  2106. // Park focus at the temporary window
  2107. WindowsAPI.SetFocus( focusCatcher.Handle);
  2108. // Kill any capturing of the mouse
  2109. WindowsAPI.ReleaseCapture();
  2110. }
  2111. }
  2112.  grabFocus = false;
  2113. }
  2114. protected void ReturnTheFocus()
  2115. {
  2116. // Restore focus to origin
  2117. WindowsAPI.SetFocus( oldFocus);
  2118. // Did we use an temporary parking location?
  2119. if ( focusCatcher != null)
  2120. {
  2121. // Kill it
  2122.  focusCatcher.DestroyHandle();
  2123.  focusCatcher = null;
  2124. }
  2125. // Reset state
  2126.  oldFocus = IntPtr.Zero;
  2127. }
  2128. protected void OnWM_PAINT(ref Message m)
  2129. {
  2130. // Paint message occurs after the window is created and we have 
  2131. // entered the message loop. So this is a good place to handle focus
  2132. if ( grabFocus)
  2133. GrabTheFocus();
  2134. Win32.PAINTSTRUCT ps = new Win32.PAINTSTRUCT();
  2135. // Have to call BeginPaint whenever processing a WM PAINT message
  2136. IntPtr hDC = WindowsAPI.BeginPaint(m.HWnd, ref ps);
  2137. Win32.RECT rectRaw = new Win32.RECT();
  2138. // Grab the screen rectangle of the window
  2139. WindowsAPI.GetWindowRect(this.Handle, ref rectRaw);
  2140. // Convert to a client size rectangle
  2141. Rectangle rectWin = new Rectangle(0, 0, 
  2142. rectRaw.right - rectRaw.left, 
  2143. rectRaw.bottom - rectRaw.top);
  2144. // Create a graphics object for drawing
  2145. using(Graphics g = Graphics.FromHdc(hDC))
  2146. {
  2147. // Create bitmap for drawing onto
  2148. Bitmap memoryBitmap = new Bitmap(rectWin.Width, rectWin.Height);
  2149. using(Graphics h = Graphics.FromImage(memoryBitmap))
  2150. {
  2151. // Draw the background area
  2152. DrawBackground(h, rectWin);
  2153. // Draw the actual menu items
  2154. DrawAllCommands(h);
  2155. }
  2156. // Blit bitmap onto the screen
  2157. g.DrawImageUnscaled(memoryBitmap, 0, 0);
  2158. }
  2159. // Don't forget to end the paint operation!
  2160. WindowsAPI.EndPaint(m.HWnd, ref ps);
  2161. }
  2162. protected void OnWM_ACTIVATEAPP(ref Message m)
  2163. {
  2164. // Another application has been activated, so we need to kill ourself
  2165. timer.Stop();
  2166.  exitLoop = true;
  2167. }
  2168. protected void SubMenuMovement()
  2169. {
  2170. // Cancel timer to prevent auto closing of an open submenu
  2171. timer.Stop();
  2172. // Has the selected item changed since child menu shown?
  2173. if ( popupItem !=  trackItem)
  2174. {
  2175. // Need to put it back again
  2176. SwitchSelection( trackItem,  popupItem, false, true);
  2177. }
  2178. // Are we a submenu?
  2179. if ( parentMenu != null)
  2180. {
  2181. // Inform parent that we have movement and so do not
  2182. // use a timer to close us up
  2183.  parentMenu.SubMenuMovement();
  2184. }
  2185. }
  2186. protected void OnWM_MOUSEMOVE(ref Message m)
  2187. {
  2188. // Are we a submenu?
  2189. if ( parentMenu != null)
  2190. {
  2191. // Inform parent that we have movement and so do not
  2192. // use a timer to close us up
  2193.  parentMenu.SubMenuMovement();
  2194. }
  2195. // Is the first time we have noticed a mouse movement over our window
  2196. if (! mouseOver)
  2197. {
  2198. // Crea the structure needed for WindowsAPI call
  2199. Win32.TRACKMOUSEEVENTS tme = new Win32.TRACKMOUSEEVENTS();
  2200. // Fill in the structure
  2201. tme.cbSize = 16;
  2202. tme.dwFlags = Win32.TrackerEventFlags.TME_LEAVE;
  2203. tme.hWnd = this.Handle;
  2204. tme.dwHoverTime = 0;
  2205. // Request that a message gets sent when mouse leaves this window
  2206. WindowsAPI.TrackMouseEvent(ref tme);
  2207. // Yes, we know the mouse is over window
  2208.  mouseOver = true;
  2209. }
  2210. // Extract the mouse position
  2211. int xPos = (int)((uint)m.LParam & 0x0000FFFFU);
  2212. int yPos = (int)(((uint)m.LParam & 0xFFFF0000U) >> 16);
  2213. Point pos = new Point(xPos, yPos);
  2214. // Has mouse position really changed since last time?
  2215. if ( lastMousePos != pos)
  2216. {
  2217. for(int i=0; i< drawCommands.Count; i++)
  2218. {
  2219. DrawCommand dc =  drawCommands[i] as DrawCommand;
  2220. if (dc.DrawRect.Contains(pos))
  2221. {
  2222. // Is there a change in selected item?
  2223. if ( trackItem != i)
  2224. {
  2225. // Modify the display of the two items 
  2226. SwitchSelection( trackItem, i, true, false);
  2227. }
  2228. }
  2229. }
  2230. // Remember for next time around
  2231.  lastMousePos = pos;
  2232. }
  2233. }
  2234. protected void OnWM_MOUSELEAVE()
  2235. {
  2236. // Deselect the old draw command if not showing a child menu
  2237. if (( trackItem != -1) && ( childMenu == null))
  2238. {
  2239. // Modify the display of the two items 
  2240. SwitchSelection( trackItem, -1, false, false);
  2241. }
  2242. // Reset flag so that next mouse move start monitor for mouse leave message
  2243.  mouseOver = false;
  2244. // No point having a last mouse position
  2245.  lastMousePos = new Point(-1,-1);
  2246. }
  2247. protected void OnWM_XBUTTONUP(ref Message m)
  2248. {
  2249. // Extract the mouse position
  2250. int xPos = (int)((uint)m.LParam & 0x0000FFFFU);
  2251. int yPos = (int)(((uint)m.LParam & 0xFFFF0000U) >> 16);
  2252. Point pos = new Point(xPos, yPos);
  2253. for(int i=0; i< drawCommands.Count; i++)
  2254. {
  2255. DrawCommand dc =  drawCommands[i] as DrawCommand;
  2256. if (dc.DrawRect.Contains(pos))
  2257. {
  2258. // Is there a change in selected item?
  2259. if ( trackItem != i)
  2260. {
  2261. // Modify the display of the two items 
  2262. SwitchSelection( trackItem, i, false, false);
  2263. }
  2264. }
  2265. }
  2266. // Is an item selected?
  2267. if ( trackItem != -1)
  2268. {
  2269. DrawCommand dc =  drawCommands[ trackItem] as DrawCommand;
  2270. // Does this item have a submenu?
  2271. if (dc.SubMenu)
  2272. {
  2273. // If we are not already showing this submenu...
  2274. if ( popupItem !=  trackItem)
  2275. {
  2276. // Is a submenu for a different item showing?
  2277. if ( childMenu != null)
  2278. {
  2279. // Inform the child menu it is no longer needed
  2280. WindowsAPI.PostMessage( childMenu.Handle, WM_DISMISS, 0, 0); 
  2281. }
  2282. // Handle the submenu
  2283. OperateSubMenu( trackItem, false);
  2284. }
  2285. }
  2286. else
  2287. {
  2288. if (dc.Expansion)
  2289. RegenerateExpansion();
  2290. else
  2291. {
  2292. // Kill any child menus open
  2293. if ( childMenu != null)
  2294. {
  2295. // Inform the child menu it is no longer needed
  2296. WindowsAPI.PostMessage( childMenu.Handle, WM_DISMISS, 0, 0); 
  2297. }
  2298. // Define the selection to return to caller
  2299.  returnCommand = dc.MenuCommand;
  2300. // Finish processing messages
  2301. timer.Stop();
  2302.  exitLoop = true;
  2303. }
  2304. }
  2305. }
  2306. }
  2307. protected void OnWM_MOUSEACTIVATE(ref Message m)
  2308. {
  2309. // Do not allow then mouse down to activate the window, but eat 
  2310. // the message as we still want the mouse down for processing
  2311. m.Result = (IntPtr)Win32.MouseActivateFlags.MA_NOACTIVATE;
  2312. }
  2313. protected void OnWM_SETCURSOR(ref Message m)
  2314. {
  2315. // Always use the arrow cursor
  2316. WindowsAPI.SetCursor(WindowsAPI.LoadCursor(IntPtr.Zero, Win32.CursorType.IDC_ARROW));
  2317. }
  2318. protected void OnWM_DISMISS()
  2319. {
  2320. // Pass on to any child menu of ours
  2321. if ( childMenu != null)
  2322. {
  2323. // Inform the child menu it is no longer needed
  2324. WindowsAPI.PostMessage( childMenu.Handle, WM_DISMISS, 0, 0); 
  2325. }
  2326. // Define the selection to return to caller
  2327.  returnCommand = null;
  2328. // Finish processing messages
  2329. timer.Stop();
  2330.  exitLoop = true;
  2331. // Hide ourself
  2332. WindowsAPI.ShowWindow(this.Handle, Win32.ShowWindowStyles.SW_HIDE);
  2333. // Kill ourself
  2334. DestroyHandle();
  2335. }
  2336. #endregion
  2337. }
  2338. }