Why do you want to go today?
Home » Programming » Visual C++ » Custom Menu [Sept, 18 1999]

 

Prologue 

Most of us have tried to figure out how to make a nice menu like MS Office has. Every command menu that has shortcut in toolbar will display a same bitmap with the toolbar. We don't have, as far as I know, such API to do that directly. And if we started to build our own menu with a special window, which very hard because we can't use the TrackWindowPopup(...)API and we have to re-implement the whole thing again. Which is not a very good idea.

Creating a custom menu is not very difficult in MFC framework. We only need to derive MFC's CMenu class and override it's CMenu::DrawItem(...) and CMenu::MeasureItem(...) functions.

CMenu::DrawItem(...) will be called by the frame work every time each menu item will be drawn. So we override this member to draw our own menu.

CMenu::MeasureItem(...)will be called by the frame work for to determine the size of the rectangle for each menu item. We're going to override this to provide some space for our bitmap.

To display bitmap in our menu, I use CMapStringToPtr to hold resource id value. Our menu going to load bitmap dynamically and draw it down to our menu surface. 

Following codes will explain to you how the whole thing works out. I only put down important part from the whole codes. I hope I'll have enough space to provide downloadable project.

 

Implementation
First we have to create a class from CMenu, lets name our class CMSMenu:

class CMSMenu : public CMenu
{
public:
    CMSMenu();
    virtual ~CMSMenu();

    // Our own member function to add Menu item replace CMenu::InsertMenu(...)
    BOOL fInsert( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, 
                  LPCTSTR lpszNewItem = NULL, UINT nBitmapID =0);

protected:
    // Member variable to hold the Bitmap resource ID
    CMapStringToPtr m_Map;

    // Two member functions that we will override from CMenu
    virtual void DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct );
    virtual void MeasureItem( LPMEASUREITEMSTRUCT lpMeasureItemStruct );
};

CMSMenu::CMSMenu()
{
}

CMSMenu::~CMSMenu()
{
    m_Map.RemoveAll();
// Don't forget to cleanup our map object. 
}

BOOL CMSMenu::fInsert( UINT nPosition, UINT nFlags, UINT nIDNewItem, 
                       LPCTSTR lpszNewItem, UINT nBitmapID)
{
    m_Map.SetAt(lpszNewItem, (void*)nBitmapID);
    return CMenu::InsertMenu(nPosition, nFlags, nIDNewItem, lpszNewItem);
}

// Overriden DrawItem member

void CMSMenu::DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct )
{
    // We don't want to redraw grayed menu everytime it gets selected
    // so we just return without doing anything.

    if(lpDrawItemStruct->itemAction & ODA_SELECT &&
        lpDrawItemStruct->itemState & ODS_DISABLED)
        return;

    // Construct CDC from lpDrawItem member
    CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);

    // Get item's rectangle
    CRect rct = lpDrawItemStruct->rcItem;

    // Get String data
    CString str;
    str = (LPCTSTR) lpDrawItemStruct->itemData;

    // Draw the menu's backgroud. Blue if selected and gray otherwise.
    if(lpDrawItemStruct->itemState & ODS_SELECTED && 
        !(lpDrawItemStruct->itemState & ODS_DISABLED) )
    {
        pDC->FillSolidRect(rct, GetSysColor(COLOR_HIGHLIGHT));
        pDC->SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
    }
    else
    {
        pDC->FillSolidRect(rct, GetSysColor(COLOR_MENU));
        pDC->SetTextColor(GetSysColor(COLOR_MENUTEXT));
    }

    // Draw String data
    CRect rctText = rct;
    rctText.OffsetRect(28,0);
    pDC->DrawText(str, rctText, DT_SINGLELINE|DT_VCENTER);

    //Clear rectangle for bitmap
    CRect rctBmp = rct;
    rctBmp.right = rctBmp.left + 20;
    pDC->FillSolidRect(rctBmp,GetSysColor(COLOR_BTNFACE));

    // Get bitmap from resource with the Resource ID our m_Map member
    void* id;
    m_Map.Lookup(str, id);
    CBitmap bmp;
    bmp.LoadBitmap((UINT)id);

    if(id !=0 )
    {
        //Clear rectangle for bitmap
        CRect rctBmp = rct; 
        rctBmp.right = rctBmp.left + 20;
        pDC->FillSolidRect(rctBmp,GetSysColor(COLOR_BTNFACE));

        CBitmap bmp;
        bmp.LoadBitmap((UINT)id);

        // Determine the bitmap position
        CPoint pnt = rct.TopLeft();
        CSize sz = rct.Size();
        sz.cx = 16;
        sz.cy = 16;
        pnt.Offset(2,2);

        // Draw bitmap
        pDC->DrawState(pnt,sz, &bmp, DST_BITMAP | DSS_NORMAL);
        bmp.DeleteObject();

        // Draw Rectangle around bitmap if selected
        if(lpDrawItemStruct->itemState & ODS_SELECTED &&
            !(lpDrawItemStruct->itemState & ODS_DISABLED))
        pDC->DrawEdge(rctBmp, BDR_RAISEDINNER, BF_RECT);
    }

    // If the item menu is disabled, draw it gray.
    if(lpDrawItemStruct->itemState & ODS_DISABLED)
        fDrawDisabled(*pDC, rct);

    CMenu::DrawItem(lpDrawItemStruct);
}

// Overriden MeasureItem member
void CMSMenu::MeasureItem( LPMEASUREITEMSTRUCT lpMeasureItemStruct )
{
    CString str = (LPCTSTR) lpMeasureItemStruct->itemData;
    CRect rct(0,0,0,0);

    CDC *pDC = AfxGetApp()->m_pMainWnd->GetDC();

    // Get item rectangle by calculating item's text rectangle
    pDC->DrawText(str, rct, DT_CALCRECT|DT_SINGLELINE);

    // Get some space
    lpMeasureItemStruct->itemHeight = rct.Height()+4;
    lpMeasureItemStruct->itemWidth = rct.Width()+ 4;

    CMenu::MeasureItem(lpMeasureItemStruct);
}

 

Epilogue
This menu only work fine if you use the fInsert(...) member function to add each menu item with related bitmap resource ID. To have the menu load itself from resource, you'll have to put extra codes. I know this is not the best implementation code since I only write this once and haven't verify it yet, but I hope you grasp the main idea about overriding CMenu.

 


Home | inHere | Products | Projects | Programming Stuff
Sign guest book | View guest book

Copyright © 1999 AdNet®. All rights reserved. AdNet® is registered to Benni Adham.