Click here to Skip to main content
15,887,746 members
Articles / Desktop Programming / MFC

How to Create Owner Drawn Menus - Step by Step

Rate me:
Please Sign up or sign in to vote.
4.34/5 (17 votes)
16 May 20043 min read 111.9K   2.5K   49   20
This tutorial explains how to create owner drawn menus step by step

Sample Image

Introduction

This is just a tutorial on how to create Step-by-Step your own - owner drawn - menu!

Tutorial

Step 1

First of all, you should make your own class. I called it COwnMenu. The base class should be CMenu. Then create a struct, that could look like this:

C++
struct MenuObject { 
HICON m_hIcon; 
CString m_strCaption; 
};

Then, you have to declare an instance of COwnMenu to your main class (for example, CMainFrame or CTestDlg). It is also recommended that you declare two vectors, that hold the addresses of every item that you've allocated!

C++
COwnMenu menu;
std::vector<DWORD> deleteItem;
std::vector<DWORD> deleteMenu;

Step 2

The next thing you've got to do is to create a function that changes all Items of your menu to MF_OWNERDRAW. I've created a recursive function to step through really every menu item... All addresses of the allocated items are saved in deleteItem, if it's an item :) and in deleteMenu, if it's a menu :)

The function could look like this:

C++
void COwnMenu::MakeItemsOwnDraw(BOOL bFirst)
{
 int iMaxItems = GetMenuItemCount();
 for(int i = 0; i < iMaxItems; i++)
 {
   MenuObject* pObject = new MenuObject;
   deleteItem.push_back((DWORD)pObject);
   pObject->m_hIcon = NULL;
   GetMenuString(i, pObject->m_strCaption, MF_BYPOSITION);
   MENUITEMINFO mInfo;
   ZeroMemory(&mInfo, sizeof(MENUITEMINFO));
   UINT uID = mInfo.wID; 
   /*I don't use GetMenuItemID because 
     it doesn't return 0/-1 when it's a Popup
      (so the MSDN is wrong)*/
   ModifyMenu(i, MF_BYPOSITION | MF_OWNERDRAW,
              uID, (char*)pObject);
   if(GetSubMenu(i))
   {
     COwnMenu* pSubMenu = new COwnMenu;
     deleteMenu.push_back((DWORD)pSubMenu);
     pSubMenu->Attach(GetSubMenu(i)->GetSafeHmenu());
     pSubMenu->MakeItemsOwnDraw();
   }
 }
}

Explanation

First of all, you step through all menu items. Then, you create a new MenuObject and add its address to deleteItem. If you have an Icon, you could change pObject->m_hIcon to its address. The next thing you do is getting the Caption of the Item and save it to pObject->m_strCaption. Then you change the style of the menu to MF_OWNERDRAW. What you have got to understand is that the last parameter of ModifyMenu is a pointer to your pObject which we've got to use in DrawItem and MeasureItem later!

The next thing you do is check whether the item is a popup item, which means that it has subitems. If it is one, you create a new COwnMenu, add its address to deleteMenu, so we can clear the whole memory when we destroy our program. After we've done this, we do the whole function for this item.

So - now you can tell me, what's difficult to understand. :)

Step 3

Now, you have to add these two functions:

C++
void COwnMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
  CRect rectFull(lpDrawItemStruct->rcItem); 
  CRect rectIcon(rectFull.left,rectFull.top,rectFull.left+20,rectFull.top+20);
  CRect rectText(rectIcon.right,rectFull.top,rectFull.right,rectFull.bottom);
  COLORREF IconRectLeft = COLORREF(RGB(246,245,244));
  COLORREF IconRectRight = COLORREF(RGB(0,209,201));
  COLORREF TextRect = COLORREF(RGB(249, 248, 247));
  CRect rectBorder = rectFull;
  rectBorder.right -= 1;
  CRect rectFill = rectBorder;
  rectFill.left += 1;
  rectFill.right -= 1;
  rectFill.top += 1;
  rectFill.bottom -= 1;
  if(((MenuObject*)lpDrawItemStruct->itemData)->bFirstMenu)
  {
    ZeroMemory(&rectIcon, sizeof(CRect));
    rectText = rectFull;
    TextRect = GetSysColor(COLOR_BTNFACE);// COLORREF(RGB(192,192,192));
  }

  CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
  FillFluentRect(pDC->GetSafeHdc(),
    rectIcon, 246,245,244,213,209,201);
  pDC->FillSolidRect(&rectText, 
  TextRect);

  if ((lpDrawItemStruct->itemState & ODS_SELECTED) &&
   (lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)))
  {
    TextRect = COLORREF(RGB(182, 189, 210));
    pDC->FillSolidRect(&rectBorder, COLORREF(RGB(10, 36, 106)));
    pDC->FillSolidRect(&rectFill, TextRect);
  }

  pDC->SetBkColor(TextRect);
  rectText.left += 5;
  rectText.top += 1;
  rectText.bottom += 1;
  pDC->TextOut(rectText.left,
    rectText.top, 
    ((MenuObject*)lpDrawItemStruct->itemData)->m_strCaption);
}

void COwnMenu::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
 lpMeasureItemStruct->itemHeight = 20;
 lpMeasureItemStruct->itemWidth = 
  ((MenuObject*)
  lpMeasureItemStruct->itemData)->m_strCaption.GetLength()*8;
}

In MeasureItem, you have to tell the menu how big your item is! I think, that's really easy to understand :).

In DrawItem, you check whether the item is selected. If it is, we draw this cool XP Style Rect. I have made a cool function called FillFluentRect that draws cool special effects to our Iconplace ;). It looks like this:

C++
void COwnMenu::FillFluentRect(HDC hDC, RECT rect, 
  byte r1, byte g1, byte b1, byte r2, byte g2, byte b2)
{
  int iWidth = rect.right - rect.left;
  int iHeight = rect.bottom - rect.top;
  short rDif = r2 - r1;
  short gDif = g2 - g1;
  short bDif = b2 - b1;
  for(int i = 0; i < iWidth; i++)
  {
    byte rCur, gCur, bCur;
    rCur = r1 + (short)(float)(((float)rDif/(float)iWidth)*(float)i);
    gCur = g1 + (short)(float)(((float)gDif/(float)iWidth)*(float)i);
    bCur = b1 + (short)(float)(((float)bDif/(float)iWidth)*(float)i);
    for(int y = 0; y < iHeight; y++)
      SetPixel(hDC, rect.left + i, rect.top + y,
    RGB(rCur, gCur, bCur));
  }
}

I think, I don't have to explain anything here.

When you want to draw an icon or bitmap in DrawItem, you just draw it into the Icon rect with BitBlt...if anybody has a problem with doing this - feel free to ask :) ...but I think it's not that difficult!

Step 4

The next thing is clearing the memory when we end our program, it's not very difficult to understand...so look at this:

C++
COwnMenu::~COwnMenu()
{
  for(int i = 0; i < deleteItem.size(); i++)
  {
    delete ((MenuObject*)deleteItem[i]);
  }
  {
    for(int i = 0; i < deleteMenu.size(); i++)
    {
      delete ((COwnMenu*)deleteMenu[i]);
    }
  }
}

Step 5

The next thing is activating our menu. :) It can either be done in CDialog::OnInitDialog() or in CMainFrame::OnCreate(). It could look like this:

C++
menu.LoadMenu(IDR_MENU);
menu.MakeItemsOwnDraw(TRUE);
SetMenu(&menu);

Additional Information

If you want your menu to have flat borders, then you’ve got to set a WindowsHook, and in the WindowProc, you have got to identify whether the window is your menu or not…. if you don’t know how this works, have a look at the CMenuXP example.

The End

Now, we've finished... it wasn't really hard to understand...was it? I hope I could help some of you.

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
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
BugWM_COMMAND always sends WID=0 Pin
Member 1543886123-Nov-21 6:59
Member 1543886123-Nov-21 6:59 
QuestionStill existing error ,i have fixed it Pin
zxglyy28-Jul-14 6:16
zxglyy28-Jul-14 6:16 
SuggestionError in funtion "MakeItemsOwnDraw" Pin
zxglyy27-Jul-14 21:04
zxglyy27-Jul-14 21:04 
GeneralMy vote of 4 Pin
meirs849-Apr-12 0:40
meirs849-Apr-12 0:40 
GeneralAssertion failure Pin
emmmatty126-Aug-09 7:08
emmmatty126-Aug-09 7:08 
GeneralBold font... Pin
alfa_aquila31-Mar-08 21:40
alfa_aquila31-Mar-08 21:40 
QuestionWhat exactly does this accomplish? Pin
SonicMouser28-Dec-06 14:52
SonicMouser28-Dec-06 14:52 
QuestionError 'IDR_MENU' : undeclared identifier Pin
kiranin25-Apr-06 22:10
kiranin25-Apr-06 22:10 
AnswerRe: Error 'IDR_MENU' : undeclared identifier Pin
Christian Graus27-Apr-06 16:46
protectorChristian Graus27-Apr-06 16:46 
Generalthanks Pin
Max Santos28-Jul-05 10:55
Max Santos28-Jul-05 10:55 
GeneralBeginner Question Pin
magsah200021-Jan-05 8:29
magsah200021-Jan-05 8:29 
GeneralA Error Pin
Orxorand2-Nov-04 23:17
Orxorand2-Nov-04 23:17 
GeneralAn error in the code Pin
MiaoMing2-Aug-04 22:32
MiaoMing2-Aug-04 22:32 
GeneralColouring the menu bar Pin
Alex Evans20-Jun-04 15:01
Alex Evans20-Jun-04 15:01 
QuestionSeparators and Checked menus??? Pin
Member 101644320-May-04 18:26
Member 101644320-May-04 18:26 
Hi!
Its really a nice work. congrats.

But you can enhance it by adding support for separators and checked state for menus.

Amit
AnswerRe: Separators and Checked menus??? Pin
renato tome21-Jun-04 18:13
renato tome21-Jun-04 18:13 
GeneralType safety Pin
Nathan Holt at EMOM19-May-04 5:08
Nathan Holt at EMOM19-May-04 5:08 
Generalseems like.. Pin
T1TAN18-May-04 0:19
T1TAN18-May-04 0:19 
GeneralGood.. Pin
Snyp17-May-04 17:13
Snyp17-May-04 17:13 
GeneralRe: Good.. Pin
Kevin Stumpf18-May-04 4:01
Kevin Stumpf18-May-04 4:01 

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.