How To: Using bitmaps in menus

Posted: (EET/GMT+2)

 

How To: Using bitmaps in menus

Level: Intermediate


If you have had the time to take a look at the latest Microsoft products, for example the members of the Office '97 family, you can see that the menus in the applications use bitmaps. When I first saw the menus, the words "wanna do that too" instanly popped into my mind. Fortunately, using bitmaps is actually very easy, you just have to make a bit more work than with the Delphi's TMenu component.

First, a bit background about menu creation. If you have programmed Windows while there was no such thing as Delphi around, you know that menus were created using plain API functions. The functions used in this How To are CreateMenu, CreatePopupMenu, InsertMenu, and SetMenu.

CreateMenu is used to create the menu bar, which is also called the "top-level" menu. Because there wouldn't be much use with only a menu bar, we also need to create few popup menus using the (you guessed it) CreatePopupMenu call.

Initially, all menus are empty. Thus, we must insert some menu items into the menus. Because top-level menu creation calls require a handle to the popup menu they work with, it is easiest to create the popup menu before creating the top-level item.

For this How To, we use InsertMenu to create the items. The call looks something like this:

    MainMenu := CreateMenu;           { create the top-level menu }
    PopupMenu := CreatePopupMenu;     { create a popup menu for the File menu }
    InsertMenu(PopupMenu,-1,mf_ByPosition+mf_OwnerDraw,1,PChar(MenuItemTexts[1]));
    


Here, we use the mf_OwnerDraw flag to tell Windows that we want to draw the menu item ourselves. Actually, this is the key to using bitmaps in menus: because we draw the items ourselves, we can display anything, including bitmaps. (BTW, it isn't necessary to include any text when creating the menu item because we draw the text, but I've included the text just in case you happen to remove the mf_OwnerDraw flag.)

After the menu is created and associated with the form using the SetMenu call, we are ready to do the actual work. Because Windows now knows that we are going to draw the menu items ourselves, it has to query us how large the items are going to be. For this reason, Windows sends the WM_MEASUREITEM message to the window associated with the menu.

Handling the message is just a matter of setting few dimensions. Of course, this example is trivial here, but in an actual application you would include a bit more complex calculations.

    Procedure TForm1.WMMeasureItem(Var Msg : TWMMeasureItem);
    Begin
      With Msg.MeasureItemStruct^do Begin
        ItemWidth := 100;
        ItemHeight := 21;
      End;
    End;
    


After this, we must create an event handler to actually draw the items. Windows send the form a WM_DRAWITEM message for every owner drawn menu item we've created. The actual handler looks more or less like this:

    Procedure TForm1.WMDrawItem(Var Msg : TWMDrawItem);
    Var
      Image  : TImage;
      Canvas : TCanvas;
      S      : String;
    
    Begin
      With Msg.DrawItemStruct^ do Begin
        Canvas := TCanvas.Create;
        With Canvas do Begin
          Handle := hDC;
          If ((ItemState And ods_Selected) <> 0) Then Begin
            Pen.Color := clHighlight;
            Brush.Color := clHighlight;
            Font.Color := clHighlightText;
          End
          Else Begin
            Pen.Color := clBtnFace;
            Brush.Color := clBtnFace;
          End;
          With RCItem do Begin
            Rectangle(Left,Top,Right,Bottom);
            Image := TImage(FindComponent('Image'+IntToStr(ItemID)));
            If (Image <> nil) Then Draw(Left+3,Top+2,Image.Picture.Graphic);
            If (ItemID = 100) Then S := '&Exit'
            Else S := MenuItemTexts[ItemID];
            Left := Left+30;
            Top := Top+3;
            Windows.DrawText(Handle,PChar(S),Length(S),RCItem,dt_Left);
          End;
        End;
      End;
    End;
    


The first thing to do in the handler is to create a canvas for the menu item. Of course, we could make the handler a bit faster by elimiating the TCanvas object, but this method is easier. After the canvas has been created and associated with the menu's device context, we are ready to draw the background.

Of course, the color depends on the selection status. If we have a selection, we use by default the blue color, othwerise light gray. Then we simply draw the rectangle, which, by the way, wouldn't be absolutely necessary when there is no selection, because Windows does this for use.

After the background, we draw the bitmap, if any. Because we numbered the menu items similarly to the TImage components which store the bitmaps, finding the correct bitmap is easy using the FindComponent call. If no bitmap is found (like in the case of the File|Exit command), we simply draw nothing. Otherwise, we draw the bitmap. Finally, we draw the menu text.

As you can see, using bitmaps in menus is easy, if not trivial. However, creating menus this way can be a waste of time, even more if you compare it with the amount of time needed to create a normal menu. For this reason, I'm on the quest to write a TBitmapMenu component. Stay tuned, it might become available some day... ;-)


Code for this How To

  • Environment: Windows 95 or NT 4.0
  • Compiler: Delphi 2.0 or 3.0 for Win32
  • Memory: 4 MB RAM, 22k disk
  • Filename: bmpmenu.zip
  • Size: 4 697 bytes, 5kB
  • Date (M/D/Y): 06/16/97 01:00AM
  • Download time: 14.4k: about 4 seconds, 28.8k: about 2 seconds
  • Distribution: Freeware
  • Registration price: none
  • Download!


Questions? Comments? Give feedback. Or just fill in a form.