Create an Owner Draw Menu - Step by Step

Environment: Visual C++ 6

For the owner draw control, I found the most difficult part is the menu. I have searched through the Web, but I only found some difficult examples. In this article, I will demonstrates the step for drawing our own menu. We'll make a menu class and a class for holding the menu data.

Let's take the SDI MFC application as an example. In the File Menu, click New to add a new project. Then, choose MFC Application Wizard (exe). In the project name, type in OwnerdrawMenu (just an example), and then click OK. In step one of the MFC App Wizard, choose Single Document. After pressing Finish, you are brought to the "New Project Information" page. You'll ignore this page, so press OK.

Let's make our menu data class first. It is just a simple class which has one CString member variable to hold the menu item's caption.

Right-click "OwnerdrawMenu classes" in the ClassView; then choose "New Class." For the class type, choose "Generic Class." In the "Name" editbox, type in "CMyMenuData" (just an example). Finally, click OK.

Add a public CString member variable which is called "m_strCaption"(just an example).

Next, we have to make a menu class. Right-click "OwnerdrawMenu classes" in the ClassView; then choose "New Class." For the class type, choose "Generic Class." In the "Name" editbox, type in "CMyMenu" (just an example). In the "Base Classes" section, type in "CMenu" in the "Derived From" editbox and choose "public" in the "As" listbox.

Our menu class is added. It's time to give some content to it. We will add three functions, some include statements, and two member variables in the CMyMenu header file (such as MyMenu.h).

#include "MyMenuData.h"
#include <vector>
using namespace std;

public:
  //a recursive function to change all the sub menu and menu items
  // to have the owner draw style
  void ChangeToOwnerDraw(CMyMenu* pMyMenu);
  //use for drawing
  virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
  //use for giving the dimension of the menu item
  virtual void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
private:
  vector<CMyMenu*> rgpMyMenu;         //use to handle sub menu
  vector<CMyMenuData*> rgpMyMenuData; //use to handle my menu data

We have to add the implementation in MyMenu.cpp, as shown below.

void CMyMenu::ChangeToOwnerDraw(CMyMenu *pMyMenu)
{
  CString str;                  //use to hold the caption temporarily
  CMyMenu* pMenu;               //use to hold the sub menu
  CMyMenuData* pMenuData;       //use to hold the menu data
  //get the number of the menu items of the parent menu
  int iMenuCount = pMyMenu->GetMenuItemCount();
  UINT nID; //use to hold the identifier of the menu items
  for (int i=0; i<iMenuCount; i++)
  {
    //get the caption of the menu item
    pMyMenu->GetMenuString(i, str, MF_BYPOSITION);
    pMenu = 0;       //reset pointer for safety
    pMenuData = 0;   //reset pointer for safety
    pMenuData = new CMyMenuData;
    pMenuData->m_strCaption = str;
    rgpMyMenuData.push_back(pMenuData);

    if (pMyMenu->GetSubMenu(i)) //if the parent menu has sub menu
    {
      pMyMenu->ModifyMenu(i, 
                             MF_BYPOSITION | MF_OWNERDRAW, 
                             0, 
                             (LPCTSTR)pMenuData);
      pMenu = new CMyMenu;
      rgpMyMenu.push_back(pMenu);
      HMENU hMenu = pMyMenu->GetSubMenu(i)->GetSafeHmenu();
      pMenu->Attach(hMenu);
      ChangeToOwnerDraw(pMenu);
    }
    else
    {
      nID = pMyMenu->GetMenuItemID(i);
      pMyMenu->ModifyMenu(i,
                             MF_BYPOSITION | MF_OWNERDRAW, 
                             (UINT)nID, 
                             (LPCTSTR)pMenuData);
    }
  }
}

void CMyMenu::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
  CMyMenuData* pMyMenuData = 
          (CMyMenuData*)lpMeasureItemStruct->itemData;
  //get the caption of the menu item
  CString str = pMyMenuData->m_strCaption;
  //assign the height of the menu item
  lpMeasureItemStruct->itemHeight = 23;0
  //assign the width of the menu item
  lpMeasureItemStruct->itemWidth = str.GetLength()*7;
}

void CMyMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
  CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
  //draw an orange background
  pDC->FillSolidRect(&lpDrawItemStruct->rcItem, 
                     RGB(255,150,0));

  //if the menu item is selected
  if ((lpDrawItemStruct->itemState & ODS_SELECTED) &&
    (lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE)))
  {
    //draw a blue background
    pDC->FillSolidRect(&lpDrawItemStruct->rcItem, 
                          RGB(0,150,255));
  }

  CMyMenuData* pMyMenuData = 
       (CMyMenuData*)lpDrawItemStruct->itemData;

  CString str = pMyMenuData->m_strCaption;
  //draw the caption of the menu item
  pDC->TextOut(lpDrawItemStruct->rcItem.left,
                  lpDrawItemStruct->rcItem.top, str);
}

CMyMenu::~CMyMenu()
{
  int iCount = rgpMyMenu.size();
  for (int i=0; i<iCount; i++)
  {
    rgpMyMenu[i]->Detach();
    delete rgpMyMenu[i];
    rgpMyMenu[i] = 0;
  }
  iCount = rgpMyMenuData.size();
  for (i=0; i<iCount; i++)
  {
    delete rgpMyMenuData[i];
    rgpMyMenuData[i] = 0;
  }
}

After we have added a lot of things to CMyMenu class, it's the time to use it in the CMainFrame class. Add the following to the MainFrm.h.

#include "MyMenu.h"

private:
  CMyMenu* pMyMenu;

At the end of the CMainFrame::OnCreate function, but before the line "return 0;", add the following code.

pMyMenu = new CMyMenu;
HMENU hMenu = ::GetMenu(GetSafeHwnd());
pMyMenu->Attach(hMenu);
SetMenu(pMyMenu);
pMyMenu->ChangeToOwnerDraw(pMyMenu);

We have to add a message handler for OnDestory, too. Right-click CMainFrame in the ClassView; choose "Add Windows Message Handler." Add WM_DESTORY handler. The code of the OnDestroy function is as the following.

void CMainFrame::OnDestroy() 
{
  CFrameWnd::OnDestroy();

  if (pMyMenu)
  {
    pMyMenu->Detach();
    delete pMyMenu;
    pMyMenu = 0;
  }
}

We have finished our long adventure. Press F7 for our final fire.

Downloads

Download demo project - 33 Kb



Comments

  • How do I change the font size?

    Posted by Gregory Cocco on 07/31/2014 10:10am

    Thanks for the article. How do I change the font size? I make my own font and then use "m_wndToolBar.SetFont(&m_font)" in the OnCreate method in the MainFrm.cpp in the demo project. Thanks again!!!

    Reply
  • Article helped me a lot

    Posted by pvaan kumar on 02/27/2013 09:42am

    thanks for the article. It is very useful and neat.

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

Top White Papers and Webcasts

  • Live Event Date: March 19, 2015 @ 1:00 p.m. ET / 10:00 a.m. PT The 2015 Enterprise Mobile Application Survey asked 250 mobility professionals what their biggest mobile challenges are, how many employees they are equipping with mobile apps, and their methods for driving value with mobility. Join Dan Woods, Editor and CTO of CITO Research, and Alan Murray, SVP of Products at Apperian, as they break down the results of this survey and discuss how enterprises are using mobile application management and private …

  • On-demand Event Event Date: February 12, 2015 The evolution of systems engineering with the SysML modeling language has resulted in improved requirements specification, better architectural definition, and better hand-off to downstream engineering. Agile methods have proven successful in the software domain, but how can these methods be applied to systems engineering? Check out this webcast and join Bruce Powel Douglass, author of Real-Time Agility, as he discusses how agile methods have had a tremendous …

Most Popular Programming Stories

More for Developers

RSS Feeds

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