Click here to Skip to main content
Click here to Skip to main content

How to create owner drawn menus - Step by Step

By , 16 May 2004
Rate this:
Please Sign up or sign in to vote.

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:

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!

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 Smile | :) and in deleteMenu, if it's a menu Smile | :)

The function could look like this:

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 dont 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 checking 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 Smile | :)

Step 3

Now, you have to add these two functions:

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 Smile | :) .

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 Wink | ;) . It looks like this:

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 Smile | :) ...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:

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 Smile | :) It can either be done in CDialog::OnInitDialog() or in CMainFrame::OnCreate(). It could look like this:

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

About the Author

Kevin Stumpf

Germany Germany
No Biography provided

Comments and Discussions

 
Generalthanks PinmemberMax Santos28-Jul-05 10:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 17 May 2004
Article Copyright 2004 by Kevin Stumpf
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid