Click here to Skip to main content
Click here to Skip to main content
Go to top

MenuEx - full image and color support for Windows Forms Menus using C#

, 13 Jul 2005
Rate this:
Please Sign up or sign in to vote.
A component suite to implement full owner-draw MenuItem support, including the painting of Menu Bars.

MenuSuite screen capture

Introduction

Whilst waiting for Visual Studio 2005 to come along together with rather more comprehensive support for decent menus, I decided that I wanted to add full colour and image support to my own menus. Obviously I could write a component using IExtenderProvider to intercept the measuring and painting of menu items, but I also wanted to be able to paint the menu bar, and to provide a structure which would allow me to use different menu painters for different circumstances.

Using the code

I decided that the support for painting the menu items and menu bar should be implemented via an interface, so that anyone could write their own component to implement the interface. This makes it very easy to add your own custom menu painter. The interface looks like this:

/// <summary>
/// Interface used by MenuEx to allow attachment of menu painters.
/// </summary>
public interface IMenuPainter {
    /// <summary>
    /// Paints the menu bar
    /// </summary>        
    /// <param name="g">A Graphics object on which to paint.</param>
    /// <param name="r">The bounding Rectangle for the paint operation.</param>
    /// <remarks>This method will only be called
    ///      when both these items are non- null.</remarks>
    void PaintBar(Graphics g, Rectangle r);

    /// <summary>
    /// Paints a MenuItem
    /// </summary>
    /// <param name="item">The MenuItem to paint.</param>
    /// <param name="e">A DrawItemEventArgs object
    ///          providing data for the paint action.</param>
    /// <param name="image">An Image associated
    ///        with the MenuItem. Note that this may be null.</param>
    /// <param name="imageSize">A MenuImageSize associated with the MenuItem. 
    ///       This indicates the desired size of the image.</param>
    void PaintItem(MenuItem item, DrawItemEventArgs e, Image image, 
                                          MenuImageSize imageSize);

    /// <summary>
    /// Measures a MenuItem object prior to painting it.
    /// </summary>
    /// <param name="item">The MenuItem to measure.</param>
    /// <param name="e">A MeasureItemEventArgs object
    ///         providing data for the measure action.</param>
    /// <param name="imageSize">A MenuImageSize associated with the MenuItem.
    ///         This indicates the desired size of the image.</param>
    void MeasureItem(MenuItem item, MeasureItemEventArgs e, MenuImageSize imageSize);
}

The main MenuEx component exposes two properties: ImageList and MenuPainter. The former allows you to select the ImageList which will provide the images for the menus, the latter allows you to attach an object which implements the IMenuPainter interface in order to handle the drawing of menus. The MenuEx component also adds an ImageIndex property to each MenuItem on the form so that you can specify the image to be associated with each MenuItem. At run-time, it intercepts the MeasureItem and DrawItem events for each MenuItem and passes on the relevant information to the attached IMenuPainter implementation in order to allow owner-drawing of the menu items.

In addition, the MenuEx component intercepts the FormResize event for the form in order to implement painting support for the Menu Bar. I tried a number of ways to implement this, but settled on using the following method when painting the Menu Bar background:

/// <summary>
/// Creates a Pattern Brush from the internal barBitmap object,
/// and notifies Windows that this is to be used
/// for painting the Menu Bar
/// </summary>
private void SetBrush() {
    BarBrush = SafeNativeMethods.CreatePatternBrush(barBitmap.GetHbitmap());

    MENUINFO mi = new MENUINFO(form);
    mi.fMask = MIM_BACKGROUND;
    mi.hbrBack = BarBrush;
    SafeNativeMethods.SetMenuInfo(mainMenu.Handle, ref mi);    
    SafeNativeMethods.SendMessage(form.Handle, WM_NCPAINT, 0, 0);
}

The relevant Windows API struct and method is shown below:

internal struct MENUINFO {
    internal int cbSize;
    internal int fMask;
    internal int dwStyle;
    internal int cyMax;
    internal IntPtr hbrBack;
    internal int dwContextHelpID;
    internal int dwMenuData;
    internal MENUINFO(Control owner) {
        cbSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(MENUINFO));
        fMask = 0;
        dwStyle = 0;
        cyMax = 0;
        hbrBack = IntPtr.Zero;
        dwContextHelpID = 0;
        dwMenuData = 0;
    }
}

[DllImport("user32.dll")]
internal static extern int SetMenuInfo(IntPtr hmenu, ref MENUINFO mi);

This method allows the MenuEx component to create a bitmap matching the size of the Menu Bar area, allows the IMenuPainter implementation to paint it, and then register it as a PatternBrush with the Windows API SetMenuInfo method. Effectively this provides user control over painting of the menu bar; there are limitations with this method, however - see Points of Interest below.

With the work complete on MenuEx, I decided to write a few sample IMenuPainter implementations, each written as a Component. I've chosen to make them inherit from the same ancestor: BaseMenuPainter, but you don't have to use it unless you want to. A series of virtual methods allows inherited components to override painting or measuring of particular menu attributes as required. The components are:

BaseMenuPainter Base implementation of IMenuPainter
Office2003MenuPainter Inherits from BaseMenuPainter and paints in the style of Office 2003
VisualStudioMenuPainter Inherits from BaseMenuPainter and paints in the style of Visual Studio 2003
PlainMenuPainter Inherits from BaseMenuPainter and paints in a simple style
SkinMenuPainter Inherits from BaseMenuPainter and paints using one of a number of "Skins"
ImageMenuPainter Inherits from BaseMenuPainter and paints using a background image for menu items.

Using the Components

Using the components is very simple:

  • Firstly ensure you have built the libraries and added all items from the "ControlVault.MenuSuite.dll" assembly to your toolbox.
  • Add a MainMenu component to your form and define your menu items in the normal way.
  • Add an ImageList and select the images you need for your menu items.
  • Add a MenuEx component to your form, and set the ImageList property to the image list added in the previous step.
  • Add one of the MenuPainter components to your form.
  • Set the MenuPainter property of the MenuEx component to the MenuPainter you added to the form.
  • Go through each MenuItem in your form, setting the ImageIndex property as appropriate.

That's it! If you run your project, you will find the menu bar and items are drawn using the selected MenuPainter. Now you could try writing your own implementation of IMenuPainter.

Points of Interest

I encountered a couple of problems along the way:

  • The "arrow" used when painting a menu item containing sub-items seems to be painted by Windows whether you like it or not. I decided not to interfere! Suggestions for handling this would be welcomed.
  • Whilst the documentation for CreatePatternBrush indicates that in Windows XP you can use any size bitmap you like, I've encountered some strange painting behaviour. Some images work better than others. Again, any suggestions for improving image support are encouraged and welcomed.

History

  • 30 June 2005 - initial article submitted.
  • 11 July 2005 - updated code to improve painting of text. Changed the GetTextColor method to pass the DrawItemState in order to allow BaseMenuPainter descendant classes to determine the state before drawing the text.
  • 11 July 2005 - Corrected a bug in ImageMenuPainter where the background of the MenuItem objects was being painted with DrawImage rather than by using a TextureBrush.

Acknowledgements

Some of the ground covered in this article has inevitably been trodden before. Whilst all the code here is my own, the following article proved illuminating:

Thanks also to innumerable Usenet contributors who gave me ideas!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Jonathan Lynas
Web Developer
United Kingdom United Kingdom
I've been programming since 1987, in recent years using Delphi versions 1 to 7 and now C#.
 
I'm particularly interested in the development of visual controls and components, and developing applications for the PC and Pocket PC
 
More components and controls can be found at the

You may also be interested in...

Comments and Discussions

 
General5 ! PinmemberMazen el Senih23-Feb-12 9:44 
QuestionCan it use for dynamic menu? Pinmemberhonpher25-May-07 16:04 
Generalcontextmenu for notifyicon has error Pinmemberhonpher25-May-07 4:09 
GeneralRe: contextmenu for notifyicon has error PinmemberJonathan Lynas25-May-07 4:36 
GeneralRe: contextmenu for notifyicon has error Pinmemberhonpher25-May-07 4:59 
GeneralNot run in VS 2003 PinmemberHimadrish Laha31-May-06 4:04 
GeneralRe: Not run in VS 2003 PinmemberJonathan Lynas5-Jun-06 22:01 
GeneralOnPaint problem Pinmemberkorn.Net10-Apr-06 18:59 
GeneralRe: OnPaint problem PinmemberJonathan Lynas10-Apr-06 19:23 
GeneralCloneMenu/MergeMenu Pinmemberzeddman17-Mar-06 7:03 
GeneralRe: CloneMenu/MergeMenu PinmemberJonathan Lynas17-Mar-06 7:21 
GeneralRe: CloneMenu/MergeMenu Pinmemberzeddman17-Mar-06 10:39 
AnswerRe: CloneMenu/MergeMenu PinmemberJonathan Lynas20-Mar-06 4:37 
GeneralRe: CloneMenu/MergeMenu Pinmemberzeddman20-Mar-06 6:19 
GeneralSub-Item Arrow Pinmembercwizman16-Feb-06 9:39 
QuestionProblem with menu change at runtime PinmembertheTron11-Feb-06 0:09 
AnswerRe: Problem with menu change at runtime PinmemberJonathan Lynas11-Feb-06 1:56 
GeneralRe: Problem with menu change at runtime PinmembertheTron11-Feb-06 3:36 
GeneralMemory Leak Pinmemberrubensr21-Jan-06 20:03 
GeneralProblem with 32 bit transparent images PinmemberBahmanRafatjoo19-Dec-05 10:30 
GeneralRe: Problem with 32 bit transparent images PinmemberJonathan Lynas20-Dec-05 5:11 
GeneralRe: Problem with 32 bit transparent images PinmemberBahmanRafatjoo20-Dec-05 10:59 
GeneralRe: Problem with 32 bit transparent images Pinmembercwizman16-Feb-06 5:50 
GeneralRe: Problem with 32 bit transparent images PinmemberBahmanRafatjoo19-Feb-06 2:41 
GeneralProblems With MDI Apps Pinmemberanu_kgec14-Nov-05 2:53 
GeneralIcons Pinmemberkenexcelon9-Sep-05 11:21 
GeneralRe: Icons PinmemberJonathan Lynas12-Sep-05 1:15 
QuestionMDI List and MRU Pinmemberkenexcelon9-Sep-05 11:12 
AnswerRe: MDI List and MRU PinmemberJonathan Lynas12-Sep-05 1:17 
GeneralProblem with ContextMenu Pinmemberskyii16-Aug-05 17:39 
GeneralRe: Problem with ContextMenu PinmemberJonathan Lynas16-Aug-05 22:42 
GeneralRe: Problem with ContextMenu [modified] PinmemberL4k15-Aug-06 1:59 
GeneralSlight bug found... PinmemberGoksly10-Aug-05 11:52 
GeneralRe: Slight bug found... PinmemberJonathan Lynas10-Aug-05 22:15 
GeneralA bit off topic - toolbar.. PinmemberGoksly9-Aug-05 13:39 
GeneralRe: A bit off topic - toolbar.. PinmemberJonathan Lynas9-Aug-05 22:40 
GeneralRe: A bit off topic - toolbar.. PinmemberGoksly10-Aug-05 0:12 
GeneralRe: Auto Save - Cant Uncheck? PinmemberJonathan Lynas9-Aug-05 1:06 
GeneralRe: Auto Save - Cant Uncheck? PinmemberGoksly9-Aug-05 1:10 
GeneralMDI-Apps PinsussAnonymous18-Jul-05 23:35 
GeneralRe: MDI-Apps PinmemberBozos22-Jul-05 6:14 
GeneralRe: MDI-Apps PinmemberJonathan Lynas31-Jul-05 23:17 
GeneralRe: MDI-Apps Pinmemberengrpetero30-Dec-05 7:25 
GeneralText Color in Plain Menu Pinmemberthomasswilliams4-Jul-05 13:24 
GeneralRe: Text Color in Plain Menu PinmemberJonathan Lynas4-Jul-05 21:01 
GeneralRe: Text Color in Plain Menu Pinmemberthomasswilliams5-Jul-05 17:13 
GeneralRe: Text Color in Plain Menu PinmemberJonathan Lynas12-Jul-05 3:27 
Questionflat menu? PinmemberUnruled Boy4-Jul-05 3:08 
AnswerRe: flat menu? PinmemberJonathan Lynas4-Jul-05 3:12 
GeneralRe: flat menu? PinmemberUnruled Boy4-Jul-05 3:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140922.1 | Last Updated 14 Jul 2005
Article Copyright 2005 by Jonathan Lynas
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid