Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C#

Putting Images Next To MenuItems In A Menu in C#

Rate me:
Please Sign up or sign in to vote.
3.67/5 (18 votes)
12 Jun 20036 min read 147.5K   772   68   8
Putting Images Next To MenuItems In A Menu in C#.NET

Sample Image - figure2.gif

Introduction

Being new to C#, I wanted to jump in with a relatively simple Windows program to get started. So for my first app, I wanted a context menu which had little icons next menu items, like most standard menus seem to always have. I thought it would be easy ....everybody does it, right? No such luck...it turned out not to be as straightforward as I assumed it would be.

I started searching the articles, forums and examples and really didn't find any tutorials or examples which showed how to do it. Well, it took a little bit of researching and playing, but I finally got it and thought I'd share it here in case other newbies to C# are struggling with this too.

Standard Menus

So, let's jump in. A menu item by itself is quite simple, and mostly automated as far as its operation. When you create a new menu item, you give it the text you wish it to display and voila, it's there when you run your program and open the menu. The only thing you have to do is add a handler to tell the program what to do when the menu item is selected.

C#
//
// menuItemSelectAll
//
this.menuItemSelectAll.Index = 3;
this.menuItemSelectAll.Text = "Select All";

// This menu item, I don't draw myself. Since I'm not
// adding an image to the menu rectangle, I just let it
// be drawn by the standard routines. I only have to add
// the event handler to define what happens when it's selected.
this.menuItemSelectAll.Click +=
  new System.EventHandler(this.menuItemSelectAll_Click);

Image 2

The index is automatically chosen by MS Visual Studio .NET (or whichever compiler you may be using). The text property is what is displayed for that menu item, usually chosen when the menu item is graphically created (See Figure 1). The last item shown is the line of code that tells the program, when the Click() event happens, run this event handler (this.menuItemSelectAll_Click).

C#
private void menuItemSelectAll_Click(System.Object sender, 
                                     System.EventArgs e)
  {
    this.textBoxMain.SelectAll();
  }

Menu Images

Ok, that's fine you say. I can see how to do a "normal" menu item, but what about adding an image?

Owner Draw Requirements

To add an image, you now lose the ability of using the standard, normal menu drawing routines. Apparently, the menu is drawn rectangle by rectangle for each menu item. So, if you put an image in any one particular item, you need to tell the compiler not use the standard menu drawing, but that you will override the drawing routine and do it yourself. You can do this by setting the owner draw option to true.

Notice the first few lines are the same as before, just the index value (set by compiler), the text and your click event handler.

C#
//
// menuItemCopy
//
this.menuItemCopy.Index = 2;
this.menuItemCopy.Text = "Copy";
this.menuItemCopy.Click += 
  new System.EventHandler(this.menuItemCopy_Click);

// Must set this to be drawn manually (e.g. by us) 
// since we are adding an image
this.menuItemCopy.OwnerDraw = true;

But when you set OwnerDraw to true, you now have to manually draw this menu item's rectangle, image and text. To do this, you must override two more event handlers (related to drawing), the DrawItem() and MeasureItem() event handlers.

C#
// When adding an image, we must override two methods.
// The MeasureItem() and the DrawItem() so that we include
// the image in the measuring of the item and also when it is
// drawn.

this.menuItemCopy.DrawItem +=
  new System.Windows.Forms.DrawItemEventHandler(this.DrawMenuItemCopy);

this.menuItemCopy.MeasureItem +=
  new System.Windows.Forms.MeasureItemEventHandler(this.MeasureItemMenuItemCopy);

The MeasureItem Event Handler

Ok, now we've told the compiler that this menu item (menuItemCopy) is going to be drawn by us. We've also told the compiler we will provide the DrawItem() and MeasureItem() event handlers.

Now, we must do just that, fill in the event handlers. First, we'll take the MeasureItem() event handler. It does the measuring of the sizes for the image and the drawn text we will need for the rectangle of the menu item.

C#
//
// MeasureItemMenuItemCopy
//
// Measure the rectangle size needed for the Copy Menu Option
private void MeasureItemMenuItemCopy(object obj,
                    MeasureItemEventArgs miea)
{
    MenuItem mi = (MenuItem)obj;

    // Get standard menu font so that the text in this
    // menu rectangle doesn't look funny with a
    // different font
    Font menuFont = SystemInformation.MenuFont;

    StringFormat strfmt = new StringFormat();

    SizeF sizef =
        miea.Graphics.MeasureString(mi.Text,
                                    menuFont,
                                    1000,
                                    strfmt);

    // Get image so size can be computed
    Bitmap bmMenuImage =
      new Bitmap(typeof(FormMenuImages),"COPY.BMP");

    // Add image height and width  to the text height and width when
    // drawn with selected font (got that from measurestring method)
    // to compute the total height and width needed for the rectangle
    miea.ItemWidth =
      (int)Math.Ceiling(sizef.Width) + bmMenuImage.Width;
    miea.ItemHeight =
      (int)Math.Ceiling(sizef.Height) + bmMenuImage.Height;
}

I tried to explain it as best I could via the comments, but just for completeness, let me cover a few points.

  1. The selection of SystemInformation.MenuFont as the menu font lets us keep the same font the menu is using. My first mistake was to miss this feature so my menu items looked funny because they were drawn in a different font than the other menu items.
  2. The MeasureString() method gets the size needed to draw the words for the menu item in the particular font chosen.
  3. Next, we instantiate the image needed for this menu item to measure its dimensions below.
  4. Lastly, we determine the width and height as the max of the text and image width and heights.

The DrawItem Event Handler

Now the last, and arguably most complicated (thought not "that" bad) part of the equation, the DrawItem() event handler. The DrawItem() method essentially draws 3 parts. The rectangle itself, the image on the left, and then the text on the right. Let's put the code down and then examine it.

C#
//
// DrawMenuItemCopy
//
// Actually perform the manual drawing of the Copy menu
// item along with its image.
//
private void DrawMenuItemCopy(object sender,
                   System.Windows.Forms.DrawItemEventArgs e)
{
    MenuItem mi = (MenuItem)sender;

    // Get standard menu font so that the text in this
    // menu rectangle doesn't look funny with a
    // different font
    Font menuFont = SystemInformation.MenuFont;

    // Get a brush to use for painting
    SolidBrush menuBrush = null ;

    // Determine menu brush for painting
    if ( mi.Enabled == false )
    {
        // disabled text if menu item not enabled
        menuBrush = new SolidBrush( SystemColors.GrayText );
    }
    else // Normal (enabled) text
    {
        if ( (e.State & DrawItemState.Selected) != 0)
        {
            // Text color when selected (highlighted)
            menuBrush =
              new SolidBrush( SystemColors.HighlightText );
        }
        else
        {
            // Text color during normal drawing
            menuBrush = new SolidBrush( SystemColors.MenuText );
        }
    }

    // Center the text portion (out to side of image portion)
    StringFormat strfmt = new StringFormat();
    strfmt.LineAlignment = System.Drawing.StringAlignment.Center;

    // Get image associated with this menu item
    Bitmap bmMenuImage =
        new Bitmap(typeof(FormMenuImages),"COPY.BMP");

    // Rectangle for image portion
    Rectangle rectImage = e.Bounds;

    // Set image rectangle same dimensions as image
    rectImage.Width = bmMenuImage.Width;
    rectImage.Height = bmMenuImage.Height;

    // Rectanble for text portion
    Rectangle rectText = e.Bounds;

    // set width to x value of text portion
    rectText.X += rectImage.Width;

    // Start Drawing the menu rectangle

    // Fill rectangle with proper background color
    // [use this instead of e.DrawBackground() ]
    if ( (e.State & DrawItemState.Selected) != 0)
    {
        // Selected color
        e.Graphics.FillRectangle(SystemBrushes.Highlight, 
                                 e.Bounds);
    }
    else
    {
        // Normal background color (when not selected)
        e.Graphics.FillRectangle(SystemBrushes.Menu, 
                                 e.Bounds);
    }

    // Draw image portion
    e.Graphics.DrawImage(bmMenuImage, rectImage);

    // Draw string/text portion
    //
    // text portion
    // using menu font
    // using brush determined earlier
    // Start at offset of image rect already drawn
    // Total height, divided to be centered
    // Formatted string
    e.Graphics.DrawString( mi.Text,
           menuFont,
           menuBrush,
           e.Bounds.Left + bmMenuImage.Width,
           e.Bounds.Top + ((e.Bounds.Height - menuFont.Height) / 2),
           strfmt ) ;
}

Ok, let's again dissect the code.

  1. Get the standard menu font just as before so that it is drawn in the same font as the other items.
  2. Define a brush for drawing and then check the menu item. If the menu item is not enabled, we want the text to display disabled. If it is not disabled, then we need to determine at draw time if the item is the currently selected item or not (to determine background color and text color). If the menu item is selected, we display it in HighlightText, else it is drawn in normal MenuText.
  3. Set the string format used to draw, same as before. Standard Operating Procedure.
  4. Again instantiate a Bitmap, but this time we'll actually display the bitmap.
  5. Instantiate a rectangle for the Image portion, then compute its height and width based on the image's height and width.
  6. Instantiate a Rectangle for the text font. Compute its width (height comes later).
  7. Again check if the item is selected. If the item is selected, we will fill the rectangle background with the Highlight color, else we will use the standard Menu color.
  8. Background was drawn, now draw the image with DrawImage().
  9. Draw the text portion. Note the arguments to the DrawString() method. Argument mi.Text is the text of the menuitem. menuBrush is the brush (color) we determined earlier. e.Bounds.Left + bmMenuImage.Width draws the text at the left side, offset by the width of the image. e.Bounds.Top + ((e.Bounds.Height - menuFont.Height) / 2) draws the text at the top offset by rectangle height - font height divided by 2. This is only to center the text vertically based on font size. Then lastly, the strformat again (Standard Operating Procedure again).

Voila! We are done!

Conclusion

So as you can see, it's not as hard as it seems, but not as easy as one would hope either. You would think that such a common thing as adding an image into menu is so commonplace that there would be a menuitem property for it, but alas no.

Included is a simple app which has a menu for cutting/copying and pasting within a text box. Just a simple app with images in the only menu (See Figure 2).

Image 3

History

  • June 1, 2003: Date posted

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
Java/C/C++ developer. Trying to push the company toward C# and .NET platform.

Comments and Discussions

 
GeneralMy vote of 5 Pin
karthik cad/cam12-Apr-11 0:11
karthik cad/cam12-Apr-11 0:11 
I was Struggling to complete this task .....
now You Made it Easy For me
thanks a lot !!! Smile | :)
GeneralImages when the menu item is disabled Pin
Ori-27-Aug-07 23:22
Ori-27-Aug-07 23:22 
QuestionImage in NotifyIcon ContextMenu Pin
ArunAntony2-Feb-06 21:53
ArunAntony2-Feb-06 21:53 
GeneralTwo bugs/improvements Pin
shivonkar18-Apr-05 3:03
shivonkar18-Apr-05 3:03 
GeneralOut of memory Pin
z8013-Oct-04 6:44
z8013-Oct-04 6:44 
GeneralChecked Pin
z8013-Oct-04 4:07
z8013-Oct-04 4:07 
GeneralOwnerDraw on Steriods Pin
Daaron17-Jun-03 4:18
Daaron17-Jun-03 4:18 
GeneralRe: OwnerDraw on Steriods Pin
Lars [Large] Werner25-Jun-03 2:05
professionalLars [Large] Werner25-Jun-03 2:05 

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.