Click here to Skip to main content
16,016,669 members
Articles / Desktop Programming / MFC
Article

Owner Drawn Menu with Icons, Titles and Shading

Rate me:
Please Sign up or sign in to vote.
4.95/5 (205 votes)
5 Dec 2003CPOL12 min read 3M   35.1K   522   1K
An easy use of owner drawn menu with variable styles like new Office products with titles, shading and icons.

STYLE_XPSTYLE_ORIGINAL
STYLE_XP_2003STYLE_ICY

Introduction

This class, CNewMenu, implements owner drawn menus derived from the CMenu class. The goal of this class is to have menus with the same look as the new menu from Microsoft products with Icons and Title support. I was inspired from the great class BCMenu from Brent Corkum. I took some ideas and almost every function but reimplemented almost all of the code and expanded it with some other nice functions. It was a hard work to change the look of the whole menu that belongs to an application - changing the border and adding shading. Furthermore, it was tricky changing all menus under windows 2000 or system menu under Windows XP when you are using themes. Over all, I hope it's now easy enough for you to use this class in new applications.

This new menu works under Windows 95/98/Me and Windows NT4.0/2000/XP. When you would like to use Chinese or some other special characters, you can compile your project with Unicode support. But keep in mind you must have installed the Unicode libraries with Visual Studio, otherwise you get a linking error.

Additionally, when you want your program to work on all platforms, you have to compile it with Visual Studio 6.0. I did not find out why the drawing didn't work correctly when it was compiled with Visual Studio 7.0 under Windows NT 4.0. Furthermore, when you do not like the flat border you can use the STYLE_XXX_NOBORDER and then menus will have the standard menu border like the system.

Special for legacy Windows like Win95 or WinNT 4.0

CNewMenu works also on those systems, but you don not have all features. So the new menu style office 2003 has only the standard menubarcolor, because changing of menubar- or menubackground is not supported on those systems. When you use the new Visual Studio 7.0 / 7.1 you must #define WINVER 0x0400 and then you won't get a runtime error.

Nice Titles in Menus

It's also possible to add a title to a menu. You can add a title on the top or on the left side of a menu. You can have a gradient or a solid color as a background. With the function SetMenuTitle you can add or remove a title from a menu. The gradient drawing also works on Windows 95.

C++
// Set a title to the system menu 
 m_SystemNewMenu.SetMenuTitle(_T("New Menu Sample"), 
                              MFT_GRADIENT|MFT_LINE|MFT_CENTER);

New Icons Effects Brightening, Glooming and Graying

 

Under menu style STYLE_XP you have the possibility to enable or disable the additional drawing style to icons. Disabled icons have a grayed look and non selected icons have a gloom look. You can see the difference between those drawing styles in the sample.

How to Use the CNewMenu in a Project

  1. The easiest way is to replace all CDialog, CFrameWnd, CMDIFrameWnd, CMDIChildWnd, CMiniFrameWnd and CMultiDocTemplate with the new classes CNewDialog, CNewFrameWnd, CNewMDIFrameWnd, CNewMDIChildWnd, CNewMiniFrameWnd and CNewMultiDocTemplate in your project.
  2. In the application derived from CWinApp's implementation, set the menustyle just before you create the frame or a dialog window in the function InitInstance.
    C++
    // Set drawing style of all menus to XP-Mode or STYLE_ORIGINAL
    CNewMenu::SetMenuDrawMode(CNewMenu::STYLE_XP);
  3. When you have a multi document template, add the loading code for menu icons just after creating the template.
    C++
    CNewMultiDocTemplate* pDocTemplate; 
    pDocTemplate = new CNewMultiDocTemplate(IDR_MDINEWTYPE,
                                            RUNTIME_CLASS(CMDINewMenuDoc),
                                            // custom MDI child frame
                                            RUNTIME_CLASS(CChildFrame),
                                            RUNTIME_CLASS(CMDINewMenuView));
    
    AddDocTemplate(pDocTemplate);
    
    
    // Loading toolbar icons into the menu 
    pDocTemplate->m_NewMenuShared.LoadToolBar(IDR_DRAWBAR);
  4. Insert at the end of OnCreate function of the MainFrame the loading code for the icons. For example:
    C++
    m_DefaultNewMenu.LoadToolBar(IDR_MAINFRAME);
  5. Finally, don't forget to include the definition from the NewMenu.h in stdafx.h and add NewMenu.cpp to the project.
  6. Now you are ready to use the class.

How to Use the CNewMenu in a Dialog-based Application

  1. In the application set the menustyle before you create the dialog in the function InitInstance.
    C++
    // Set drawing style of all menus to XP-Mode
    CNewMenu::SetMenuDrawMode(CNewMenu::STYLE_XP);
  2. Change all occurances of CDialog to CNewDialog in your Project.
  3. For loading bitmaps in to the menu the best place is in OnInitDialog function after calling the base class function.
    C++
    // CDialogDlg could be your Dialog.
    BOOL CDialogDlg::OnInitDialog() 
    { 
      // Call the baseclass
      CNewDialog::OnInitDialog(); 
    
     
      // Load icons to the menu 
    
    
      m_DefaultNewMenu.LoadToolBar(IDR_MAINFRAME);
    }
  4. Don't forget to include the the definition from the NewMenu.h in stdafx.h and add NewMenu.cpp to the project.
  5. Special: I added the automatically update of the menu. You can add an OnUpdate-handler like in a frame window for updating menuitems. When you want to have the standard handling of menu in your dialog you should overwirte OnInitMenuPopup likecall of the update handler change in your
    C++
    // For disable automatically
    void CDialogDlg::OnInitMenuPopup(CMenu* pMenu, UINT nIndex,
      BOOL bSysMenu)
    {
      CNewFrame<CDialog>::OnInitMenuPopup(pMenu,nIndex,bSysMenu);
    }

How to Use the CNewMenu in a Special Frame Window

  1. Normally you should overwrite measureitem, menuchar and initmenupopup. For this purpose I use the CNewFrame template class. So you can replace CYourSpecialBase with CNewFrame<CYourSpecialBase> in your project.
  2. Don't change it in IMPLEMENT_DYNAMIC, IMPLEMENT_SERIAL or IMPLEMENT_DYNACREATE to sub-class the new one.
    C++
    IMPLEMENT_DYNAMIC(CYourNewSpecialFrame, CYourSpecialBase)
  3. Don't forget to change the baseclass in BEGIN_MESSAGE_MAP.
    C++
    BEGIN_MESSAGE_MAP(CYourNewSpecialFrame, CNewFrame<CYourSpecialBase>) 
      //{{AFX_MSG_MAP(CYourNewSpecialFrame) 
      //}}AFX_MSG_MAP
     END_MESSAGE_MAP()
  4. For loading bitmaps in to the menu, you can add the code in OnCreate function.
  5. Finally don't forget to include the the definition from the NewMenu.h in stdafx.h and add NewMenu.cpp to the project.

How to Use the CNewMenu in an MDI Application

  1. Change in the the application's base class in the BOOL InitInstance() function from CMultiDocTemplate to CNewMultiDocTemplate:
    CNewMultiDocTemplate* pDocTemplate; 
    pDocTemplate = new CNewMultiDocTemplate(IDR_MDINEWTYPE,
                                            RUNTIME_CLASS(CMDINewMenuDoc),
                                            // custom MDI child frame
                                            RUNTIME_CLASS(CChildFrame),  
                                            RUNTIME_CLASS(CMDINewMenuView));
    AddDocTemplate(pDocTemplate);
  2. Now insert the following code for loading additional bitmaps from a toolbar into the menus.
    // Loading toolbar icons into the menu 
    pDocTemplate->m_NewMenuShared.LoadToolBar(IDR_DRAWBAR);
           
    
    // Set drawing style of all menus to XP-Mode
    CNewMenu::SetMenuDrawMode(CNewMenu::STYLE_XP);
  3. Change the base class from the main frame class (CMainFrame) from CMDIFrameWnd to CNewMDIFrame in the definition and implementation file.
      // Change the base class in the following makro
    IMPLEMENT_DYNAMIC(CMainFrame, CNewMDIFrameWnd)
    // don't forget to change here the baseclass
    BEGIN_MESSAGE_MAP(CMainFrame, CNewMDIFrameWnd)
    //{{AFX_MSG_MAP(CMainFrame) 
    // NOTE - the ClassWizard will add and remove mapping macros here. 
    // DO NOT EDIT what you see in these blocks of generated code ! 
    ON_WM_CREATE() 
    //}}AFX_MSG_MAP
    END_MESSAGE_MAP()
  4. Add at the end of int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) for loading bitmap in the default menu:
    m_DefaultNewMenu.LoadToolBar(IDR_MAINFRAME);
  5. Finally, change the base class of the child frame from CMDIChildWnd to CNewMDIChildWnd. Do the same for all derived classes.
  6. Don't forget to include the the definition from the NewMenu.h in stdafx.h and add NewMenu.cpp to the project.

How to Use the CNewMenu in an SDI Application

  1. In the application set the menustyle before you create the dialog in the function InitInstance.
    // Set drawing style of all menus to XP-Mode
    CNewMenu::SetMenuDrawMode(CNewMenu::STYLE_XP);
  2. Change all occurances of CDialog to CNewDialog in your Project.
  3. Change the base class from the main frame class (CMainFrame) from CFrameWnd to CNewFrameWnd in definition and implementation file.
  4. Add at the end of int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) for loading bitmap in the default menu:
    m_DefaultNewMenu.LoadToolBar(IDR_MAINFRAME);
  5. Don't forget to include the the definition from the NewMenu.h in stdafx.h and add NewMenu.cpp to the project.

How to Replace System Menus in System Dialogs

Sometimes it would be nice to change the menu in a message box or in a CFileDialog Unfortunately, a MessageBox has no base class for overriding. Another problem is the file dialog. There are at least two dialogs and the CFileDialog only sub-classes the child dialog of the FileDialog displayed. For those and other dialogs, I added a special sub-classing mechanism.

C++
// Subclass dialog with system menu 
CNewMenuHelper myHelper(NEW_MENU_DIALOG_SUBCLASS|NEW_MENU_DIALOG_SYSTEM_MENU); 
AfxMessageBox(_T("You must restart the application"), MB_OK);

What can one do when he or she deso not want to have a menu border replacing in the next dialog displayed? It is very simple! Just place the CNewMenuHelper before your call like in the following sample.

C++
// Subclass dialog with normal border painting 
CNewMenuHelper myHelper(NEW_MENU_DEFAULT_BORDER); 
AfxMessageBox(_T("You must restart the application"), MB_OK);

Color Replacing of Bitmaps with 16 Colors

The MFC library replaces some colors depending on the chosen system colors. When you use a black line in the bitmap, the color black will be replaced with the system color of COLOR_BTNTEXT. Following the color mapping map. By the way, it is the same color mapping of the toolbars.

  • The color RGB(0x00,0x00,0x00) will be changed to COLOR_BTNTEXT (black)

  • The color RGB(0x80,0x80,0x80) will be changed to COLOR_BTNSHADOW (dark gray)

  • The color RGB(0xC0,0xC0,0xC0) will be changed to COLOR_BTNFACE (bright gray)

  • The color RGB(0xFF,0xFF,0xFF) will be changed to COLOR_BTNHIGHLIGHT (white)

Adding Icons to Menus

There are several possibilities for adding icons to a menu. An easy way is with a toolbar resource. But developer studio only supports 16 colors for toolbar-bitmaps. What you can do is: first define your toolbar then replace the saved toolbar-bitmap with a hicolor bitmap by hand. Additionally, you can define every icon in a different bitmap and assign it to a menu item. My way was to define a bitmap like in an image list, make a helper table with the resource id of the bitmap and the size of an icon followed by the ids of the icons. The end of the table must be marked with the id number NULL.

C++
// Define the table
static WORD ToolId[] =
              {  IDR_NEWMENU256, // Resource id of the bitmap-icons 
                 16, 15,         // Dimension of each icon
 

                 ID_FILE_NEW,    // First icon's ID
                 ID_FILE_OPEN,   // Second icon's ID 
                 ID_FILE_SAVE,   // and so on...
 
                 NULL}; // Marker for end of table 

//Load the definition into menu with transparent color
m_DefaultNewMenu.LoadToolBar(ToolId,RGB(192,192,192));

How to Add an Icon to a Submenu

It is very simple to add or change the title of a submenu entry when you know the menu text. The helper function ModifyODMenu can be used.

// set the icon IDB_MORE_C to the submenu "More A"
m_NewMenuShared.ModifyODMenu(0,_T("More A"),IDB_MORE_C);

Menus under Windows 2000

It was strange to find that under Windows 2000 you have a window menu with handle 0x10012 which is shared by all applications. (I think under Windows 98 the menu is 0x0090). I don't know why but this is also a reason of the specialty for some effects belonging to the sub-classed menu. First, when you have disabled menu effects, the menu windows are alternating created or shown. That means the first shown menu is mostly the special menu - never created nor destroyed - only shown and disabled, and the second is created normally and destroyed after closing. The third time, sometimes, the special window is shown again and then the next menu is created normally. For this reason, when you sub-class the special menu, you are responsible for restoring all values before unsub-classing it. Be careful when you change some flags or styles, because the other applications like having the standard menu. You can also receive bluescreens when you forget to unsub-class or when changing painting areas belonging to that menu. This is really ugly! Second, when you enable menu effects every displayed menu is created from scratch. For this reason it doesn't matter if you don't restore all flags or styles!

Let me explain how I got the goal to sub-class all menus. First, you can't sub-class the system menu class #32768 of an application; I was also looking for other possibilities. I decided to create a windows hook for all windows messages and catch all messages for creating a window. After checking for the window class, when I found the menu class, I subclassed it. But how I can sub-class the special menu, which is never created? I found that one of the first messages which were being sent to that menu, was 0x01E2 and one of the latest WM_SHOWWINDOW. So I caught these messages for sub- and unsub-classing the special menu. Only between those two messages do you have access to the special window! I don't know why, so be careful.

How to Paint the Border

After I sub-class the menu window I can draw the border by by processing WM_NCPAINT message. But how is the shading effect created? Just before the menu is shown on the screen I save the menu region in a bitmap. This is the reason why it does not work when the background has been changed. Then I combined it with a shadow drawing. Another way would be using layered windows, but this will not work on all systems like windows 95/98.

Gradient Menu Bar

Since Windows 98 or Windows 2000 it's possible to set a brush for menu and submenus. So it's easy to change the color. But how can we have a gradient in the bar? Well you define a bitmap-brush with the width of the screen and set it to the menu of the frame menu. Are there any clues more? Oh yes, on non NT system like Windows98/Me you have to set the brush origin for not to have an offset with the bitmap when you repaint a part of the menu bar item.

System Menu under Windows XP

I thought owner drawing a menu is easy all the time, but on Windows XP, with themes enabled, it is a problematic thing - especially the main frame when the system menus were displayed. Instead of owner drawing, the menu items were drawn by the frame. The frame had painted the windows button of the width of the menu at the place of the menu item instead of drawing it, so I found a work-around. I increased the menu item's identifier before showing the menu and restored it after closing it. So it works under Windows 2000 and XP.

Important Info for Debugging the New Menu under Windows 2000

You should never stop debugging when you are in middle of painting the menus because the special menu wouldn't be restored or unsub-classed. Furthermore, there were a lot of strange effects. It is not forbidden but be careful and have a lot time for rebooting the system when it doesn't work correctly after stopping the debugger.

How to use CNewToolBar

CNewToolBar can only be used with CNewMenu. In your project you need to replace CToolBar with CNewToolBar. Additionally when you like to have the dokingbar in the same color as the menu bar you must define USE_NEW_DOCK_BAR before you include newmenu.h.

History

30 November 2003 - Version 1.18

  • Expanded CNewToolBar with ICY / 2003 styles

11 September 2003 - Version 1.17

  • Better handling of menubar brushes for STYLE_XP and STYLE_XP_2003
  • Drawing of menuicons on WinNT 4.0 fixed

12 July 2003 - Version 1.16

  • Added gradient-dockbar (CNewDockBar) for office 2003 style
  • fixed menu checkmarks with bitmaps
  • Drawing to a memory-DC minimizing flickering
  • with of menu-bar-item without "&" now better

22 June 2003 - Version 1.15

  • Shortkey-handling for ModifyODMenu and InsertODMenu
  • Changed os identifer for OS>5.0 to WinXP
  • Color scheme for Office menu changed and updating menubarcolor too
  • Gradientcolor for Office 2003 bar
  • XP-changing settings Border Style => fixed border painting without restarting

23 April 2003 - Version 1.14

  • Fixed the window menu with extreme width
  • Shortkey-handling for added menu item with AppendMenu is now better
  • Measureitem for default item corrected
  • New menu style Office 2003 (changing menubar color is not supported on Win95 & WinNT 4.0)
  • Menubardrawing on deactivated app corrected for win95 & WinNT 4.0

23 January 2003 - Version 1.13

  • SetMenuText fixed.
  • Menubar color with theme fixed.
  • Added some helper for borderhandling of non CNewMenu

10 November 2002 - Version 1.11

  • Border drawing with menu animation under Win 98 corrected.
  • Border drawing / repainting when submenu closed fixed.
  • New menu style ICY.
  • Helperclasses for changing system menu for CommonDialog.

30 July 2002 - Version 1.10

  • Sharing Icons between menu for saving GDI-Resources.
  • New effects for drawing icons under STYLE_XPxx.
  • Recoloring of menu icons by changing system colors.
  • Drawing of the scroll button by extreme big menus a little bit better.
  • Mixing different icon sizes in a menu is now supported but they are not zoomed to the same size.

Known bugs

  • Menu border with shade is not updated when underground has been changed.
  • The High Contrast Mode is not supported with all right colors.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralSimple CNewToolBar error. Pin
jcem29-Dec-07 14:56
jcem29-Dec-07 14:56 
AnswerRe: Simple CNewToolBar error. Pin
Bruno Podetti2-Jan-08 12:25
Bruno Podetti2-Jan-08 12:25 
QuestionAbout VC7 to VC8 Pin
银木头13-Dec-07 22:38
银木头13-Dec-07 22:38 
AnswerRe: About VC7 to VC8 Pin
Bruno Podetti2-Jan-08 11:52
Bruno Podetti2-Jan-08 11:52 
GeneralRe: About VC7 to VC8 Pin
Alisdair Piercy8-Feb-08 10:38
Alisdair Piercy8-Feb-08 10:38 
GeneralRe: About VC7 to VC8 Pin
Bruno Podetti8-Feb-08 21:33
Bruno Podetti8-Feb-08 21:33 
AnswerRe: About VC7 to VC8 Pin
wr12728-Oct-08 22:07
wr12728-Oct-08 22:07 
AnswerRe: About VC7 to VC8 [modified] Pin
golocy24-May-09 22:40
golocy24-May-09 22:40 
If you use VC8 or later,you should add this code in here ,like this...

typedef CNewFrame<baseclass> ThisClass; //....Add in here
typedef CNewFrame<baseclass> MynewFrame;
...

static const AFX_MSGMAP_ENTRY* GetMessageEntries()
{
static const AFX_MSGMAP_ENTRY Entries[] =
{
ON_WM_MEASUREITEM()
....
};
return Entries;
}

Because of after the maro is extended,The ThisClass have not definded in here;

And if you have more error about call xxx lost argument..you may also modify these code in here ..

AddTheme(new CMenuTheme(CNewMenu::STYLE_ICY,
CNewMenu::MeasureItem_Icy,
CNewMenu::DrawItem_Icy,
CNewMenu::DrawMenuTitle,TRUE));
you must add the char '&' in front of CNewMenu:: like this...

AddTheme(new CMenuTheme(CNewMenu::STYLE_ICY,
&CNewMenu::MeasureItem_Icy,
&CNewMenu::DrawItem_Icy,
&CNewMenu::DrawMenuTitle,TRUE));

Do what want to do,Go where want to go ,nothing is impossible!

modified on Monday, May 25, 2009 4:47 AM

GeneralAdding a icon Pin
sunil kumar verma10-Dec-07 0:27
sunil kumar verma10-Dec-07 0:27 
GeneralRe: Adding a icon Pin
Bruno Podetti2-Jan-08 11:51
Bruno Podetti2-Jan-08 11:51 
GeneralQuery On MainMenu BackColor Pin
Mr. Raj7-Nov-07 17:23
Mr. Raj7-Nov-07 17:23 
AnswerRe: Query On MainMenu BackColor Pin
Bruno Podetti2-Jan-08 12:14
Bruno Podetti2-Jan-08 12:14 
QuestionPopUpMenu Pin
amigian7416-Oct-07 4:54
amigian7416-Oct-07 4:54 
QuestionHow to use in a dll? Pin
hjw200725-Sep-07 22:41
hjw200725-Sep-07 22:41 
GeneralOdd behaviour for popup menu Pin
Nov.Ice22-Sep-07 2:23
Nov.Ice22-Sep-07 2:23 
AnswerRe: Odd behaviour for popup menu Pin
Bruno Podetti23-Sep-07 22:30
Bruno Podetti23-Sep-07 22:30 
QuestionRe: Odd behaviour for popup menu Pin
Nov.Ice4-Oct-07 11:28
Nov.Ice4-Oct-07 11:28 
QuestionLoadBarState Pin
Samuel Gerber18-Sep-07 2:29
Samuel Gerber18-Sep-07 2:29 
AnswerRe: LoadBarState Pin
larusoft21-Oct-07 1:43
larusoft21-Oct-07 1:43 
GeneralProblem with the buttons highlighting in Vista Pin
nipou484-Sep-07 8:15
nipou484-Sep-07 8:15 
GeneralButton problem Pin
hansen.Xue27-Aug-07 3:43
hansen.Xue27-Aug-07 3:43 
QuestionDifferent colors used under W2K and WXP Pin
™byTM24-Jul-07 23:14
™byTM24-Jul-07 23:14 
QuestionLost the system menu of child frame window Pin
larusoft23-Jul-07 2:59
larusoft23-Jul-07 2:59 
AnswerRe: Lost the system menu of child frame window Pin
Bruno Podetti23-Jul-07 4:02
Bruno Podetti23-Jul-07 4:02 
GeneralRe: Lost the system menu of child frame window Pin
larusoft21-Oct-07 1:50
larusoft21-Oct-07 1:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.