Click here to Skip to main content
15,885,873 members
Articles / Desktop Programming / Windows Forms

Traceract

Rate me:
Please Sign up or sign in to vote.
3.69/5 (9 votes)
3 Sep 20059 min read 98.6K   1.6K   37  
A prototype debug tracer with an added dimension.
using System;
using System.Collections;
using System.Drawing;
using System.IO;
using System.Resources;
using System.Windows.Forms;

// original menu extender code from: http://www.codeproject.com/cs/menu/menuimage.asp

namespace BetterMenu
{
//	public delegate void SMEventDelegate(object sender, EventArgs e);

	public class MainMenu : System.Windows.Forms.MainMenu, IXaml
	{
		private string name;
		private object tag;
		private static Hashtable menus=new Hashtable();

		public string Name
		{
			get {return name;}
			set
			{
				name=value;
				if (menus.Contains(name))
				{
					menus[name]=this;
				}
				else
				{
					menus.Add(name, this);
				}
			}
		}

		public object Tag
		{
			get {return tag;}
			set {tag=value;}
		}

		public event EventHandler ShowHelp;

		public MainMenu()
		{
			ShowHelp=null;
		}

		public bool FireShowHelp(MenuItem mi)
		{
			bool ret=false;
			if (ShowHelp != null)
			{
				ShowHelp(mi, EventArgs.Empty);
				ret=true;
			}
			return ret;
		}

		protected void FireEvent(MenuItemCollection mis, string itemName)
		{
			// brute forcing it for now.
			foreach (Menu m in mis)
			{
				if (m is MenuItem)
				{
					MenuItem mi=(MenuItem)m;
					if (mi.Name==itemName)
					{
						mi.FireEvent();
						return;
					}
				}
				FireEvent(m.MenuItems, itemName);
			}
		}

		static public void FireEvent(string menuName, string itemName)
		{
			if (menus.Contains(menuName))
			{
				MainMenu mm=(MainMenu)menus[menuName];
				mm.FireEvent(mm.MenuItems, itemName);
			}
		}
	}

	public class MenuItem : System.Windows.Forms.MenuItem, IXaml
	{
		//		private string onClick;
		//		private string data;
		//		private string helpText;
		private Image image;
		private Icon icon;
		private static Image menuParentImage=null;
		private string shortcutText;
		private string helpText;
		private string toolBarTag;
		private string name;
		private object tag;

		public event EventHandler ShowHelp;

		public Image Bitmap
		{
			get {return image;}
			set
			{
				image=(Image)value;
			}
		}

		public Icon Icon
		{
			get {return icon;}
			set
			{
				icon=value;
				MemoryStream mem=new MemoryStream();
				icon.Save(mem);
				image=new Bitmap(mem);
			}
		}

		public string ShortcutText
		{
			get {return shortcutText;}
			set {shortcutText=value;}
		}

		public string HelpText
		{
			get {return helpText;}
			set {helpText=value;}
		}

		public string ToolBarTag
		{
			get {return toolBarTag;}
			set {toolBarTag=value;}
		}

		public string Name
		{
			get {return name;}
			set {name=value;}
		}

		public object Tag
		{
			get {return tag;}
			set {tag=value;}
		}

		public Image MenuParentImage
		{
			get {return menuParentImage;}
		}

		public MenuItem() : base()
		{
			helpText=String.Empty;
			ShowHelp=null;
			OwnerDraw=true;
			MeasureItem+=new MeasureItemEventHandler(OnMeasureItem);
			DrawItem+=new DrawItemEventHandler(OnDrawItem);
			Select+=new EventHandler(OnSelectEvent);
			if (menuParentImage == null)
			{
				ResourceManager rm=ResourceManager.CreateFileBasedResourceManager("MenuIcons", @".\", null);
				Icon icon=(Icon)rm.GetObject("MenuParent.ico");

				MemoryStream mem=new MemoryStream();
				icon.Save(mem);
				menuParentImage=new Bitmap(mem);
			}
		}

		public bool FireShowHelp(MenuItem mi)
		{
			bool ret=false;
			if (ShowHelp != null)
			{
				ShowHelp(mi, EventArgs.Empty);
				ret=true;
			}
			return ret;
		}

		public void FireEvent()
		{
			OnClick(EventArgs.Empty);
		}

		private void OnMeasureItem( Object sender, MeasureItemEventArgs e )
		{
			// retrieve the image list index from hash table
			MenuItem menuItem = (MenuItem) sender ;
			MenuHelper menuHelper = new MenuHelper( menuItem, e.Graphics, image);

			// calculate the menu height
			e.ItemHeight = menuHelper.CalcHeight() ;
			e.ItemWidth = menuHelper.CalcWidth() ;
		}

		public void OnSelectEvent(object obj, System.EventArgs e)
		{
			// find a handler for the showing the help
			Menu mi=(Menu)obj;
			bool ret=false;
			while ( (!ret) && (mi != null) )
			{
				if (mi is MainMenu)
				{
					ret=((MainMenu)mi).FireShowHelp((MenuItem)obj);
					mi=null;
				}
				else if (mi is MenuItem)
				{
					ret=((MenuItem)mi).FireShowHelp((MenuItem)obj);
					mi=((MenuItem)mi).Parent;
				}
				else if (mi is System.Windows.Forms.MenuItem)
				{
					mi=((System.Windows.Forms.MenuItem)mi).Parent;
				}
			}
		}
		
		/// <summary>
		/// Event triggered to owner draw the provide <c>MenuItem</c>.
		/// </summary>
		/// <param name="sender">the menu item client object</param>
		/// <param name="e">the event arguments</param>
		private void OnDrawItem( Object sender, DrawItemEventArgs e )
		{
			// derive the MenuItem object, and create the MenuHelper
			MenuItem menuItem = (MenuItem) sender ;
			MenuHelper menuHelper = new MenuHelper( menuItem, e.Graphics, image);
			
			// draw the menu background
			bool menuSelected = (e.State & DrawItemState.Selected) > 0 ;
			menuHelper.DrawBackground( e.Bounds, menuSelected ) ;

			if ( menuHelper.IsSeperator() == true )
				menuHelper.DrawSeperator( e.Bounds ) ;
			else
			{
				menuHelper.DrawMenu( e.Bounds, menuSelected);
			}
		}
	}		

	internal class MenuHelper
	{
		// some pre-defined buffer values for putting space between
		// icon, menutext, seperator text, and submenu arrow indicators
		private const int SEPERATOR_HEIGHT = 8 ;
		private const int SBORDER_WIDTH = 3 ;
		private const int BORDER_SIZE = SBORDER_WIDTH * 2 ;
		private const int SBUFFER_WIDTH = 5 ;
		private const int LBUFFER_WIDTH = 15 ;
		private const int SHORTCUT_BUFFER_SIZE = 20 ;
		private const int ARROW_WIDTH = 15 ;
		private int IMAGE_WIDTH = SystemInformation.SmallIconSize.Width ;
		private int IMAGE_HEIGHT = SystemInformation.SmallIconSize.Height ;
		private int IMAGE_BUFFER_SIZE = SystemInformation.SmallIconSize.Width + 8 ;
		
		// holds the local instances of the MenuItem and Graphics
		// objects passed in through the Constructor
		MenuItem _menuItem = null ;
		Graphics _graphics = null ;
		Image image=null;
		
		/// <summary>
		/// MenuHelper constructor to assist in owner drawn menus.
		/// </summary>
		/// <param name="menuItem">a <c>MenuItem</c> object to custom draw</param>
		/// <param name="graphics">a <c>Graphics</c> object provided by the <c>MeasureItem</c> and <c>DrawItem</c> events</param>
		public MenuHelper( MenuItem menuItem, Graphics graphics, Image image)
		{
			_menuItem = menuItem ;
			_graphics = graphics ;
			this.image = image;
		}
		
		/// <summary>
		/// Based on the menu item text, and the <c>SystemInformation.SmallIconSize,</c>
		/// performs a calculation to determine the correct <c>MenuItem</c> height.
		/// </summary>
		/// <returns>Returns an <c>int</c> value that contains the calculated height of the menu item.</returns>
		public int CalcHeight()
		{
			// if the menu is a seperator, then return a fixed height
			// otherwise calculate the menu size based on the system font
			// and smalliconsize calculations (with some added buffer values)
			if ( _menuItem.Text == "-" )
				return SEPERATOR_HEIGHT ;
			else
			{
				// depending on which is longer, set the menu height to either
				// the icon, or the system menu font
				if ( SystemInformation.MenuFont.Height > SystemInformation.SmallIconSize.Height )
					return SystemInformation.MenuFont.Height + BORDER_SIZE ;
				else
					return SystemInformation.SmallIconSize.Height + BORDER_SIZE ;
			}
		}
		
		/// <summary>
		/// Based on the menu item text, and the <c>SystemInformation.SmallIconSize,</c>
		/// performs a calculation to determine the correct <c>MenuItem</c> width.
		/// </summary>
		/// <returns>Returns an <c>int</c> value that contains the calculated width of the menu item.</returns>
		public int CalcWidth()
		{
			// prepare string formatting used for rendering menu caption
			StringFormat sf = new StringFormat() ;
			sf.HotkeyPrefix = System.Drawing.Text.HotkeyPrefix.Show ;
		
			// set the menu width by measuring the string, icon and buffer spaces
			int menuWidth = (int) _graphics.MeasureString( _menuItem.Text, SystemInformation.MenuFont, 1000, sf).Width ;
			int shortcutWidth = (int) _graphics.MeasureString( this.ShortcutText, SystemInformation.MenuFont, 1000, sf).Width ;
		
			// if a top-level menu, no image support
			if ( this.IsTopLevel() == true )
				return menuWidth ;
			else
				return IMAGE_BUFFER_SIZE + menuWidth + SHORTCUT_BUFFER_SIZE + shortcutWidth ;
		}
		
		/// <summary>
		/// A method to evaluate if the <c>MenuItem</c> has a shortcut selected, and the shortcut
		/// has been selected for show.
		/// </summary>
		/// <returns>Returns True/False whether the menu has a shortcut to be displayed.</returns>
		public bool HasShortcut()
		{
			return ( _menuItem.ShowShortcut == true && _menuItem.Shortcut != Shortcut.None ) ;
		}
		
		/// <summary>
		/// Evaluates whether the <c>MenuItem</c> is a seperator by evaluating the text.
		/// </summary>
		/// <returns>Returns True/False whether the menu is a seperator.</returns>
		public bool IsSeperator()
		{
			return ( _menuItem.Text == "-" ) ;
		}
		
		/// <summary>
		/// Evaluates whether the <c>MenuItem</c> is a top-level menu that is sited directly
		/// on a <c>MainMenu</c> control.
		/// </summary>
		/// <returns>Returns True/False if the menu item is a top-level menu.</returns>
		public bool IsTopLevel()
		{
			return ( _menuItem.Parent is MainMenu ) ;
		}
		
		/// <summary>
		/// Formats the <c>MenuItem</c> and returns the shortcut selection
		/// as a displayable text string.
		/// </summary>
		/// <value>a string, the menu item shortcut as text</c></value>
		public string ShortcutText
		{
			get
			{
				if ( _menuItem.ShowShortcut == true && _menuItem.Shortcut != Shortcut.None )
				{
					if (((MenuItem)_menuItem).ShortcutText != "")
					{
						return ((MenuItem)_menuItem).ShortcutText;
					}
					//					Keys keys=(Keys)_menuItem.Shortcut;
					//					string key=System.ComponentModel.TypeDescriptor.GetConverter(keys.GetType()).ConvertToString(keys) ;
					//					return "\t"+key;
					//					return _menuItem.Shortcut.ToString();
				}
				return null ;
			}
		}

		/// <summary>
		/// Draws a normal menu item including any related icons, checkboxes,
		/// menu text, shortcuts text, and parent/submenu arrows.
		/// </summary>
		/// <param name="bounds">a <c>Rectangle</c> that holds the drawing canvas boundaries</param>
		/// <param name="selected">True/False if the menu item is currently selected</param>
		public void DrawMenu ( Rectangle bounds, bool selected)
		{
			// draw the menu text
			DrawMenuText( bounds, selected ) ;
			
			// since icons make the menu height longer,
			// paint a custom arrow if the menu is a parent
			// to augment the one painted by the control
			// HACK: The default arrow shows up even for ownerdrawn controls ???
			if ( _menuItem.IsParent == true )
			{
				this.DrawArrow(((MenuItem)_menuItem).MenuParentImage, bounds) ;
			}
			
			// if the menu item is checked, ignore any menuimage index
			// and draw the checkbox, otherwise draw the custom image
			if ( _menuItem.Checked )
				DrawCheckBox ( bounds ) ;
			else
			{
				// see if the menu item has an icon associated and draw image
				if (image != null)
				{
					DrawImage(image, bounds) ;
				}
			}
		}
		
		/// <summary>
		/// Draws the <c>MenuItem</c> background.
		/// </summary>
		/// <param name="bounds">a <c>Rectangle</c> that holds the painting canvas boundaries</param>
		/// <param name="selected">True/False if the menu item is currently selected</param>
		public void DrawBackground( Rectangle bounds, bool selected )
		{
			// if selected then paint the menu as highlighted,
			// otherwise use the default menu brush
			if (selected == true)
			{
				//				_graphics.FillRectangle(SystemBrushes.Highlight, bounds);

				_graphics.FillRectangle(new SolidBrush(Color.FromArgb(180, 180, 255)), bounds);
				_graphics.DrawRectangle(new Pen(Color.Blue), bounds.X, bounds.Y, bounds.Width-1, bounds.Height-1);

				//				_graphics.FillRectangle(SystemBrushes.InactiveCaption, bounds);
				//				_graphics.DrawRectangle(new Pen(SystemBrushes.ActiveCaption), bounds.X, bounds.Y, bounds.Width-1, bounds.Height-1);
			}
			else
			{
				Rectangle imageMargin=bounds;
				imageMargin.Width=IMAGE_BUFFER_SIZE-2;
				bounds.X+=imageMargin.Width;
				_graphics.FillRectangle(SystemBrushes.ControlLight, imageMargin);
				_graphics.FillRectangle( SystemBrushes.Menu, bounds ) ;
			}
		}
		
		/// <summary>
		/// Draws a menu seperator.
		/// </summary>
		/// <param name="bounds">a <c>Rectangle</c> that holds the drawing canvas boundaries</param>
		public void DrawSeperator( Rectangle bounds )
		{
			// create the seperator line pen
			Pen pen = new Pen(SystemColors.ControlDark) ;

			// calculate seperator boundaries
			int xLeft = bounds.Left + IMAGE_BUFFER_SIZE ;
			int xRight = xLeft + bounds.Width ;
			int yCenter = bounds.Top  + (bounds.Height / 2) ;

			// draw a seperator line and return
			_graphics.DrawLine(pen, xLeft, yCenter, xRight, yCenter) ;
		}
		
		/// <summary>
		/// Draws the text for an ownerdrawn <c>MenuItem</c>.
		/// </summary>
		/// <param name="bounds">a <c>Rectangle</c> that holds the drawing area boundaries</param>
		/// <param name="selected">True/False whether the menu item is currently selected</param>
		private void DrawMenuText ( Rectangle bounds, bool selected )
		{
			// use system fonts and colors to select the menu brush so the menu
			// will appear correctly for any desktop theme
			Font menuFont = SystemInformation.MenuFont ;
			SolidBrush menuBrush = null ;
			if ( _menuItem.Enabled == false )
				menuBrush = new SolidBrush( SystemColors.GrayText ) ;
			else
			{
				//				if ( selected == true )
				//					menuBrush = new SolidBrush( SystemColors.HighlightText ) ;
				//				else
				menuBrush = new SolidBrush( SystemColors.MenuText ) ;
			}

			int width=bounds.Left+IMAGE_BUFFER_SIZE;
			
			// draw the menu text
			StringFormat sfMenu = new StringFormat() ;
			sfMenu.HotkeyPrefix = System.Drawing.Text.HotkeyPrefix.Show ;
			_graphics.DrawString( _menuItem.Text, menuFont, menuBrush, width, bounds.Top + ((bounds.Height - menuFont.Height) / 2), sfMenu ) ;

			// if the menu has a shortcut, then also 
			// draw the shortcut right aligned
			if ( this.IsTopLevel() != true || this.HasShortcut() == false )
			{
				StringFormat sfShortcut = new StringFormat() ;
				sfShortcut.HotkeyPrefix = System.Drawing.Text.HotkeyPrefix.Show ;
				sfShortcut.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
				int shortcutWidth = (int) _graphics.MeasureString( this.ShortcutText, menuFont, 1000, sfShortcut).Width ;
				_graphics.DrawString(this.ShortcutText, menuFont, menuBrush, (bounds.Width) - LBUFFER_WIDTH , bounds.Top + ((bounds.Height - menuFont.Height) / 2), sfShortcut);
			}
		}			
		
		/// <summary>
		/// Draws a checked item next to a <c>MenuItem</c>.
		/// </summary>
		/// <param name="bounds">a <c>Rectangle</c> that identifies the drawing area boundaries</param>
		private void DrawCheckBox( Rectangle bounds )
		{
			// use the very handy ControlPaint object to paint
			// a checkbox. ButtonState is a bitwise flat that can be built
			// to accomodate style and state appearance
			ButtonState btnState = ButtonState.Flat ;
			
			if ( _menuItem.Checked == true )
				btnState = btnState | ButtonState.Checked ;
			
			if ( _menuItem.Enabled == false )
				btnState = btnState | ButtonState.Inactive ;
			
			// draw the checkbox
			ControlPaint.DrawCheckBox(_graphics, bounds.Left + SBORDER_WIDTH, bounds.Top + ((bounds.Height - IMAGE_HEIGHT) / 2), IMAGE_WIDTH, IMAGE_HEIGHT, btnState ) ;
		}
		
		/// <summary>
		/// Draws a provided image onto the <c>MenuItem</c>.
		/// </summary>
		/// <param name="menuImage">an <c>Image</c> to paint on the menu</param>
		/// <param name="bounds">a <c>Rectangle</c> that holds the drawing space boundaries</param>
		private void DrawImage( Image menuImage, Rectangle bounds )
		{
			// if the menu item is enabled, then draw the image normally
			// otherwise draw it as disabled
			if ( _menuItem.Enabled == true )
				_graphics.DrawImage(menuImage, bounds.Left + SBORDER_WIDTH, bounds.Top + ((bounds.Height - IMAGE_HEIGHT) / 2), IMAGE_WIDTH, IMAGE_HEIGHT ) ;	
			else
				ControlPaint.DrawImageDisabled(_graphics, menuImage, bounds.Left + SBORDER_WIDTH, bounds.Top + ((bounds.Height - IMAGE_HEIGHT) / 2), SystemColors.Menu ) ;
		}
		
		/// <summary>
		/// Draws a custom arrow on the right-side edge of the menu to indicate
		/// the menu has submenu items. Used to supplement a base contorl arrow
		/// that is painted incorrectly (seems to be a bug), and make the arrow
		/// appear correctly for longer menu items.
		/// </summary>
		/// <param name="menuImage"></param>
		/// <param name="bounds"></param>
		private void DrawArrow( Image menuImage, Rectangle bounds )
		{
			_graphics.DrawImage(menuImage, bounds.Left + bounds.Width - ARROW_WIDTH, bounds.Top + ((bounds.Height - IMAGE_HEIGHT) / 2), IMAGE_WIDTH, IMAGE_HEIGHT ) ;	
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions