Click here to Skip to main content
15,879,535 members
Articles / Programming Languages / XML

Menu Images using C# and IExtenderProvider - A Better Mousetrap!

Rate me:
Please Sign up or sign in to vote.
4.91/5 (87 votes)
24 Nov 20025 min read 395.6K   1.2K   188   64
How to extend the standard menus to support icons using IExtender in C#

Sample Image - MenuImage.jpg

Introduction

If you are reading this article, you have probably noticed that Microsoft failed to provide a decent menu control in Visual Studio .NET. The standard .NET Framework MainMenu and MenuItem controls are as basic as they get.

A long search through some other articles on how to extend the features of the .NET Framework menus, left me unsatisfied; either they used Interop and Win32 API calls which I am trying to avoid and/or, they subclass the MenuItem which means foregoing the menu designer in Visual Studio.NET, and hand-tooling your menu implementation. I also found that either the code was far too simple (and incomplete), or too complex and integrated into a large control suite that made it difficult to pull-out just the menu functionality.

The source project included with this submission uses IExtenderProvider to create a bridge between an ImageList control containing menu icons, and standard MenuItem controls. The benefit is that, you can continue to design your menus using the menu designer in Visual Studio.NET, and simply extend them to support a MenuImage property that also takes care of the work of owner drawing your menu items. All you need is a few drag-and-drop operations, and few property set values and you will have fully-functional graphical menus with no-coding required.

Using the Extender

Using the MenuImage extender is as simple as it gets:

  • Draw your menu using the Visual Studio.NET menu designer as normal.
  • Drag an ImageList control to your form, and populate the control with your menu icons. It is recommended that you use 16x16 transparent icons. Although the MenuImage extender will support any image supported by the ImageList, my implementation does not make bitmaps transparent.
  • Drag a MenuImage extender to your form. Open the properties window, and select your ImageList control instance from the ImageList property drop-down menu. This hooks your ImageList into the extender. Next, select your menu items and note a new property - MenuImage. Enter the numeric index of the image item to associate with this menu.

That's it! No coding required. When you run your application, the MenuImage extender will retrieve the indicated image from your ImageList, and owner draw the menu as seen above.

Owner Drawn Menus

By default, .NET Framework menus provide no image property on the MenuItem class. To add one requires defining your own drawing and painting code and basically rendering the menus yourself from scratch.

To indicate that you are going to draw your own menu items, the MenuItem class provides an OwnerDraw property. By default, this property is false. To custom draw your menus, set the OwnerDraw property to true.

NOTE: The MenuImage extender does this for you by default. If OwnerDraw is true, then the MenuItem class raises two events that can be used to draw the menu.

Special note: You do not need to implement a subclass of the control to have access to these events - this is what makes implementing this functionality as an extender possible.

The MeasureItem event is used to calculate the height and width of the canvas required for the control. This event is raised prior to DrawItem. The primary activity is to set the ItemHeight and ItemWidth properties to the correct size.

C#
private void OnMeasureItem( Object sender, MeasureItemEventArgs e )
{
    // retrieve the image list index from hash table
    MenuItem menuItem = (MenuItem) sender ;

    // create a menu helper to actually do the menu drawing/painting functions
    MenuHelper menuHelper = new MenuHelper( menuItem, e.Graphics, _imageList ) ;

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

The DrawItem event is used to actually perform the drawing. The event argument provides the state (selected or not), the bounds of the canvas, and even provides a graphics object to do the painting.

C#
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, _imageList ) ;
            
    // draw the menu background
    bool menuSelected = (e.State & DrawItemState.Selected) > 0 ;
    menuHelper.DrawBackground( e.Bounds, menuSelected ) ;

    // if the menu is a seperator, then draw it otherwise, draw a normal menu item
    if ( menuHelper.IsSeperator() == true )
        menuHelper.DrawSeperator( e.Bounds ) ;
    else
    {
        int imageIndex = this.GetMenuImageIndex( sender ) ;
        menuHelper.DrawMenu( e.Bounds, menuSelected, imageIndex ) ;
    }
}

These are the basics behind owner-drawn controls in general. To keep the main extender class code simple, I encapsulated the actual menu drawing and painting features in a separate MenuHelper class. More details on the actual implementation of the drawing and painting can be found by reviewing the sample project source code.

Points of Interest

Since adding an image requires changing the default offsets for menu text, you cannot mix and match owner-drawn menu items with non-owner drawn menu items. It's an all or nothing deal. Part of the complexity associated with this type of code is related to having to handle separators, non-graphical menus, menu shortcuts, sub-menus, etc., selected vs. non-selected menu items, and enabled and disabled states.

The code does not directly call any Interop or Win32 API functionality. In fact, once I started digging, I found that .NET provides a lot of functionality that allowed me to keep my code footprint relatively small - it is just seeded around a lot of different classes. A special thanks goes to the various authors noted in my acknowledgements. Many of the hidden tricks I have used were pulled from their various articles.

Final Notes

I developed these extensions to support the standard Windows style menu design versus the new XP/Office style menus for a number of reasons:

  • I am not as google over the XP style menus as some people. I find the typical style menu does the job and is visual appealing. A particular advantage is that it allowed me to use standard system colors and fonts, which means the menu is more likely to adapt correctly to different desktop themes with no extra coding on my part. The XP style menus require doing special color blending that adds complexity. For those intent on having the XP style functionality, it would not take much effort to support XP style menus. I may consider adding a MenuStyle property that allows the user to select either standard or XP style menus in future revisions.
  • Using standard Window style menus means I do not have to draw the top-level menus. Again, this would not be that hard to change if you really wanted XP style menus, but it would mean having to add some additional code to also detect and paint top-level menu items.

History

  • 24th November, 2002 - Initial submission

Acknowledgements

I drew from a number of different articles by various authors. I would like to acknowledge their indirect contributions as follows:

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
Web Developer
United States United States
Chris Beckett has been contributing to the analysis, design and development of distributed enterprise-level business systems for more than 16 years, with more than 10 years in a technical leadership role. He has delivered systems in the government, banking, broadcasting, entertainment, manufacturing, and finance industries.

Chris Beckett continues to be an active .NET architect, designer and developer, and a strong advocate for best practices in Quality Assurance and Lifecycle Management.

Comments and Discussions

 
GeneralThank you Chris Pin
KingTermite22-May-03 14:04
KingTermite22-May-03 14:04 
Questionhow to add to toolbox? Pin
dazinith22-May-03 3:01
dazinith22-May-03 3:01 
AnswerRe: how to add to toolbox? Pin
dazinith22-May-03 3:08
dazinith22-May-03 3:08 
GeneralA minor bug with ALT+x items... Pin
Marc Clifton6-Apr-03 7:20
mvaMarc Clifton6-Apr-03 7:20 
GeneralRe: A minor bug with ALT+x items... Pin
Grailman17-Sep-04 9:07
Grailman17-Sep-04 9:07 
QuestionMdiList problems ??? Pin
lica2-Apr-03 5:36
lica2-Apr-03 5:36 
AnswerRe: MdiList problems ??? Pin
xanatos38724-Oct-04 6:17
xanatos38724-Oct-04 6:17 
GeneralSuggestions Pin
snortblt28-Mar-03 6:06
snortblt28-Mar-03 6:06 
Very cool implementation. I share your frustration with the other icon menu classes out there. Thanks for taking the time to do it right.

One suggestion I have is to use the ControlPaint.DrawMenuGlyph to draw the check boxes so they look more like a regular Windows menu (unless of course you're not going for that look). DrawMenuGlyph always draws with a white background though, so you have to use the ImageAttributes class to map the background color.

Also, when items are selected their icons are being partially painted over by the selection background color, at least on my system.



snortblt
GeneralRe: Suggestions Pin
snortblt21-Jan-04 8:14
snortblt21-Jan-04 8:14 
GeneralMenuHelper Pin
Member 29090519-Mar-03 23:21
Member 29090519-Mar-03 23:21 
GeneralRe: MenuHelper Pin
Irkinak4-Apr-05 13:01
Irkinak4-Apr-05 13:01 
GeneralA newbie question Pin
UnderWarrior21-Feb-03 5:29
UnderWarrior21-Feb-03 5:29 
GeneralNotifyIcon ContextMenu Icon Error Pin
rund1me18-Feb-03 3:36
rund1me18-Feb-03 3:36 
GeneralRe: NotifyIcon ContextMenu Icon Error Pin
jmacduff18-Jan-04 9:03
jmacduff18-Jan-04 9:03 
GeneralRe: NotifyIcon ContextMenu Icon Error Pin
Marcin G2-Mar-04 5:47
Marcin G2-Mar-04 5:47 
GeneralRe: NotifyIcon ContextMenu Icon Error Pin
Gabriel Szabo21-Jun-04 4:18
Gabriel Szabo21-Jun-04 4:18 
GeneralRe: NotifyIcon ContextMenu Icon Error [modified] Pin
alatka2-Feb-07 17:11
alatka2-Feb-07 17:11 
GeneralMenu Blackout Pin
Hollerith7-Feb-03 1:58
Hollerith7-Feb-03 1:58 
GeneralRe: Menu Blackout Pin
Chris Beckett7-Feb-03 5:10
Chris Beckett7-Feb-03 5:10 
GeneralRe: Menu Blackout Pin
Anonymous14-May-03 15:00
Anonymous14-May-03 15:00 
GeneralRe: Menu Blackout and MDIList Pin
TpB5-Jan-04 7:55
TpB5-Jan-04 7:55 
GeneralRe: Menu Blackout and MDIList Pin
snortblt21-Jan-04 8:20
snortblt21-Jan-04 8:20 
GeneralRe: Menu Blackout Pin
Scuba2duba31-Aug-04 6:05
sussScuba2duba31-Aug-04 6:05 
GeneralRe: Menu Blackout Pin
David Gollasch24-Jul-05 12:42
David Gollasch24-Jul-05 12:42 
QuestionHow about the RadioCheck box? Pin
Libra9-Dec-02 5:14
Libra9-Dec-02 5:14 

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.