Cool Owner Drawn Menus with Bitmaps
Version 3.036 June 2005
By
Brent Corkum

 

Update Information

Download MFC MDI Source Code+Example (97KB)
Download MFC Dialog Sorce Code+Example (78KB)

 

 

 

What's new in Version 3.0

As you can see  I've added the new Office XP drawing style for the menu's. I just got a machine with Windows XP on it and I noticed that the menu's in all our applications looked terrible. So I decided to do something about it and after 2 years of not looking at the class, added the new menu drawing style, and added lot's of fixes and user requests. Now the new drawing style isn't exactly like Microsoft's, but I got it so it looks good enough to me. For people that use the old class it's a simple matter of taking exchanging the old BCMenu .cpp and .h files with the new ones.

The class currently uses the new style on XP and the old style on Win9x/NT and 2000. However, if you like the new style and you want to use it on all Windows platforms, just change the following line at the top of the BCMenu.cpp file from:

UINT BCMenu::original_drawmode=BCMENU_DRAWMODE_ORIGINAL;

to

UINT BCMenu::original_drawmode=BCMENU_DRAWMODE_XP;

Likewise, if you think I did a terrible job you can change the drawing style to the original one on all the platforms.

Other additions include support for images with greater than 16 colors. The example contains images with both 256 and 16 million colors. There is also an option for how to draw disabled options. In XP mode they are not selected but this can be changed. I also fixed the problem with multiple menu items with the same command ID not getting images (only the first one did!).  I also went through most of the articles on codeguru and codeproject and fixed a number of bugs that people pointed out. See the update page for more information.

Introduction

This class, BCMenu, implements owner drawn menus derived from the CMenu class. The purpose of which is to mimic the menu style used in Visual C++ 5.0/6.0 and MS Word. I can't take credit for all the code, some portions of it were taken from code supplied by Ben Ashley and Girish Bharadwaj. The difference between their codes and this one is quite simple, this one makes it very easy to build those cool menus with bitmaps into your application. I've removed the Icon loading stuff and replaced it with Bitmaps. The bitmaps allow you to use the 16X15 toolbar bitmaps directly from your toolbars in the resource editor. As well, there is no scaling of the bitmaps so they always look good. You can also load Bitmap resources and define bitmaps for your check marks. I've also added the default checkmark drawing stuff, separators, proper alignment of keyboard accelerator text, keyboard shortcuts, proper alignment of popup menu items, proper system color changes when the Display Appearance changes, plus bug fixes to the Ben Ashley's LoadMenu function for complex submenu systems. I made quite a few other modifications as well, too many to list or remember. If you find any bugs, memory leaks, or just better ways of doing things, please let me know. I used Visual C++ 5.0 and I have not tested compatibility with earlier VC versions. I've tested it on Win 95/NT at various resolutions and color palette sizes.

Installation (MDI Application)

Well, enough of the boring stuff, lets talk about implementation. To make it easy I'm first going to list step by step the method for implementing the menus into a MDI application:

  1. Create your MDI application using the App Wizard.
  2. Insert the BCMenu.cpp and BCMenu.h files into your Workspace.
  3. Add the following public member functions to your CMainFrame class in the MainFrm.h header file:
HMENU NewMenu();
HMENU NewDefaultMenu();
  1. Add the following public member variables to your CMainFrame class in the MainFrm.h header file:
BCMenu m_menu,m_default;
  1. Add the line:
#include "BCMenu.h"

to the top of the MainFrm.h header file.

  1. Open the Mainfrm.cpp implementation file and add the NewMenu and NewDefaultMenu member functions as listed below. IMPORTANT: Make sure you replace the IDR_MYMENUTYPE menu id in the below LoadMenu call to the menu id associated with the menu's in your application. Look in the menus folder of the Resources view.
 
HMENU CMainFrame::NewMenu()
{
  static UINT toolbars[]={
    IDR_MAINFRAME
  };
  // Load the menu from the resources
  m_menu.LoadMenu(IDR_MYMENUTYPE);  // ****replace IDR_MENUTYPE with your menu ID****
  // Use ModifyODMenu to add a bitmap to a menu options.The first parameter
  // is the menu option text string.If it's NULL, keep the current text
  // string.The second option is the ID of the menu option, or the menu
  // option text (can use this for adding bitmaps to popup options) to change.
  // The third option is the resource ID of the bitmap.This can also be a
  // toolbar ID in which case the class searches the toolbar for the
  // appropriate bitmap.Only Bitmap and Toolbar resources are supported.
  // m_menu.ModifyODMenu(NULL,ID_WINDOW_NEW,IDB_WINDOW_NEW);
  // m_menu.ModifyODMenu(NULL, "&Tile",IDB_WINDOW_TILE);
  // Another method for adding bitmaps to menu options is through the
  // LoadToolbars member function.This method allows you to add all
  // the bitmaps in a toolbar object to menu options (if they exist).
  // The first function parameter is an array of toolbar id's.
  // The second is the number of toolbar id's. There is also a
  // function called LoadToolbar that just takes an id.
  m_menu.LoadToolbars(toolbars,1);
  return(m_menu.Detach());
}
 
HMENU CMainFrame::NewDefaultMenu()
{
  m_default.LoadMenu(IDR_MAINFRAME);
  m_default.LoadToolbar(IDR_MAINFRAME);
  return(m_default.Detach());
}
  1. Edit the InitInstance() member function of your CWinApp derived class and add following highlighted code in the position noted:
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
  return FALSE;
m_pMainWnd = pMainFrame;
// This code replaces the MFC created menus with the Ownerdrawn versions 
pDocTemplate->m_hMenuShared=pMainFrame->NewMenu();
pMainFrame->m_hMenuDefault=pMainFrame->NewDefaultMenu();
 
// This simulates a window being opened if you don't have
// a default window displayed at startup
pMainFrame->OnUpdateFrameMenu(pMainFrame->m_hMenuDefault);
 
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
  1. Add the message handlers for the WM_MEASUREITEM, WM_MENUCHAR, and WM_INITMENUPOPUP messages to your CMainFrame class. Do this by right clicking on the CMainFrame class in the ClassView and selecting Add Windows Message Handler. Choose the Window option from the Filter for messages available to class combo box. Select the message and add the handler. Then edit the handler and add the below code.
//This handler ensure that the popup menu items are drawn correctly
void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) 
{
  BOOL setflag=FALSE;
  if(lpMeasureItemStruct->CtlType==ODT_MENU){
    if(IsMenu((HMENU)lpMeasureItemStruct->itemID)){
      CMenu* cmenu=CMenu::FromHandle((HMENU)lpMeasureItemStruct->itemID);
      if(BCMenu::IsMenu(cmenu)){
        m_menu.MeasureItem(lpMeasureItemStruct);
        setflag=TRUE;
      }
    }
  }
  if(!setflag)CMDIFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
 
//This handler ensures that keyboard shortcuts work
LRESULT CMainFrame::OnMenuChar(UINT nChar, UINT nFlags, CMenu* pMenu) 
{
  LRESULT lresult;
  if(BCMenu::IsMenu(pMenu))
    lresult=BCMenu::FindKeyboardShortcut(nChar, nFlags, pMenu);
  else
    lresult=CMDIFrameWnd::OnMenuChar(nChar, nFlags, pMenu);
  return(lresult);
}
 
//This handler updates the menus from time to time
void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) 
{
  CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
  if(!bSysMenu){
    if(BCMenu::IsMenu(pPopupMenu))BCMenu::UpdateMenu(pPopupMenu);
  }
}
  1. If you are debugging or you are mixing standard menus with the BCMenu's (maybe you have different Document templates using the standard menu's) then you should turn on RTTI in the project settings.

Well, that's it. Compile the program and look in the File menu. You should see the bitmaps. I've tried the menus with context menus and they seem to work fine. I also have a small sample program (source+exe) that also uses bitmaps for check marks and bitmaps for a few menu options and has a context menu when you right click in a view.

Installation (SDI Application)

If you want to add the menus to a SDI application:

  1. Create your SDI application using the App Wizard.
  2. Insert the BCMenu.cpp and BCMenu.h files into your Workspace.
  3. Add the following public member function to your CMainFrame class in the MainFrm.h header file:
HMENU NewMenu();
  1. Add the following public member variables to your CMainFrame class in the MainFrm.h header file:
BCMenu m_menu;
  1. Add the line:
#include "BCMenu.h"

to the top of the MainFrm.h header file.

  1. Open the Mainfrm.cpp implementation file and add the NewMenu and NewDefaultMenu member functions as listed below. IMPORTANT: Make sure you replace the IDR_MAINFRAME menu id in the below LoadMenu call to the menu id associated with the menu's in your application. Look in the menus folder of the Resources view.
 
HMENU CMainFrame::NewMenu()
{
  // Load the menu from the resources
  m_menu.LoadMenu(IDR_MAINFRAME);  // ****replace IDR_MAINFRAME with your menu ID****
 
  // Use ModifyODMenu to add a bitmap to a menu options.The first parameter
  // is the menu option text string.If it's NULL, keep the current text
  // string.The second option is the ID of the menu option to change.
  // The third option is the resource ID of the bitmap.This can also be a
  // toolbar ID in which case the class searches the toolbar for the
  // appropriate bitmap.Only Bitmap and Toolbar resources are supported.
  // m_menu.ModifyODMenu(NULL,ID_ZOOM,IDB_ZOOM);
 
  // Another method for adding bitmaps to menu options is through the
  // LoadToolbar member function.This method allows you to add all
  // the bitmaps in a toolbar object to menu options (if they exist).
  // The function parameter is an the toolbar id.
  // There is also a function called LoadToolbars that takes an
  // array of id's.
  m_menu.LoadToolbar(IDR_MAINFRAME);
 
  return(m_menu.Detach());
}
  1. Edit the InitInstance() member function of your CWinApp derived class and add following highlighted code in the position noted:
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
  return FALSE;
 
  CMenu* pMenu = m_pMainWnd->GetMenu();
  if (pMenu)pMenu->DestroyMenu();
  HMENU hMenu = ((CMainFrame*) m_pMainWnd)->NewMenu();
  pMenu = CMenu::FromHandle( hMenu );
  m_pMainWnd->SetMenu(pMenu);
  ((CMainFrame*)m_pMainWnd)->m_hMenuDefault = hMenu;
  1. Add the message handlers for the WM_MEASUREITEM, WM_MENUCHAR, and WM_INITMENUPOPUP messages to your CMainFrame class. Do this by right clicking on the CMainFrame class in the ClassView and selecting Add Windows Message Handler. Choose the Window option from the Filter for messages available to class combo box. Select the message and add the handler. Then edit the handler and add the below code.
//This handler ensure that the popup menu items are drawn correctly
void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) 
{
  BOOL setflag=FALSE;
  if(lpMeasureItemStruct->CtlType==ODT_MENU){
    if(IsMenu((HMENU)lpMeasureItemStruct->itemID)){
      CMenu* cmenu=CMenu::FromHandle((HMENU)lpMeasureItemStruct->itemID);
      if(BCMenu::IsMenu(cmenu)){
        m_menu.MeasureItem(lpMeasureItemStruct);
        setflag=TRUE;
      }
    }
  }
  if(!setflag)CFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
}
 
//This handler ensures that keyboard shortcuts work
LRESULT CMainFrame::OnMenuChar(UINT nChar, UINT nFlags, CMenu* pMenu) 
{
  LRESULT lresult;
  if(BCMenu::IsMenu(pMenu))
    lresult=BCMenu::FindKeyboardShortcut(nChar, nFlags, pMenu);
  else
    lresult=CFrameWnd::OnMenuChar(nChar, nFlags, pMenu);
  return(lresult);
}
 
//This handler updates the menus from time to time
void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) 
{
  CFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
  if(!bSysMenu){
    if(BCMenu::IsMenu(pPopupMenu))BCMenu::UpdateMenu(pPopupMenu);
  }
}
  1. If you are debugging or you are mixing standard menus with the BCMenu's (maybe you have different Document templates using the standard menu's) then you should turn on RTTI in the project settings.