Click here to Skip to main content
15,884,176 members
Articles / Programming Languages / C#

The Application Automation Layer - Using XML to generate Menus

Rate me:
Please Sign up or sign in to vote.
4.81/5 (56 votes)
5 May 200313 min read 238.3K   2.6K   194  
Exploring the issues of menu management as specified externally via an XML file, in the context of status bars, toolbars, and events.
using System;
using System.Drawing;
using System.Windows.Forms;

namespace AAL
{
	internal class SmartMenuItem : MenuItem
	{
		private MenuMgr menuMgr;
		private string onClick;
		private string data;
		private string helpText;
		private Image image;
		private string shortcut;

		public string ShortcutText
		{
			get
			{
				return shortcut;
			}
		}

		public SmartMenuItem(MenuMgr mnuMgr, string caption, string shortcut, Image image, bool isMainMenuItem) : base(caption)
		{
			helpText="";
			this.menuMgr=mnuMgr;
			this.image=image;
			this.shortcut=shortcut;
			Select+=new EventHandler(OnSelectEvent);

			if (!isMainMenuItem)
			{
				OwnerDraw=true;
				MeasureItem+=new MeasureItemEventHandler(OnMeasureItem);
				DrawItem+=new DrawItemEventHandler(OnDrawItem);
			}
		}

		public SmartMenuItem(MenuMgr mnuMgr, string caption, string shortcut, string onClick, string data, string helpText, Image image, bool isMainMenuItem) : base(caption)
		{
			this.menuMgr=mnuMgr;
			this.onClick=onClick;
			this.data=data;
			this.helpText=helpText;
			this.image=image;
			this.shortcut=shortcut;
			Click+=new EventHandler(OnClickEvent);
			Select+=new EventHandler(OnSelectEvent);

			if (!isMainMenuItem)
			{
				OwnerDraw=true;
				MeasureItem+=new MeasureItemEventHandler(OnMeasureItem);
				DrawItem+=new DrawItemEventHandler(OnDrawItem);
			}
		}

		public void OnClickEvent(object obj, System.EventArgs e)
		{
			EventData ev=EventData.Marshal(new MED[] {new MED("MenuData", data)});
			menuMgr.ICM.Invoke(onClick, ev);
		}

		public void OnSelectEvent(object obj, System.EventArgs e)
		{
			EventData ev=EventData.Marshal(null, null, new MED[] {new MED("HelpText", helpText)});
			menuMgr.ICM.Invoke("FormMgr.SetMenuHelp", ev);
		}

		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() ;
		}
		
		/// <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);
			}
		}
	}		
	/// <summary>
	/// 
	/// </summary>
	/// <remarks>
	/// </remarks>
	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 (((SmartMenuItem)_menuItem).ShortcutText != "")
					{
						return ((SmartMenuItem)_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(MenuManager.menuMgr.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