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. |
|
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. |
|
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:
- Add the supplied helper code and header to your project
- Add OnMeasureItem and OnDrawItem to the window handling the popup menu
- Call the helper functions to handle the measuring and drawing of your popup menu title
item - 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