Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

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

0.00/5 (No votes)
13 Jul 2005 2  
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 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