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