Owner Drawn Menu with Icons (3) (uses toolbar res)

 

 

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!). See the bottom of this article for more information on updates.

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 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. I also use the disabled bitmap dithering function of Jean-Edouard Lachand-Robert to create the disabled state bitmaps. I must admit, it does a much better job then the DrawState() function. 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:
  4. HMENU NewMenu();
    HMENU NewDefaultMenu();
  5. Add the following public member variables to your CMainFrame class in the MainFrm.h header file:
  6. BCMenu m_menu,m_default;
  7. Add the line:
  8. #include "BCMenu.h"

    to the top of the MainFrm.h header file.

  9. 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.
  10. HMENU CMainFrame::NewMenu()
    {
      static UINT toolbars[]={
          IDR_MAINFRAME
      };
    
      // Load the menu from the resources
      // ****replace IDR_MENUTYPE with your menu ID****
      m_menu.LoadMenu(IDR_MYMENUTYPE);  
      // One 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());
    }
    
  11. Edit the InitInstance() member function of your CWinApp derived class and add following highlighted code in the position noted:
  12. // 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);
    
  13. 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.
  14. //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(m_menu.IsMenu(cmenu)||m_default.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(m_menu.IsMenu(pMenu)||m_default.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(m_menu.IsMenu(pPopupMenu)||m_default.IsMenu(pPopupMenu))
          BCMenu::UpdateMenu(pPopupMenu);
      }
    }
    
  15. 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)

  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:
  4. HMENU NewMenu();
    
  5. Add the following public member variables to your CMainFrame class in the MainFrm.h header file:
  6. BCMenu m_menu;
    
  7. Add the line:
  8. #include "BCMenu.h"
    

    to the top of the MainFrm.h header file.

  9. 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.
  10. HMENU CMainFrame::NewMenu()
    {
      // Load the menu from the resources
     // ****replace IDR_MAINFRAME with your menu ID****
      m_menu.LoadMenu(IDR_MAINFRAME);  
     // One 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());
    }
    
  11. Edit the InitInstance() member function of your CWinApp derived class and add following highlighted code in the position noted:
  12.   // 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;
    
  13. 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 MDI code from above. Replace the references to CMDIFrameWnd to CFrameWnd.
  14. 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.

Improvements and Bug Fixes

Version 3.0

  1. Added Office XP drawing style.
  2. Added support for menu images with greater than 16 colors.
  3. Can set whether disabled menu options can be selected.
  4. Improved the memory storage of the images. The entire toolbar image is no longer stored for a menu option.
  5. Added a SetMenuText method.
  6. Added a RemoveMenu function that takes the popup menu title instead of it's location.
  7. Added a GetSubMenu function that returns a pointer to the menu.
  8. Fixed the problem with only the first of multiple menu options with the same command id getting an image.
  9. InsertMenu now takes -1 for the position and properly appends the option to the end of the menu.
  10. Fixed the problem with RichEditViews 
  11. If the menu option was given a checked state in the resource editor it wasn't getting applied. Fixed.
  12. Reformatted and improved the class layout.

Version 2.63

  1. RemoveMenu/DeleteMenu with popups asserts, this has been fixed.
  2. Updated the example.

Version 2.62

  1. There were some problems with dynamically adding and removing separators.
  2. InsertMenu using the MF_BYCOMMAND flag didn't work correctly.

Version 2.61

  1. The ImageListDuplicate function was always being called with an offset of 0 instead of the user defined offset.
  2. The ImageListDuplicate function was improved. It now handles 16X16 bitmaps and can handle more color depth.
  3. User information can now be tagged on to a menu item through the pContext BCMenuData member variable.
  4. A FindMenuItem function is added to allow you to retrieve menu data using a command id.
  5. A m_bDynIcons member variable has been added. If it is set to TRUE then resource id's are loaded as Icons instead of bitmaps.

Version 2.6

  1. Version 2.5 was not VC++ 5.0 compatible. I had used a CImageList::Create( CImageList* pImageList ); overload that isn't available in 5.0. The above overload also calls a SDK function that is not supported in Windows 95 without IE 4.0. As a result, the menu class would crash if you use any of the menu functions that add a bitmap through a CBitmap or CImageList object. All is now fixed!

Version 2.5

  1. Added support for dynamically created menu's. The new functions include:
    • BOOL RemoveMenu(UINT uiId,UINT nFlags);
    • BOOL DeleteMenu(UINT uiId,UINT nFlags);
    • BOOL AppendMenu(UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,int nIconNormal=-1);
    • BOOL AppendMenu(UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,CImageList *il,int xoffset);
    • BOOL AppendMenu(UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,CBitmap *bmp);
    • BOOL InsertMenu(UINT nPosition,UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,int nIconNormal=-1);
    • BOOL InsertMenu(UINT nPosition,UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,CImageList *il,int xoffset);
    • BOOL InsertMenu(UINT nPosition,UINT nFlags,UINT nIDNewItem,const char *lpszNewItem,CBitmap *bmp);
    See the example for details on implementation. You can define the bitmap for the appended or inserted menu item using a resource id, CBitmap object or a CImageList object.
  2. Added new functions:
    • BCMenu* AppendODPopupMenuA(LPCSTR lpstrText); - A simple method for dynamically adding a popup menu.
    • BOOL ModifyODMenuA(const char *lpstrText,UINT nID,CImageList *il,int xoffset);
    • BOOL ModifyODMenuA(const char *lpstrText,UINT nID,CBitmap *bmp);
  3. Can now define the bitmap using a Cbitmap object or a CImageList object
  4. BCMenu now maintains a list of every BCMenu created. Using the now static member function BCMenu::Ismenu you can easily determine if a menu is a BCMenu.
  5. A few resource leaks were fixed.

Version 2.4

  1. Updated the code to work with VC++ 6.0. Fixed the GetMenuItemInfo problem.
  2. If a tab character existed in every menu option there was a problem with the InsertSpaces function.
  3. The FindMenuOption would not find a popup menu option if it was in a popup itself.
  4. Initialization was added for pFont and nID.

Version 2.3 (courtesy of Stefan Kuhr)

  1. Conversions from Unicode to ANSI and vice versa are now being made through the helper macros described in MFC Tech Note 059. Memory allocations for the conversions are now being made primarily on the stack, which is much safer and faster on Win32 than on the heap. And the code looks much better and shorter.
  2. The Unicode String in the BCMenuData is now to be allocated on the heap thus having only the memory footprint it needs to have. BCMenuData has new member functions for accessing the now private String variable wchar_t *m_szMenuText.
  3. Corrected a few situations in BCMenu code where portable constant strings (_T()) should be used.
  4. Improved look on the old shell. It really looks nice now on WinNT 3.51 and Win32s. You can now run the sample on NT3.51.
  5. The function 'IsNewShell' is now replaced by 'IsShellType'. BCMenu has a new static member function BOOL IsNewShell(void).
  6. Added Unicode project settings to the sample.
  7. Changed strings in the sample to portable versions by use of the _T() macro.
  8. Set warning level of all project settings in the sample to the highest level (W4) and corrected everything to compile cleanly without any warning being thrown by VC 5.

Version 2.2

  1. Fixed a memory leak in AddBitmapToImageList. This only affected floating popup menu's (right mouse button).

Version 2.1

  1. Was not deleting the disabled bitmap in DrawItem causing a resource leak. This has been fixed.

Version 2.0

  1. Version 1.9 had some problems with the new disabled look under different desktop color schemes. This has been fixed.
  2. There was a bug in the image list stuff. For non-default desktop color schemes, the 3D colors were not being done correctly to match the toolbar.

Version 1.9

  1. The disabled look of the menu icons now match the disabled look of the toolbar buttons in Joerg Koenig's flat toolbar class available from http://www.codeguru.com. If you prefer the old look you can get it back through a call to the SetDisableOldStyle() member function.

Version 1.8

  1. Made a modification to support OCX controls.

Version 1.7

  1. Added UNICODE and Win32s old shell support (WIN 3.1 and WINNT 3.5).
  2. Added integrated radio and checked buttons.
  3. Now using AfxLoadSysColorBitmap to load the bitmaps.
  4. Now using the WM_INITMENUPOPUP message handler to update the menus instead of the OnUpdateWindowNew.

Version 1.6

  1. Multiple LoadMenu/DestroyMenu combinations crashed. This has been fixed.

Version 1.5

  1. Can now easily add bitmaps to popup options. See the CMainFrame::Newmenu member function in the sample application for implementation instructions.
  2. The UpdateMenu member function will now work to update menu's after a dynamic change in the menu's options text.

Version 1.4

  1. More work on the sizing and position of Accelerator text on menus.

Version 1.3

  1. Keyboard shortcuts did not work correctly with the active view menu options in the MDI Window popup.
  2. Keyboard shortcuts did not work correctly with the most-recently-used (MRU) file list. Menu options after this list could not be selected with keyboard shortcuts.
  3. For some reason MFC treats MeasureItem results differently for first level popups. The expanded tabs size calculation (GetTabbedTextExtent) works well for submenus but MFC seems to pad too much space onto the first level popups. I tried to be smart and reduce the size manually, but this messed up submenus. I've gone back to the original tabbed size calculation in MeasureItem.

Version 1.2

  1. The memory used to store the checkmark bitmaps was not being released when the menu was destroyed. This resulted in a memory leak. This has been fixed.
  2. Added the AddFromToolBar member function for loading all the bitmaps from a toolbar into menu options (if they exist). If you want ALL the bitmaps from a toolbar to be mapped to menu options, use this function. This replaces the many ModifyODMenu function calls that you would have to make. If you only want a few of the toolbar options mapped to menu options then you have to use the ModifyODMenu method.

Version 1.1

  1. When the user typed a keyboard shortcut that didn't exist the program crashed.
  2. The first item, if it contained a bitmap, was not drawn correctly when keyboard shortcuts were used.
  3. Large fonts caused some alignment problems, this has been fixed.
  4. The AppendODMenu and ModifODMenu member functions were prototyped incorrectly.
  5. The LoadMenu(LPCTSTR lpszResourceName) overloaded member function didn't work.
  6. Added RTTI checking in the code if it's defined in the project Settings. This is both helpful for debugging and is required if you mix standard menus with BCMenu's.
  7. Added Word 97 radio button style for checked menu options with bitmaps.

Downloads

Download demo project - 254 Kb


Comments

  • Problems

    Posted by alan93 on 08/30/2004 08:23am

    Wrote author about why wrong icons or split icons showing up on windows 2000 when using the LoadToolbar method. Didn't understand it. Also, couldn't get shortcuts to work even after adding OnMenuChar(). pressing a shortcut key after menu dropped down would dimiss the menu but the function would not execute. removed BCMenu code and shortcuts worked again.

    • woops

      Posted by alan93 on 08/30/2004 08:41am

      my fault on the short cut keys not working, i was trying one i had duplicates on. bad me. However, still didn't get the loadtoolbar to show icons correctly.

      Reply
    Reply
  • Great class!! Very easy to use <eom>

    Posted by Legacy on 01/20/2004 12:00am

    Originally posted by: Dave Schumann

    see title

    Reply
  • TrackPopupMenu doesn't show icons

    Posted by Legacy on 04/23/2003 12:00am

    Originally posted by: Phil

    Hi,

    I have noticed that popup menus don't seem to load the toolbars properly. For example right click on the client area of the window in the sample app. The popup menu doesn't display the icons even though the LoadToolbar function was called.

    Phil

    Reply
  • how to convert it to Activex Control

    Posted by Legacy on 04/16/2003 12:00am

    Originally posted by: santosh

    how can i convert this into an ActiveX control , so that it can be added to any appliction

    Reply
  • Use in Non Doc/View App

    Posted by Legacy on 01/01/2003 12:00am

    Originally posted by: Steve

    If you used this code in a non doc/view app, you might have gotten a strange compiler error on line 1272 of BCMenu.cpp. Here's the fix:
    original:
    CString junk=OptionText;

    Error:
    g:\Visual Studio Projects\MFC Applications\My Program\BCMenu.cpp(1272): error C2440: 'initializing' : cannot convert from 'wchar_t *' to 'ATL::CStringT<BaseType,StringTraits>'
    with
    [
    BaseType=char,
    StringTraits=StrTraitMFC<char,ATL::ChTraitsCRT<char>>
    ]


    Fix:
    CString junk;
    junk.Format(_T(&s), OptionText);

    Reply
  • Can I add the owendrow menu to a dialog?

    Posted by Legacy on 12/29/2002 12:00am

    Originally posted by: akiy

    Just lick the title!

    Reply
  • New version (3.03) is here

    Posted by Legacy on 12/15/2002 12:00am

    Originally posted by: Good guy

    New version (3.03) is here
    http://www.codeproject.com/menu/bcmenu.asp

    I wonder why author didn't updated article here.

    Reply
  • Seletall option

    Posted by Legacy on 10/09/2002 12:00am

    Originally posted by: Chandira

    Hi,

    I want to know how does the selectall option in the edit menu works.
    It actullay said to call the CEdit function SetSel(0,-1).But it doesn't.

    May i get answer for implementing the selectall function.

    Thanks and Regards
    V.Chandira mohan

    Reply
  • Dockable Menu Band

    Posted by Legacy on 09/22/2002 12:00am

    Originally posted by: Lea Hayes

    Hi,

    I think that your classes are excellent. How easy would it be to combine your classes with those in the article called "DevStudio style Dockable Menu Bar(does not require MSIE)" to make the menu band dockable but at the same time having the great style of your classes?

    Keep up the great work!

    Yours Sincerely,
    Lea Hayes

    Reply
  • Great work

    Posted by Legacy on 09/04/2002 12:00am

    Originally posted by: Kyle

    Great job man....works great, thanks a lot!!!
    

    Reply
  • Loading, Please Wait ...

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

Top White Papers and Webcasts

  • You probably have several goals for your patient portal of choice. Is "community" one of them? With a bevy of vendors offering portal solutions, it can be challenging for a hospital to know where to start. Fortunately, YourCareCommunity helps ease the decision-making process. Read this white paper to learn more. "3 Ways Clinicians can Leverage a Patient Portal to Craft a Healthcare Community" is a published document owned by www.medhost.com

  • Instead of only managing projects organizations do need to manage value! "Doing the right things" and "doing things right" are the essential ingredients for successful software and systems delivery. Unfortunately, with distributed delivery spanning multiple disciplines, geographies and time zones, many organizations struggle with teams working in silos, broken lines of communication, lack of collaboration, inadequate traceability, and poor project visibility. This often results in organizations "doing the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds