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…
- AppendODMenu(). Appends an Owner-Drawn Item to the menu.
You can pass an icon, a selected icon and a disabled
icon. - ModifyODMenu(), as above but modifies an existing menu
given it’s command Identifier (Not positional
information) - 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)…
/* =============================================================================== CMenu TCMenu ---------------
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 { public: TCMenuData () { menuIconNormal = -1; menuIconSelected = -1; menuIconDisabled = -1;nID=0;}; char menuText[32]; UINT menuIconNormal; UINT menuIconSelected; UINT menuIconDisabled; UINT nID; };
typedef enum {Normal,TextOnly} HIGHLIGHTSTYLE;
class TCMenu : public CMenu // Derived from CMenu { // Construction public: TCMenu();
// Attributes protected: 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 public:
// Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CCustomMenu) //}}AFX_VIRTUAL
// Implementation public: 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 nFlags = MF_OWNERDRAW, 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 protected: COLORREF m_crText; COLORREF m_clrBack; COLORREF m_clrText; COLORREF m_clrHilight; COLORREF m_clrHilightText; LOGFONT m_lf; 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;
//{{AFX_MSG(CCustomMenu) // NOTE - the ClassWizard will add and remove member functions here. //}}AFX_MSG };
/////////////////////////////////////////////////////////////////////////// //
//{{AFX_INSERT_LOCATION}} // 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__; #endif /* ================================================================================= TCMenu::TCMenu() TCMeny::~TCMenu() ----------------- Constructor and Destructor. ================================================================================= */ TCMenu::TCMenu() { 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)); NONCLIENTMETRICS nm; 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); } TCMenu::~TCMenu() { DestroyMenu(); } BOOL TCMenu::DestroyMenu() { // Destroy Sub menus: int m; int numSubMenus = m_SubMenus.GetUpperBound(); for(m = numSubMenus; m >= 0; m--) delete(m_SubMenus[m]); m_SubMenus.RemoveAll(); // 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++) delete(m_MenuList[m]); m_MenuList.RemoveAll(); // Call base-class implementation last: return(CMenu::DestroyMenu()); }; /////////////////////////////////////////////////////////////////////////// // // TCMenu message handlers /* ================================================================================= void TCMenu::DrawItem(LPDRAWITEMSTRUCT) --------------------------------------- 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. ================================================================================= */ void TCMenu::DrawItem (LPDRAWITEMSTRUCT lpDIS) { ASSERT(lpDIS != NULL); CDC* pDC = CDC::FromHandle(lpDIS->hDC); CRect rect; HICON hIcon; COLORREF crText = m_crText; // draw the colored rectangle portion rect.CopyRect(&lpDIS->rcItem); // 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; //GetWindowText(strText); 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; else { 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); } else { 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); } } else { strText.Empty(); 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); else 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; } else { 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, m_iconX,m_iconY,0,NULL,DI_NORMAL); }; //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: UINT nFormat = DT_LEFT|DT_SINGLELINE|DT_EXPANDTABS|DT_VCENTER; if(!(lpDIS->itemState & ODS_GRAYED)) { pDC->SetTextColor(crText); pDC->DrawText (strText,rect,nFormat); } else { // Draw the slightly lighter greyed text at an offset: RECT offset = *rect; offset.left+=1; offset.right+=1; offset.top+=1; offset.bottom+=1; pDC->SetTextColor(GetSysColor(COLOR_BTNHILIGHT)); pDC->DrawText(strText,&offset, nFormat); // And the standard Grey text: pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT)); pDC->DrawText(strText,rect, nFormat); }; pFont = pDC->SelectObject (&dispFont); pDC->SetBkMode( iOldMode ); pDC->SelectObject (pFont); //set it to the old font } dispFont.DeleteObject (); } /* ================================================================================= void TCMenu::MeasureItem(LPMEASUREITEMSTRUCT) --------------------------------------------- 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. ================================================================================= */ void TCMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMIS ) { // 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; } /* ================================================================================= BOOL TCMenu::AppendODMenu(LPCTSTR, UINT, UINT, UINT, UINT) BOOL TCMenu::ModifyODMenu(LPCTSTR, UINT, UINT, UINT, UINT) ---------------------------------------------------------- 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; m_MenuList.Add(mdata); 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) { delete(mdata); m_MenuList.RemoveAt(m); break; }; }; }; // Create a new TCMenuData structure: mdata = new TCMenuData; m_MenuList.Add(mdata); 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) { return(TCMenu::LoadMenu(MAKEINTRESOURCE(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 Resourcen"); ASSERT(FALSE); }; // 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 Resourcen"); ASSERT(FALSE); }; // Attempt to create us as a menu... if(!CMenu::CreateMenu()) { TRACE0("TCMenu::LoadMenu() - Failed to create Menun"); ASSERT(FALSE); }; // Get Item template Header, and calculate offset of MENUITEMTEMPLATES MENUITEMTEMPLATEHEADER* pTpHdr = (MENUITEMTEMPLATEHEADER*)LockResource(hGlobal); 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... do { // 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 pTp+=sizeof(WORD); } else 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; pSubMenu->CreatePopupMenu(); // 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... } else { 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 if(bLastPopup) { bLastPopup = FALSE; m_Stack.RemoveAt(m_Stack.GetUpperBound()); // And again... }; }; }while(m_Stack.GetUpperBound() != -1); return(TRUE); }; //*************************************************************************