|

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 get:
- 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.
private void OnMeasureItem( Object sender, MeasureItemEventArgs e )
{
MenuItem menuItem = (MenuItem) sender ;
MenuHelper menuHelper = new MenuHelper( menuItem, e.Graphics, _imageList ) ;
e.ItemHeight = menuHelper.CalcHeight() ;
e.ItemWidth = menuHelper.CalcWidth() ;
}
The DrawItem event is used to actually perform the drawing. The event
argument provide the state (selected or not), the bounds of the canvas, and
even provide a graphics object to do the painting.
private void OnDrawItem( Object sender, DrawItemEventArgs e )
{
MenuItem menuItem = (MenuItem) sender ;
MenuHelper menuHelper = new MenuHelper( menuItem, e.Graphics, _imageList ) ;
bool menuSelected = (e.State & DrawItemState.Selected) > 0 ;
menuHelper.DrawBackground( e.Bounds, menuSelected ) ;
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
- November 24th, 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:
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 62 (Total in Forum: 62) (Refresh) | FirstPrevNext |
|
 |
|
|
 |
|
|
Hi,
I've given up on finding how to add icons on the context menu shown by a dropdown toolbar button.
I'd like to know if would be possible to use this technique in the PocketPC.
Thanks.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
A few people have pinged me over the years about using this in commercial projects. The code is free, open and royalty free. Use it however you wish, I like to share
|
| Sign In·View Thread·PermaLink | 5.00/5 (2 votes) |
|
|
|
 |
|
|
Hi,
I used something like your menu extender to display images in a menu. But there are a lot of images, the automatically displayed arrows are great, but the menu takes all the screen height! My boss want the menu to be shorter...
I tried to display only a few images, and added my own arrow items at the beginning and the end of them, but I can't make the menu scroll when I click/select the arrows!
- when clicking the arrows, the menu automatically closes - when selecting them, I dynamically modify the images items, but I can't make the menu update! I tried everything: Refresh/Update/Invalidate of the parent control, PerformSelect on all the images items, calling SendMessage with WM_PAINT, WM_PRINT, WM_PRINT_CLIENT with the menu handle, and the parent control handle, I've even tried to set to false then back to true all the images items OwnerDraw, they update more or less, but the false ownerdraw menu (brief) displaying is awful!
I didn't manage to find an answer to my problems on the web, so if you have a clue it would be great to tell me! Either about how making my long menu not taking all the screen height, or forcing the menu to update when I dynamically modify it!
Thanks a lot Thibaud
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Hello,
At first compliments for the solution!! But ..one question is still open for me. In the article (§2 point 3) is said: "Drag a MenuImage to your form ...". However, there is no MenuImage on my toolbar. Is this what you mean, that the MenuImage should be on the toolbar? I tried to realise this by customising the toolbar with one of your dll's, whithout result.. I made allready a reference to the MenuImageLib.dll, but this as done from the Solution Explorer. In the end I copied it from your sample project, with success. Is there a better solution? Greetings from Holland, Bertran de Boer http://home.ict.nl/~bertboer --
Siemens Nederland N.V. B.F.A. (Bertran) de Boer, MSc. Business Center Zoetermeer Werner von Siemensstraat 1 2712 PN Zoetermeer (Netherlands) Building 1.3.19 Mobile: +31 (0)6 - 4629 6758 Bertran.deBoer.ext@siemens.com
|
| Sign In·View Thread·PermaLink | 2.00/5 (2 votes) |
|
|
|
 |
|
|
hi, i think your code is awesome. I have one problem, that I cannot seem to fix. My context menus inside my application and the menu items inside the application all work and function perfectly (thanks to your code), but when i minimize my program and send it to the system tray (essentially), when i right click on the icon in the tray, it pops up the menu, like it's supposed to, but not fully, only about 1/5 of what the total width of the menu would be. No icons show, though I've assigned them, and not even the text for each item shows! Please help.
Thank you.
Matt
|
| Sign In·View Thread·PermaLink | 3.50/5 (2 votes) |
|
|
|
 |
|
|
 |
|
|
 |
|
|
As icons on the menu were ugly I decided to remove IExtProvider from my project. I removed image list and provider itself from designer. Also removed .dll dependency. Now all of my menus are gone at runtime. I see main menu bar (&File,&Edit etc) but menu does not pop up at all. Keyboard shortcuts work OK. What garbage should I remove from InitializeComponent() code ? What is wrong with this removal????
Greeting, Art
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
line 390 at menuimage.cs // 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 ) { Image menuImage = null ; System.IO.Stream stream = this.GetType().Assembly.GetManifestResourceStream("MenuImage.SubItem16.ico") ; menuImage = Image.FromStream(stream) ; this.DrawArrow( menuImage, bounds ) ; }
************************** hi im getting this wierd error, i dont know why _menuItem.IsParent becomes true i have MainMenus and some (2) ContextMenu, one of my ContextMenu reaches this line. It gets an error when menuImage = Image.FromStream(stream) ; <----<< is null, error occured that null is not valid.
why does _menuItem.IsParent becomes true? what is causing this how do i prevent this?
help
-- modified at 23:01 Thursday 13th July, 2006
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello
I have successfully used the component in a test project, but when compiling an existing project with a MenuImage component added to it, I get the following error:
..Main.cs(17): 'Chris.Beckett.MenuImageLib.MenuImage' is inaccessible due to its protection level
This is odd, considering that the component works out of the box anywhere else.
Suggestions anyone?
Thank you
Edward
|
| Sign In·View Thread·PermaLink | 1.83/5 (3 votes) |
|
|
|
 |
|
|
restarting .NET after including the dll is important to get it run
getting a compile error i changed the namespace of the dll, then it worked
after clearing a imagevalue, i got exception´s possible solution: public void SetMenuImage( Component component, string indexValue ) { if (indexValue == "") indexValue = null;
getting a icon in a top-menu i insert the code SetMenuImage() manualy
clever code 
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I was succesfully merged the MDI child and parent menu. You must make some mistake. Maybe in connection beetween MenuImage extender and image list or something in properties of menu items like ownerdraw or mergr type and order.
Adam Pietrzyk
|
| Sign In·View Thread·PermaLink | 3.50/5 (2 votes) |
|
|
|
 |
|
|
Hi, Excellent component. I always wondered how to do this in .NET.
There is a bug in MenuHelper.CalcWidth, which causes all menu items to have an extra 2cm space to the right of their text. It always adds SHORTCUT_BUFFER_SIZE when calculating the width, even for menu items that don't have shortcuts. To fix it, check if (this.ShortcutText == null) and don't add the extra width.
I don't have a website, but also made several other small changes, that maybe someone would like. I'll e-mail it if anyone wants it.
Most noteable change is, I changed SetMenuImage's 2nd parameter to an int. There was some pretty strange code there to convert the string to an int (converted to an int variable but never used, just to see if it would throw an exception). I deleted all the strange code, and made it an int to begin with.
Mine also doesn't have the arrow icon built into resources, and I removed the method that drew an arrow. Not sure but it looks like that was a workaround for a bug in the version 1 framework, not needed in version 1.1.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I don't know how source code will come out in here, but anyway... I didn't notice the HasShortcut method yesterday, so now I've changed my CalcWidth fix to use it instead, rather than call the this.ShortcutText property more than necessary.
I also edited my version of the ShortcutText property to workaround the KB814353 bug mentioned in an earlier post. There are probably better ways of doing this though, but it works for me... (Note that in my edited project, I changed a lot of var names so this code is slightly different to the original, but shouldn't be a problem for anyone, I hope. e.g. private members are prefixed with "m", not underscores.)
Here's my code:
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)mGraphics.MeasureString(mMenuItem.Text, SystemInformation.MenuFont, 1000, sf).Width;
// If a top-level menu, no image support. if (IsTopLevel()) { return menuWidth; } else if (HasShortcut()) { int shortcutWidth = (int)mGraphics.MeasureString(this.ShortcutText, SystemInformation.MenuFont, 1000, sf).Width; return IMAGE_BUFFER_SIZE + menuWidth + SHORTCUT_BUFFER_SIZE + shortcutWidth; } else { /* 2005/04/12: Bugfix by Jerome Viveiros. Original version was always adding SHORTCUT_BUFFER_SIZE, even for items * without a shortcut. This made all the menu items too wide with about 2cm extra space on the right of the text. */ return IMAGE_BUFFER_SIZE + menuWidth; } }
public string ShortcutText { get { if (mMenuItem.ShowShortcut && mMenuItem.Shortcut != Shortcut.None) { Keys keys = (Keys)mMenuItem.Shortcut; string shortcutText = Convert.ToChar(Keys.Tab, CultureInfo.InvariantCulture) + System.ComponentModel.TypeDescriptor.GetConverter(keys.GetType()).ConvertToString(keys);
/* Workaround KB814353 bug: * If the shortcut contains +D, but is not Ctrl+D or Alt+D, remove the D. */ if (shortcutText.IndexOf("+D") != -1 && string.Compare(shortcutText, "Ctrl+D", true, CultureInfo.InvariantCulture) != 0 && string.Compare(shortcutText, "Alt+D", true, CultureInfo.InvariantCulture) != 0) { shortcutText = shortcutText.Replace("+D", "+"); }
return shortcutText; } return null; } }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Funny, I made the same mods, except I used a simpler test for the "+D" scenario...
string s = Convert.ToChar(Keys.Tab) + ... if (s.IndexOf("+D") == s.Length - 3 { s = s.Replace("+D", "+"); }
If you have +D followed by one other character, it will be a "plus digit"
Also, I think in your code (above post), the test for "Ctrl+D", etc will always fail, because the string will always begin with a tab before the shortcut text.
-Rachel
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Nope, the tab is not returned as part of the shortcut text, so the code does work. But yours is simpler. 
There are other bugs in the original code though, which I only noticed afterwards: 1) Menu items height is wrong. Too narrow compared to standard menu height. 2) Using ControlPaint to draw checkmarks draws XP-style, not standard style. 3) Menu separators are drawn with XP-style, not standard style. 4) I mentioned the 2cm extra space on the right of menu items. 5) It doesn't draw radio check marks at all. 6) Disabled menu item text is drawn wrong. Using Graytext. ControlPaint.DrawStringDisabled or something to that effect fixes it.
I also borrowed the property editor from http://www.codeproject.com/cs/menu/MenuExtender.asp and fixed some oddities in there too. (It has the item "(none)" at the end of the list instead of the beginning, and does some unnecessary string to int conversion at some point, getting an image index for list items which already have an ImageIndex property. So it inadvertently uses exceptions for flow control and slows things down.)
I'd gladly send you the version I did but I can't without your e-mail adress. Anyway, nothing I changed was difficult, so you can do the same. And the original component is still good... I'd never heard of IExtenderprovider and this was a great introduction.
Jerome
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hey Hey! There has been heaps of talk about the ImageList component trashing the alpha channel on 32 bit images that are added into it. It's getting plenty of people upset and suicidal over it. But don't book yourself onto an anger management course just yet.
I liked this control, because I wanted images on my context menus. But wanted 32 bit ones. 1 bit transparency is just a thing of the past. So I looked into the MenuImage.cs file, and changed the function DrawMenu()
... // see if the menu item has an icon associated and draw image if ( indexValue > -1 ) { // Image menuImage = null ; // menuImage = _imageList.Images[indexValue] ; // DrawImage( menuImage, bounds ) ; _imageList.Draw( _graphics, bounds.X, bounds.Y, indexValue ); }
By using _imageList.Draw() it does the trick. Stick as many 32bit PNG's into your imagelist as you like. You also need to make sure that VisualStyles is enabled in your Main function
[STAThread] static void Main() { Application.EnableVisualStyles(); Application.DoEvents(); Application.Run(new MainForm()); }
Hope that is okay, seemed to do the damage for me.
-Ross
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
This method never draws an icon in the disabled state should the menu be disabled. This mod will achive this:
// see if the menu item has an icon associated and draw image if ( indexValue > -1 ) { //Image menuImage = null ; //menuImage = _imageList.Images[indexValue] ; //DrawImage( menuImage, bounds ) ; if ( _menuItem.Enabled == true ) //_graphics.DrawImage(menuImage, bounds.Left + SBORDER_WIDTH, bounds.Top + ((bounds.Height - IMAGE_HEIGHT) / 2), IMAGE_WIDTH, IMAGE_HEIGHT ) ; _imageList.Draw( _graphics, bounds.X, bounds.Y, indexValue ); else { Image menuImage = null ; menuImage = _imageList.Images[indexValue] ; ControlPaint.DrawImageDisabled(_graphics, menuImage, bounds.Left + SBORDER_WIDTH, bounds.Top + ((bounds.Height - IMAGE_HEIGHT) / 2), SystemColors.Menu ) ; } }
Hope that helps
Matt...
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I am new to C# and want to use this menu.
I was just wondering if you (or anyone) could break everything down to step by step instructions?
I have tried a few times at implementing this menu but never seems to work at all.
The grass is always grenner on the other side
|
| Sign In·View Thread·PermaLink | 3.00/5 (3 votes) |
|
|
|
 |
|
|
Here's a ReadMe file I created for a friend. Hope this helps.
ReadMe.Txt ==========
This file contains information about setting up and using a C# Menu extender that provides functionality to add icons to menus. The extender is freeware offered by Chris Beckett at http://www.codeproject.com/cs/menu/menuimage.asp.
Setting up, Installing, and Using the MenuImage extender to add icons to C# menus. =================================================================================
[SetUp]
1. Extract the MenuImage_src.zip files. 2. In Visual Studio, open the MenuImage solution and build it. 3. Run the project and examine the MainMenu's icons. 4. Close the project.
[Install the MenuImage on the Toolbox]
1. Right-click the Toolbox and choose Add/Remove Items... 2. When the Customize Toolbox dialog appears, click the .NET Framework Components tab, then click Browse. 3. Browse to the extracted file, MenuImageLib.dll. Click Open. 4. Click OK. The MenuImage component should display at the bottom of the Toolbox objects.
[Using MenuImage]
1. Start a new Windows project. 2. Drop a MainMenu component on your form and set up its Items. 3. Drop an ImageList component on your form. Populate the ImageList with suitable icons (16x16 transparent icons are recommended). 4. Drop a MenuImage component and set its ImageList property to your ImageList. 5. Select each MainMenu Item and set its MenuImage property to the ImageList's numeric index for the desired icon. 6. Build, run, enjoy.
Joe Harbin St. Paul, MN
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
In some rare cases it might be necessary to change image of an item programmatically. Do you men know how?
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
General News Question Answer Joke Rant Admin
|