Creating Popup Menus with Titles

This article was contributed by Mark E. Pennell.

Introduction

Popups, popups everywhere!  Have you noticed that we now live in an age of the
endless collection of popup menus?  In practically every modern application, there
exists several UI objects which have their own popup menus.  One must admit that
these "context" menus have provided a needed boost in working with applications
with ‘heavy’ UIs.  However, there are times where the user of your application may be
a little confused about to which ‘item’ the invoked popup menu refers.

Let’s give an example, however contrived it may be. Imagine
using a home architecting product in which you can click items in your room layout to
invoke context menus. When you click on the chair in the living room, you get the context
menu to the right. Now, you select the ‘Remove’ command.  Oops! 
Your carefully designed Persian rug disappears from under the chair you thought you had
selected.
   menu1.gif (2879 bytes)

Okay … maybe not the best example I could have found, but hopefully you are both
laughing (not just at me) and understanding that there are cases where a slightly more
user friendly approach to context menus might be useful.  So what might this be?

Titled Popup Menus

There has been a recent UI trend involving the addition of titles to popup menus.
  These titles are used to convey the exact object to which the context applies.
  These are also being used to prompt the user with clear information on why they
should make a menu selection.  As an example of the latter, you may have seen
pulldown toolbar buttons where you can also select the ‘default’ action to occur when the
toolbar button is left clicked.  It is common to see the popup menu for the button
start with the title "Set Default" appearing in bold as the first item in the
menu.  This item is not selectable, but is a visual indicator better describing the
use/reason for the menu.

So to finish our example above, to the right is a titled context menu
which might have better helped our user.
  menu2.gif (3813 bytes)

Disclaimer for UI purists – This article is not meant to give an opinion on whether or
not this is good UI style, but rather to provide information on implementing this feature
to those who are interested.  If you wish to add titled popup menus to your product,
I hope this code helps you accomplish your goal.

Method 1 – SetMenuDefaultItem(…)

The Win32 API provides a single function which will “bold” a menu item to
indicate that it is the default menu item. You can disable the menu item as
well to meet most of our goals above. The item will still invert as you move
the mouse over it, which is not as desireable as our example/description above.


//
// This function adds a title entry to a popup menu
//
void AddMenuTitle(CMenu* popup, LPCSTR title)
{
    // insert a separator item at the top
    popup->InsertMenu(0, MF_BYPOSITION | MF_SEPARATOR, 0, title);

    // insert title item
    // note: item is not selectable (disabled) but not grayed
    popup->InsertMenu(0, MF_BYPOSITION | MF_STRING | MF_DISABLED, 0, title);

    SetMenuDefaultItem(popup->m_hMenu, 0, TRUE);
}

Method 2 – Owner Drawn Item

This method is more involved, but it gives you full control over the display
of the titled item. With this method, moving the mouse over the item does
not cause it to highlight as in Method 1.

This method also doubles as a good tutorial on owner drawn menu items in general.

Utilization Example Code

The following steps can be used to create titled popup menus in your application
utilizing the supplied implementation code for Method 2:

  1. Add the supplied helper code and header to your project
  2. Add OnMeasureItem and OnDrawItem to the window handling the popup menu
  3. Call the helper functions to handle the measuring and drawing of your popup menu title
    item
  4. Setup the popup menu to have a title just before displaying (tracking) the popup menu

The following is a code snippet from a simple dialog class which added the above example
titled popup menu:


void CExampleDlg::OnContextMenu(CWnd* pWnd, CPoint point) {    // load the popup menu to display    CMenu menu;    menu.LoadMenu(idrMenu1);    CMenu* popup = menu.GetSubMenu(0);    // call the helper function to setup this as a titled popup menu    AddMenuTitle(popup);    // display the popup menu    popup->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, this); } void CExampleDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) {    if (ODT_MENU == lpDrawItemStruct->CtlType)    {       // call our helper function to draw the item title       DrawPopupMenuTitle(lpDrawItemStruct, "Persian Rug");    }    else    {       CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);    } } void CExampleDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) {    if (ODT_MENU == lpMeasureItemStruct->CtlType)    {       // call our helper function to measure the item title       MeasurePopupMenuTitle(lpMeasureItemStruct, "Persian Rug");    }    else    {       CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);    } }

The header file…. (TitledMenu.h)…


#ifndef __TitledMenu_h #define __TitledMenu_h void AddMenuTitle(CMenu* popup); void MeasurePopupMenuTitle(LPMEASUREITEMSTRUCT mi, LPCSTR menuTitle); void DrawPopupMenuTitle(LPDRAWITEMSTRUCT di, LPCSTR menuTitle); #endif

The Implementation File (TitledMenu.cpp)


#include "stdafx.h" #include "TitledMenu.h" // // This function adds a title entry to a popup menu // void AddMenuTitle(CMenu* popup) {     // insert a separator item at the top     popup->InsertMenu(0, MF_BYPOSITION | MF_SEPARATOR, 0, (LPSTR)0);     // insert an empty owner-draw item at top to serve as the title     // note: item is not selectable (disabled) but not grayed     popup->InsertMenu(0, MF_BYPOSITION | MF_OWNERDRAW | MF_STRING | MF_DISABLED, 0, (LPSTR)0); } // // This function creates the bold font for the popup menu's title // static HFONT CreatePopupMenuTitleFont() {     // start by getting the stock menu font     HFONT font = (HFONT)GetStockObject(ANSI_VAR_FONT);     if (font)     {         // now, get the complete LOGFONT describing this font         LOGFONT lf;         if (GetObject(font, sizeof(LOGFONT), &lf))         {             // set the weight to bold             lf.lfWeight = FW_BOLD;             // recreate this font with just the weight changed             font = CreateFontIndirect(&lf);         }     }     // return the new font - Note: Caller now owns this GDI object     return font; } // // This is a helper function to measure the popup menu's title item // void MeasurePopupMenuTitle(LPMEASUREITEMSTRUCT mi, LPCSTR menuTitle) {     // create the font we will use for the title     HFONT font = CreatePopupMenuTitleFont();     if (font)     {         // get the screen dc to use for retrieving size information         CDC dc;         dc.Attach(::GetDC(NULL));         // select the title font         CFont* old = (CFont*)dc.SelectObject(CFont::FromHandle(font));         // compute the size of the title         CSize size = dc.GetTextExtent(menuTitle);         // deselect the title font         dc.SelectObject(old);         // delete the title font         DeleteObject(font);         // add in the left margin for the menu item         size.cx += GetSystemMetrics(SM_CXMENUCHECK)+8;         // return the width and height         mi->itemWidth = size.cx;         mi->itemHeight = size.cy;     } } // // This is a helper function to draw the popup menu's title item // void DrawPopupMenuTitle(LPDRAWITEMSTRUCT di, LPCSTR menuTitle) {     // create the font we will use for the title     HFONT font = CreatePopupMenuTitleFont();     if (font)     {         // create the background menu brush         HBRUSH bgb = CreateSolidBrush(GetSysColor(COLOR_MENU));         // fill the rectangle with this brush         FillRect(di->hDC, &di->rcItem, bgb);         // delete the brush         DeleteObject(bgb);         // set text mode to transparent         int mode = SetBkMode(di->hDC, TRANSPARENT);         // set text color to menu text color         COLORREF color = SetTextColor(di->hDC, GetSysColor(COLOR_MENUTEXT));         // select this font into the dc         HFONT old = (HFONT)SelectObject(di->hDC, font);         // add the menu margin offset         di->rcItem.left += GetSystemMetrics(SM_CXMENUCHECK)+8;         // draw the text left aligned and vertically centered         DrawText(di->hDC, menuTitle, -1, &di->rcItem, DT_SINGLELINE|DT_VCENTER|DT_LEFT);         // deselect the font         SelectObject(di->hDC, old);         // restore the text background mode         SetBkMode(di->hDC, mode);         // restore the text color         SetTextColor(di->hDC, color);     } }

About the Author

Mark E. Pennell is a Senior Engineer for
Visioneer, Inc. of Fremont, CA., and a former Windows Programming instructor for the
University of California – Santa Cruz Extension.  Mark telecommutes to work from his
home in Austin, TX.

Last updated: 13 May 1998

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read