Owner Drawn Menu with Icons (2)

This code is an extension to Girish Bharadwaj's Owner Drawn menu. The code he produced does not lack anything, but I thought it would be advantageous to the user to be able to load a Menu Resource in. Currently, you have to construct the menu yourself, item-by-item. This can be very time consuming. Instead, I have introduced 4 new functions to the Owner Drawn menu class...

  1. AppendODMenu(). Appends an Owner-Drawn Item to the menu. You can pass an icon, a selected icon and a disabled icon.
  2. ModifyODMenu(), as above but modifies an existing menu given it's command Identifier (Not positional information)
  3. LoadMenu(int) & LoadMenu(LPCTSTR). Given a menu resource, these commands (which effectively replace CMenu's LoadMenu()), will load the specified menu resource, creating each item as an owner-drawn item. You may then use the ModifyODMenu() member function to assign icon states to the items. It is not possible at this time to specify icons within the menu resource, although I suspect it is possible by defining a custom resource type. This is something I will be looking in to in the next few weeks.

Some problems the new code does not address, is the drawing of separators, checkboxes or the correct placement of the shortcut key text. This is also something I will be looking at, but of course, anyone else is free to implement it.

As I renamed the menu class TCMenu, just giving you the added snippets is probably not enough. It is for this reason that the entire code is here. Apart from a few changes to the drawing behaviour of the menu, I have only added those functions mentioned above. The rest of the code must be credited to Girish Bharadwaj.

The header file.... (TCMenu.h)...


Implements Owner-drawn menu behaviour

#ifndef TCMENU_H
#define TCMENU_H

// TCMenuData class. Fill this class structure to define a single menu item:

class TCMenuData
TCMenuData () { menuIconNormal = -1; menuIconSelected = -1; menuIconDisabled = -1;nID=0;};
char menuText[32];
UINT menuIconNormal;
UINT menuIconSelected;
UINT menuIconDisabled;

typedef enum {Normal,TextOnly} HIGHLIGHTSTYLE;

class TCMenu : public CMenu	// Derived from CMenu
// Construction
// Attributes
CTypedPtrArray<CPtrArray, TCMenuData*> m_MenuList;	// Stores list of menu items 
// When loading an owner-drawn menu using a Resource, TCMenu must keep track of
// the popup menu's that it creates. Warning, this list *MUST* be destroyed
// last item first :)

CTypedPtrArray<CPtrArray, TCMenu*>	m_SubMenus;	// Stores list of sub-menus 
// Operations
// Overrides
// ClassWizard generated virtual function overrides
// Implementation
virtual ~TCMenu();	// Virtual Destructor 
// Drawing: 
virtual void DrawItem( LPDRAWITEMSTRUCT);	// Draw an item
virtual void MeasureItem( LPMEASUREITEMSTRUCT );	// Measure an item

// Custamizing:

void SetTextColor (COLORREF );	// Set the text color
void SetBackColor (COLORREF);	// Set background color
void SetHighlightColor (COLORREF);	// Set highlight Color
void SetIconSize (int, int);	// Set icon size
void SetHighlightStyle (HIGHLIGHTSTYLE );	// Set Highlight style
void SetHighlightTextColor (COLORREF);	// Set Highlight text color

virtual BOOL AppendODMenu(LPCTSTR lpstrText, 
UINT nID = 0,
UINT nIconNormal = -1, 
UINT nIconSelected = -1,
UINT nIconDisabled = -1);	// Owner-Drawn Append 
virtual BOOL ModifyODMenu(LPCTSTR lpstrText,
UINT	nID = 0,
UINT	nIconNormal = -1,
UINT	nIconSelected = -1,
UINT	nIconDisabled = -1);	// Owner-Drawn Modify 
virtual BOOL LoadMenu(LPCTSTR lpszResourceName);	// Load a menu
virtual BOOL LoadMenu(int nResource);	// ... 
// Destoying:

virtual BOOL DestroyMenu();

// Generated message map functions
COLORREF m_crText;
COLORREF m_clrBack;
COLORREF m_clrText;
COLORREF m_clrHilight;
COLORREF m_clrHilightText;
CFont m_fontMenu;
UINT m_iMenuHeight;
BOOL m_bLBtnDown;
CBrush m_brBackground,m_brSelect;
CPen m_penBack;
int m_iconX,m_iconY;
HIGHLIGHTSTYLE m_hilightStyle; 
// NOTE - the ClassWizard will add and remove member functions here.
// Microsoft Developer Studio will insert additional declarations immediately before the previous line. 
#endif // CCUSTOMMENU_H 

The Implementation File (TCMenu.cpp)


// TCMenu.cpp : implementation file

#include "stdafx.h"				// Standard windows header file
#include "TCMenu.h"				// TCMenu class declaration

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;


	Constructor and Destructor.

	m_clrText =  GetSysColor (COLOR_MENUTEXT);				// Default menu text colour
    m_clrBack = GetSysColor (COLOR_MENU);
    m_brBackground.CreateSolidBrush (m_clrBack);
    m_penBack.CreatePen (PS_SOLID,0,m_clrBack);
    m_crText = m_clrText;
    m_bLBtnDown = FALSE;

    m_iconX = 16;						// Icon sizes default to 16 x 16
    m_iconY = 16;						// ...

    m_clrHilight = GetSysColor (COLOR_HIGHLIGHT);			// User Colours...
    m_brSelect.CreateSolidBrush (m_clrHilight);				// Create a brush
    m_clrHilightText = GetSysColor (COLOR_HIGHLIGHTTEXT);	// User Colours...

    ZeroMemory ((PVOID) &m_lf,sizeof (LOGFONT));
    nm.cbSize = sizeof (NONCLIENTMETRICS);

    //Get the system metrics for the Captionfromhere

	VERIFY (SystemParametersInfo (SPI_GETNONCLIENTMETRICS,0,&nm,0)); 

    m_lf =  nm.lfMenuFont;
    m_iMenuHeight = nm.iMenuHeight;
    m_fontMenu.CreateFontIndirect (&m_lf);


BOOL TCMenu::DestroyMenu()
	// Destroy Sub menus:

	int m;
	int numSubMenus = m_SubMenus.GetUpperBound();
	for(m = numSubMenus; m >= 0; m--)


	// Destroy brushes:

    if ((HBRUSH) m_brBackground != NULL)		// Background brush
        m_brBackground.DeleteObject ();
    if ((HFONT)m_fontMenu !=NULL)				// Font for the menu
        m_fontMenu.DeleteObject ();
    if ((HBRUSH)m_brSelect != NULL)				// Selected background brush
        m_brSelect.DeleteObject ();

	// Destroy menu data

	int numItems = m_MenuList.GetUpperBound();
	for(m = 0; m <= numItems; m++)


	// Call base-class implementation last:


// TCMenu message handlers


	Called by the framework when a particular item needs to be drawn.  We overide
	this to draw the menu item in a custom-fashion, including icons and the 3D
	rectangle bar.

    ASSERT(lpDIS != NULL);

    CDC* pDC = CDC::FromHandle(lpDIS->hDC);
    CRect rect;
    HICON hIcon;
    COLORREF crText = m_crText;
	// draw the colored rectangle portion

	// draw the up/down/focused/disabled state

    UINT action = lpDIS->itemAction;
    UINT state = lpDIS->itemState;
    CString strText;
    LOGFONT lf;
    lf = m_lf;

    CFont dispFont;
    CFont *pFont;
    if (lpDIS->itemData != NULL)
		UINT nIconNormal = (((TCMenuData*)(lpDIS->itemData))->menuIconNormal);
		UINT nIconSelected = (((TCMenuData*)(lpDIS->itemData))->menuIconSelected);
		UINT nIconDisabled = (((TCMenuData*)(lpDIS->itemData))->menuIconDisabled);
        strText = (((TCMenuData*) (lpDIS->itemData))->menuText);

        if(nIconNormal == -1)
			hIcon = NULL;
			hIcon = NULL;

			// Obtain the IDs for the appropriate Icons:

			if (state & ODS_SELECTED && !(state & ODS_GRAYED))		// Selected (but not disabled)
                if(nIconSelected != -1)
					hIcon = AfxGetApp ()->LoadIcon(nIconSelected);
				if(state & ODS_GRAYED)								// Disabled (selected or not)
					if(nIconDisabled != -1)
						hIcon = AfxGetApp()->LoadIcon(nIconDisabled);

			// If we didn't manage to select a specific icon, we'll use the
			// default (normal) one...

			if(hIcon == NULL)
				hIcon = AfxGetApp()->LoadIcon(nIconNormal);
        hIcon = NULL;

    if ( (state & ODS_SELECTED) )
        // draw the down edges

        CPen *pOldPen = pDC->SelectObject (&m_penBack);

        // You need only Text highlight and thats what you get

        if (m_hilightStyle != Normal)
            pDC->FillRect (rect,&m_brBackground);
            pDC->FillRect (rect,&m_brSelect);

        pDC->SelectObject (pOldPen);
        //pDC->Draw3dRect (rect,GetSysColor (COLOR_3DHILIGHT),GetSysColor(COLOR_3DSHADOW));

		// This version provides a black-border:

		pDC->Draw3dRect (rect,GetSysColor (COLOR_3DHILIGHT),RGB(0,0,0));        

		//lf.lfWeight = FW_BOLD;
        if ((HFONT)dispFont != NULL)
                dispFont.DeleteObject ();
        dispFont.CreateFontIndirect (&lf);
        crText = m_clrHilightText;
        CPen *pOldPen = pDC->SelectObject (&m_penBack);
        pDC->FillRect (rect,&m_brBackground);
        pDC->SelectObject (pOldPen);

        // draw the up edges

        pDC->Draw3dRect (rect,m_clrBack,m_clrBack);
        if ((HFONT)dispFont != NULL)
                dispFont.DeleteObject ();
        dispFont.CreateFontIndirect (&lf); //Normal


    // draw the text if there is any
    //We have to paint the text only if the image is nonexistant

    if (hIcon != NULL)
        DrawIconEx (pDC->GetSafeHdc(),rect.left,rect.top,hIcon,

    //This is needed always so that we can have the space for check marks

    rect.left = rect.left +((m_iconX)?m_iconX:32) + 2; 

    if ( !strText.IsEmpty())
        pFont->GetLogFont (&lf);

        int iOldMode = pDC->GetBkMode();

        pDC->SetBkMode( TRANSPARENT);

		// Draw the text in the correct colour:

		if(!(lpDIS->itemState & ODS_GRAYED))
	        pDC->DrawText (strText,rect,nFormat);

			// Draw the slightly lighter greyed text at an offset:

			RECT offset = *rect;
			pDC->DrawText(strText,&offset, nFormat);

			// And the standard Grey text:

	        pDC->DrawText(strText,rect, nFormat);
        pFont = pDC->SelectObject (&dispFont);
        pDC->SetBkMode( iOldMode );
        pDC->SelectObject (pFont); //set it to the old font
    dispFont.DeleteObject ();


	Called by the framework when it wants to know what the width and height of our
	item will be.  To accomplish this we provide the width of the icon plus the
	width of the menu text, and then the height of the icon.

	// Obtain the width of the text:

	CWnd *pWnd = AfxGetMainWnd();						// Get main window
    CDC *pDC = pWnd->GetDC();							// Get device context
    CFont* pFont = pDC->SelectObject (&m_fontMenu);		// Select menu font in...
	LPCTSTR lpstrText = (((TCMenuData*)(lpMIS->itemData))->menuText);	// Get pointer to text
	SIZE t;
	GetTextExtentPoint32(pDC->GetSafeHdc(), lpstrText, strlen(lpstrText), &t); // Width of caption
    pDC->SelectObject (pFont);							// Select old font in
    AfxGetApp()->m_pMainWnd->ReleaseDC (pDC);			// Release the DC

	// Set width and height:

    lpMIS->itemWidth = m_iconX + t.cx + 10;
	lpMIS->itemHeight = m_iconY;

void TCMenu::SetIconSize (int width, int height)
    m_iconX = width;
    m_iconY = height;

void TCMenu::SetTextColor (COLORREF clrText)
    m_crText = clrText;

void TCMenu::SetBackColor (COLORREF clrBack)
    m_clrBack = clrBack;
    if ((HBRUSH)m_brBackground != NULL)
		m_brBackground.DeleteObject ();

    m_brBackground.CreateSolidBrush (clrBack);

void TCMenu::SetHighlightColor (COLORREF clrHilight)
    m_clrHilight = clrHilight;
    if ((HBRUSH)m_brSelect != NULL)
                    m_brSelect.DeleteObject ();

    m_brSelect.CreateSolidBrush (clrHilight);

void TCMenu::SetHighlightTextColor (COLORREF clrHilightText)
    m_clrHilightText = clrHilightText;

void TCMenu::SetHighlightStyle (HIGHLIGHTSTYLE hilightStyle)
    m_hilightStyle = hilightStyle;


	These 2 functions effectively replace the CMenu::AppendMenu() and CMenu::ModifyMenu()
	classes, with support for 3 icon states.  When using Owner-drawn menu items,
	use these functions instead of the usual ones.

BOOL TCMenu::AppendODMenu(LPCTSTR lpstrText,
						  UINT  nFlags,
						  UINT	nID,
						  UINT	nIconNormal,
						  UINT	nIconSelected,
						  UINT	nIconDisabled)
	// Add the MF_OWNERDRAW flag if not specified:

	if(!(nFlags & MF_OWNERDRAW))
		nFlags |= MF_OWNERDRAW;

	TCMenuData *mdata = new TCMenuData;
	lstrcpy(mdata->menuText, lpstrText);
	mdata->menuIconNormal = nIconNormal;
	mdata->menuIconSelected = nIconSelected;
	mdata->menuIconDisabled = nIconDisabled;
	return(CMenu::AppendMenu(nFlags, nID, (LPCTSTR)mdata));

BOOL TCMenu::ModifyODMenu(LPCTSTR lpstrText,
						  UINT	  nID,
						  UINT	  nIconNormal,
						  UINT	  nIconSelected,
						  UINT	  nIconDisabled)
	// Delete the existing TCMenuData structure associated with this item (if any)

	int numMenuItems = m_MenuList.GetUpperBound();
	TCMenuData *mdata;
	for(int m = 0; m <= numMenuItems; m++)
		if((mdata = m_MenuList[m]) != NULL)
			if(mdata->nID == nID)

	// Create a new TCMenuData structure:

	mdata = new TCMenuData;
	lstrcpy(mdata->menuText, lpstrText);
	mdata->menuIconNormal = nIconNormal;
	mdata->menuIconSelected = nIconSelected;
	mdata->menuIconDisabled = nIconDisabled;
	return(CMenu::ModifyMenu(nID, MF_BYCOMMAND | MF_OWNERDRAW, nID, (LPCTSTR)mdata));

BOOL TCMenu::LoadMenu(LPCTSTR lpszResourceName)

BOOL TCMenu::LoadMenu(int nResource)

	// Find the Menu Resource:

	DWORD dwSize;
	WORD wrd = MAKEWORD(nResource, 0);
	HRSRC hRsrc = FindResource(NULL, (LPCTSTR)wrd, RT_MENU);	// Find the resource
	if(hRsrc == NULL)
		TRACE0("TCMenu::LoadMenu() - Failed to find Menu Resource\n");

	// Get size of resource:

	dwSize = SizeofResource(NULL, hRsrc);

	// Load the Menu Resource:

	HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
	if(hGlobal == NULL)
		TRACE0("TCMenu::LoadMenu() - Failed to load Menu Resource\n");

	// Attempt to create us as a menu...

		TRACE0("TCMenu::LoadMenu() - Failed to create Menu\n");

	// Get Item template Header, and calculate offset of MENUITEMTEMPLATES

	BYTE*					pTp   = (BYTE*)pTpHdr + 
									(sizeof(MENUITEMTEMPLATEHEADER) + pTpHdr->offset);

	// Variables needed during processing of Menu Item Templates:

	TCMenuData*	pData = NULL;							// New OD Menu Item Data
	WORD		dwFlags = 0;							// Flags of the Menu Item
	WORD		dwID	= 0;							// ID of the Menu Item
	BOOL		bLastPopup = 0;							// Last popup?
	UINT		uFlags;									// Actual Flags.
	char		caption[80];							// Allows up to 80 chars for caption
	int			nLen   = 0;								// Length of caption
	CTypedPtrArray<CPtrArray, TCMenu*>	m_Stack;		// Popup menu stack
	m_Stack.Add(this);									// Add it to this...


		// Obtain Flags and (if necessary), the ID...

		memcpy(&dwFlags, pTp, sizeof(WORD));pTp+=sizeof(WORD);	// Obtain Flags
		if(!(dwFlags & MF_POPUP))
			memcpy(&dwID, pTp, sizeof(WORD));					// Obtain ID
			dwID = 0;

		uFlags = (UINT)dwFlags;			// Remove MF_END from the flags that will
		if(uFlags & MF_END)				// be passed to the Append(OD)Menu functions.
			uFlags -= MF_END;

		// Obtain Caption (and length)

		nLen = 0;
		char *ch = (char*)pTp;
		while(*ch != 0)
			caption[nLen] = ch[0];
			nLen++;						// Increment length
			ch+=2;						// Accounts for UNICODE '0's...
		caption[nLen] = 0;				// Zero-terminate the string...
		pTp+=((nLen+1)*2);				// Increase Pointer...

		// Handle popup menus first....

		if(dwFlags & MF_POPUP)
			if(dwFlags & MF_END)
				bLastPopup = TRUE;

			TCMenu* pSubMenu = new TCMenu;

			// Append it to the top of the stack:

			m_Stack[m_Stack.GetUpperBound()]->AppendMenu(uFlags, (UINT)pSubMenu->m_hMenu, caption);
			m_Stack.Add(pSubMenu);					// Add to PopupStack
			m_SubMenus.Add(pSubMenu);				// For deletion...
			m_Stack[m_Stack.GetUpperBound()]->AppendODMenu(caption, uFlags, dwID, -1, -1, -1);
			if(dwFlags & MF_END)
				m_Stack.RemoveAt(m_Stack.GetUpperBound());	// Remove top of stack

				bLastPopup = FALSE;
				m_Stack.RemoveAt(m_Stack.GetUpperBound());	// And again...

	}while(m_Stack.GetUpperBound() != -1);


  • Why Couldn't you add the source demo???

    Posted by Legacy on 01/24/2002 12:00am

    Originally posted by: zjw

    Why Couldn't you add the source demo???

  • Your program doesn't work at all !!

    Posted by Legacy on 09/15/2000 12:00am

    Originally posted by: Rick

    Let's see:
    What's the meaning of "pFont->GetLogFont (&lf);" in TCMenu.cpp's DrawItem()? pFont is a point of class CFont. You even haven't create a CFont object yet, but you use the statement above, which bring us nothing but an Assert failure!
    And after I analysed your DrawItem(), I find that the result of this statement, which is in "lf", is never used! Furthermore, it isn't compatible with languages other than English(Surely it should be).

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Thanks to the Internet of Things (IoT), physical assets are turning into participants in real-time global digital markets. The countless types of assets around us will become as easily indexed, searched and traded as any online commodity. While some industries will be tougher to transform than others – those with physical limitations, such as manufacturing, will be harder to digitize – untold economic opportunities exist for growth and advancement. Our research shows this will create a new "Economy …

  • Lenovo recommends Windows 8 Pro. "I dropped my laptop getting out of the taxi." This probably sounds familiar to most IT professionals. If your employees are traveling, you know their devices are in for a rough go. Whether it's a trip to the conference room or a convention out of town, any time equipment leaves a user's desk it is at risk of being put into harm's way. Stay connected at all times, whether at the office or on the go, with agile, durable, and flexible devices like the Lenovo® …

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date