47.2.4 Creating Owner-drawn Menu Items

If you need to have complete control over the appearance of a menu item, you can use an owner-drawn menu item in your application. This section describes the steps involved in creating and using an owner-drawn menu item.

47.2.4.1 Setting the MF_OWNERDRAW Flag

You can't define an owner-drawn item in your application's resource-definition file. Instead, you must create a new item or modify an existing one by using the MF_OWNERDRAW menu flag. You can use any of the following functions to specify an owner-drawn menu item:

AppendMenu

InsertMenu

ModifyMenu

When you call any of these functions, you can pass a 32-bit value as the lpNewItem parameter. This value can represent any information that is meaningful to your application, and will be available to your application when the item is to be displayed. For example, the value could contain a pointer to a structure; the structure, in turn, might contain a string and the handle of a logical font that your application will use to draw the string.

47.2.4.2 Responding to the WM_MEASUREITEM Message

Before Windows displays an owner-drawn menu item for the first time, it sends the WM_MEASUREITEM message to the window procedure of the window that owns the item's menu. This message contains a pointer to a MEASUREITEMSTRUCT structure that identifies the item and contains the optional 32-bit value for the item. When your window procedure receives the WM_MEASUREITEM message, it must fill in the itemWidth and itemHeight members of the structure before returning from processing the message. Windows uses the information in these members when creating the bounding rectangle in which your application draws the menu item; it also uses the information to detect when the user chooses the item.

47.2.4.3 Responding to the WM_DRAWITEM Message

Whenever the item must be drawn (for example, when it is first displayed or when the user chooses it as a command), Windows sends the WM_DRAWITEM message to the window procedure of the menu's owner window. This message contains a pointer to a DRAWITEMSTRUCT structure. Like the MEASUREITEMSTRUCT structure, DRAWITEMSTRUCT contains information about the item and its optional 32-bit data. In addition, DRAWITEMSTRUCT contains flags that indicate the state of the item (such as grayed or checked) as well as a bounding rectangle and a device context with which your application will draw the item.

In response to the WM_DRAWITEM message, your application must perform the following actions before returning from processing the message:

1.Determine the type of drawing that is necessary. To do so, check the itemAction member of the DRAWITEMSTRUCT structure.

2.Draw the menu item appropriately, using the bounding rectangle and device context obtained from the DRAWITEMSTRUCT structure. Your application must draw only within the bounding rectangle. For performance reasons, Windows doesn't clip portions of the image that are drawn outside the rectangle.

3.Restore all GDI objects selected for the menu item's device context.

For example, if the menu item is selected, Windows sets the itemAction member of the DRAWITEMSTRUCT structure to ODA_SELECT and sets the ODS_SELECTED flag in the itemState member. This is your application's cue to redraw the menu item so that the item indicates that it has been selected.

47.2.4.4 Example: Setting Fonts for Menu-Item Strings

This section contains code from an application that uses owner-drawn menu items in a pop-up menu. The pop-up menu contains items that set the attributes of the current font; the items are displayed using the appropriate font attribute, as shown in the following illustration:

The following example shows how the menu is defined in the resource-definition file. The strings for the Regular, Bold, Italic, and Underline items are assigned at run time, so their strings are empty in the resource-definition file.

MainMenu MENU

BEGIN

POPUP "&Character"

BEGIN

MENUITEM "", IDM_REGULAR

MENUITEM SEPARATOR

MENUITEM "", IDM_BOLD

MENUITEM "", IDM_ITALIC

MENUITEM "", IDM_ULINE

END

END

The application's window procedure processes the messages involved in using owner-drawn menu items. The application uses the WM_CREATE message to do the following:

Set the MF_OWNERDRAW flag for the menu items

Set the strings for the menu items

Obtain handles of the fonts used to draw the items

Obtain the text and background colors values for selected menu items

The strings and font handles are stored in an array of application-defined MYITEM structures. The application-defined function GetAFont creates a font that corresponds to the given font attribute and returns the handle of the font. The handles are destroyed during the processing of the WM_DESTROY message.

During the processing of the WM_MEASUREITEM message, the example gets the width and height of a menu-item string and copies these values into the MEASUREITEMSTRUCT structure. Windows uses the width and height values to calculate the size of the pop-up menu.

During the processing of the WM_DRAWITEM message, the example draws a menu item's string, leaving room next to the string for the check mark bitmap. If the user has selected the item, the example uses the selected text and background colors to draw the item.

LRESULT APIENTRY MainWndProc(hwnd, uMsg, wParam, lParam)

HWND hwnd;

UINT uMsg;

WPARAM wParam;

LPARAM lParam;

{

typedef struct _MYITEM {

HFONT hfont;

LPSTR psz;

} MYITEM; /* structure for item font and string */

MYITEM *pmyitem; /* pointer to item's font and string */

static MYITEM myitem[CITEMS]; /* array of MYITEMS */

static HMENU hmenu; /* handle of main menu */

static COLORREF crSelText; /* text color of selected item */

static COLORREF crSelBkgnd; /* bkgnd color of selected item */

COLORREF crText; /* text color, unselected item */

COLORREF crBkgnd; /* bkgnd color, unselected item */

LPMEASUREITEMSTRUCT lpmis; /* points to item item data */

LPDRAWITEMSTRUCT lpdis; /* points to item drawing data */

HDC hdc; /* handle of screen DC */

SIZE size; /* menu-item text extents */

DWORD dwCheckXY; /* check mark dimensions */

WORD wCheckX; /* check mark width */

int nTextX; /* width of menu item */

int nTextY; /* height of menu item */

int i; /* loop counter */

HFONT hfontOld; /* handle of old font */

BOOL fSelected = FALSE; /* menu-item selection flag */

switch (uMsg) {

case WM_CREATE:

/*

* Modify the Regular, Bold, Italic, and Underline

* menu items to make them owner-draw items. Associate

* a MYITEM structure with each item to contain the

* string and font handle for each item.

*/

hmenu = GetMenu(hwnd);

ModifyMenu(hmenu, IDM_REGULAR, MF_BYCOMMAND |

MF_CHECKED | MF_OWNERDRAW, IDM_REGULAR,

(LPTSTR) &myitem[REGULAR]);

ModifyMenu(hmenu, IDM_BOLD, MF_BYCOMMAND |

MF_OWNERDRAW, IDM_BOLD, (LPTSTR) &myitem[BOLD]);

ModifyMenu(hmenu, IDM_ITALIC, MF_BYCOMMAND |

MF_OWNERDRAW, IDM_ITALIC,

(LPTSTR) &myitem[ITALIC]);

ModifyMenu(hmenu, IDM_ULINE, MF_BYCOMMAND |

MF_OWNERDRAW, IDM_ULINE, (LPTSTR) &myitem[ULINE]);

/*

* Retrieve each item's font handle and copy it into

* the hfont member of each item's MYITEM structure.

* Also, copy each item's string into the structures.

*/

myitem[REGULAR].hfont = GetAFont(REGULAR);

myitem[REGULAR].psz = "Regular";

myitem[BOLD].hfont = GetAFont(BOLD);

myitem[BOLD].psz = "Bold";

myitem[ITALIC].hfont = GetAFont(ITALIC);

myitem[ITALIC].psz = "Italic";

myitem[ULINE].hfont = GetAFont(ULINE);

myitem[ULINE].psz = "Underline";

/*

* Retrieve the text and background colors of selected

* menu text.

*/

crSelText = GetSysColor(COLOR_HIGHLIGHTTEXT);

crSelBkgnd = GetSysColor(COLOR_HIGHLIGHT);

return 0;

case WM_MEASUREITEM:

/* Retrieve a device context for the main window. */

hdc = GetDC(hwnd);

/*

* Retrieve pointers to the menu item's

* MEASUREITEMSTRUCT structure and MYITEM structure.

*/

lpmis = (LPMEASUREITEMSTRUCT) lParam;

pmyitem = (MYITEM *) lpmis->itemData;

/*

* Select the font associated with the item into

* the main window's device context.

*/

hfontOld = SelectObject(hdc, pmyitem->hfont);

/*

* Retrieve the width and height of the item's string,

* then copy the width and height into the

* MEASUREITEMSTRUCT structure's itemWidth and

* itemHeight members.

*/

GetTextExtentPoint(hdc, pmyitem->psz,

lstrlen(pmyitem->psz), &size);

lpmis->itemWidth = size.cx;

lpmis->itemHeight = size.cy;

/*

* Select the old font back into the device context,

* and then release the device context.

*/

SelectObject(hdc, hfontOld);

ReleaseDC(hwnd, hdc);

return TRUE;

break;

case WM_DRAWITEM:

/*

* Get pointers to the menu item's DRAWITEMSTRUCT

* structure and MYITEM structure.

*/

lpdis = (LPDRAWITEMSTRUCT) lParam;

pmyitem = (MYITEM *) lpdis->itemData;

/*

* If the user has selected the item, use the selected

* text and background colors to display the item.

*/

if (lpdis->itemState & ODS_SELECTED) {

crText = SetTextColor(lpdis->hDC, crSelText);

crBkgnd = SetBkColor(lpdis->hDC, crSelBkgnd);

fSelected = TRUE;

}

/*

* Remember to leave space in the menu item for the

* check mark bitmap; retrieve the width of the bitmap

* and add it to the width of the menu item.

*/

dwCheckXY = GetMenuCheckMarkDimensions();

wCheckX = LOWORD(dwCheckXY);

nTextX = wCheckX + lpdis->rcItem.left;

nTextY = lpdis->rcItem.top;

/*

* Select the font associated with the item into the

* item's device context, and then draw the string.

*/

hfontOld = SelectObject(lpdis->hDC, pmyitem->hfont);

ExtTextOut(lpdis->hDC, nTextX, nTextY, ETO_OPAQUE,

&lpdis->rcItem, pmyitem->psz,

lstrlen(pmyitem->psz), NULL);

/*

* Select the previous font back into the device

* context.

*/

SelectObject(lpdis->hDC, hfontOld);

/*

* Return the text and background colors to normal (not

* selected).

*/

if (fSelected) {

SetTextColor(lpdis->hDC, crText);

SetBkColor(lpdis->hDC, crBkgnd);

}

return TRUE;

.

. /* Process other messages. */

.

case WM_DESTROY:

/* Destroy the menu items' font handles. */

for (i = 0; i < CITEMS; i++)

DeleteObject(myitem[i].hfont);

PostQuitMessage(0);

break;

default:

return (DefWindowProc(hwnd, uMsg, wParam, lParam));

}

return (NULL);

}

HFONT GetAFont(fnFont)

int fnFont; /* font-attribute flag */

{

static LOGFONT lf; /* structure for font information */

/*

* Get a handle to the ANSI fixed-pitch font and copy

* information about the font to a LOGFONT structure.

*/

GetObject(GetStockObject(ANSI_FIXED_FONT), sizeof(LOGFONT),

&lf);

/* Set the font attributes as appropriate. */

if (fnFont == BOLD)

lf.lfWeight = FW_BOLD;

else

lf.lfWeight = FW_NORMAL;

lf.lfItalic = (fnFont == ITALIC);

lf.lfItalic = (fnFont == ULINE);

/* Create the font and return its handle. */

return (CreateFont(lf.lfHeight, lf.lfWidth,

lf.lfEscapement, lf.lfOrientation, lf.lfWeight,

lf.lfItalic, lf.lfUnderline, lf.lfStrikeOut, lf.lfCharSet,

lf.lfOutPrecision, lf.lfClipPrecision, lf.lfQuality,

lf.lfPitchAndFamily, lf.lfFaceName));

}