Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / Windows Forms

MenuEx - Full Image and Color Support for Windows Forms Menus using C#

Rate me:
Please Sign up or sign in to vote.
4.75/5 (31 votes)
13 Jul 2005CPOL4 min read 194.9K   1.4K   91   58
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:

C#
/// <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:

C#
/// <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:

C#
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)


Written By
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 ControlVault Website

Comments and Discussions

 
QuestionAdding a CheckBox AND Image to the Menu? Pin
Tim8w12-Mar-21 6:18
Tim8w12-Mar-21 6:18 
General5 ! Pin
Mazen el Senih23-Feb-12 9:44
professionalMazen el Senih23-Feb-12 9:44 
QuestionCan it use for dynamic menu? Pin
honpher25-May-07 16:04
honpher25-May-07 16:04 
Generalcontextmenu for notifyicon has error Pin
honpher25-May-07 4:09
honpher25-May-07 4:09 
GeneralRe: contextmenu for notifyicon has error Pin
Jonathan Lynas25-May-07 4:36
Jonathan Lynas25-May-07 4:36 
GeneralRe: contextmenu for notifyicon has error Pin
honpher25-May-07 4:59
honpher25-May-07 4:59 
GeneralNot run in VS 2003 Pin
Populate12331-May-06 4:04
Populate12331-May-06 4:04 
GeneralRe: Not run in VS 2003 Pin
Jonathan Lynas5-Jun-06 22:01
Jonathan Lynas5-Jun-06 22:01 
GeneralOnPaint problem Pin
korn.Net10-Apr-06 18:59
korn.Net10-Apr-06 18:59 
GeneralRe: OnPaint problem Pin
Jonathan Lynas10-Apr-06 19:23
Jonathan Lynas10-Apr-06 19:23 
GeneralCloneMenu/MergeMenu Pin
zeddman17-Mar-06 7:03
zeddman17-Mar-06 7:03 
GeneralRe: CloneMenu/MergeMenu Pin
Jonathan Lynas17-Mar-06 7:21
Jonathan Lynas17-Mar-06 7:21 
GeneralRe: CloneMenu/MergeMenu Pin
zeddman17-Mar-06 10:39
zeddman17-Mar-06 10:39 
AnswerRe: CloneMenu/MergeMenu Pin
Jonathan Lynas20-Mar-06 4:37
Jonathan Lynas20-Mar-06 4:37 
GeneralRe: CloneMenu/MergeMenu Pin
zeddman20-Mar-06 6:19
zeddman20-Mar-06 6:19 
Thanks Jonathan,

It's working well.

Zedd
GeneralSub-Item Arrow Pin
cwizman16-Feb-06 9:39
cwizman16-Feb-06 9:39 
QuestionProblem with menu change at runtime Pin
theTron11-Feb-06 0:09
theTron11-Feb-06 0:09 
AnswerRe: Problem with menu change at runtime Pin
Jonathan Lynas11-Feb-06 1:56
Jonathan Lynas11-Feb-06 1:56 
GeneralRe: Problem with menu change at runtime Pin
theTron11-Feb-06 3:36
theTron11-Feb-06 3:36 
GeneralMemory Leak Pin
rubensr21-Jan-06 20:03
rubensr21-Jan-06 20:03 
GeneralProblem with 32 bit transparent images Pin
BahmanRafatjoo19-Dec-05 10:30
BahmanRafatjoo19-Dec-05 10:30 
GeneralRe: Problem with 32 bit transparent images Pin
Jonathan Lynas20-Dec-05 5:11
Jonathan Lynas20-Dec-05 5:11 
GeneralRe: Problem with 32 bit transparent images Pin
BahmanRafatjoo20-Dec-05 10:59
BahmanRafatjoo20-Dec-05 10:59 
GeneralRe: Problem with 32 bit transparent images Pin
cwizman16-Feb-06 5:50
cwizman16-Feb-06 5:50 
GeneralRe: Problem with 32 bit transparent images Pin
BahmanRafatjoo19-Feb-06 2:41
BahmanRafatjoo19-Feb-06 2:41 

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

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