Modifying Owner-Draw Code
Modifying Owner-Draw Code
Applications that implement owner-draw menus need to use the Windows® theme APIs to achieve rendering that is consistent with the design of the menu items rendered by the operating system. Applications developers can modify the measurement and drawing code to suit their needs, but aspects of the menu rendering that do not need to be customized can be achieved by using the theme APIs provided by the operating system.
Rendering that is aware of themes uses a theme handle (HTHEME) that provides access to the metrics and functionality necessary to match the design of the system. The handle is opened in the context of the parent window, which maps to the Menu class. When themes are disabled, the handle becomes unavailable. In this case, the system and applications need to use the non-theme rendering mode for menus.
The sample code provided in this article demonstrates how to implement the non-theme rendering mode. The code focuses on the CVistaOwnerDrawMenu class and its helper class CMenuMetrics, which handle the initialization, measurements, and drawing of the non-themed menus. The article also discusses special considerations when developing applications that enable switching between theme-rendering mode and non-theme rendering mode for menus.
Initialization
The most important part of the initialization occurs in the CMenuMetrics::Initialize() function. An HTHEME is requested from the parent window, which maps to the Menu class. If the request succeeds, the metrics that enable proper measurement and layout of the menu items are retrieved.
The following code snippet (pared down for purposes of illustration) demonstrates the initialization process.
HRESULT CMenuMetrics::Initialize()
{
HRESULT hr = E_FAIL;
hTheme = OpenThemeData(hwndTheme, VSCLASS_MENU);
if (hTheme)
{
GetThemePartSize(hTheme, NULL, MENU_POPUPCHECK, 0, NULL,
TS_TRUE, &sizePopupCheck);
GetThemeInt(hTheme, MENU_POPUPITEM, 0, TMT_BORDERSIZE,
&iPopupBorderSize);
GetThemeMargins(hTheme, NULL, MENU_POPUPCHECK, 0,
TMT_CONTENTMARGINS, NULL, &marPopupCheck);
hr = S_OK;
}
return hr;
}
Measurement
Metrics information—such as the dimensions, spacing, and size of border and text dimensions associated with the part/state pair (see Owner-Draw Menus)—can be calculated using the Visual Style APIs in Windows Vista®. The following table lists several of these functions.
Function |
Metric information |
---|---|
GetThemePartSize() |
The dimensions of a part/state pair. |
GetThemeMargins() |
The spacing around a part/state pair. |
GetThemeInt(…, TMT_BORDERSIZE, …) |
The size of the border around a part/state pair. |
GetThemeTextExtent() |
The specified dimensions of the text using the correct font for the part/state pair. The parameters are similar to the DrawText API, including a parameter that specifies the text formatting. |
In the test application provided in Owner-Draw Menus, CMenuMetricscaches these metrics and provides helper functions, such as ToMeasureSize, which applies the specified margins to the tight bounding box calculated for the menu item.
Drawing
The drawing process includes manipulating the DRAWITEMSTRUCT structure, laying out the menu items according to the calculated metrics, and drawing the menu items.
First, convert the field of DRAWITEMSTRUCT into the correct state ID (POPUPITEMSTATES for pop-up menus). For example, ODS_HOTLIGHT is translated to MPI_HOT, and ODS_INACTIVE is translated to MPI_DISABLED. For detailed information, see the CMenuMetrics::ToItemStateId() function.
Next, lay out the items according to the calculated metrics in WM_MEASUREITEM and use the DrawThemeBackground() function to draw the menu in layers (parts), starting from the bottom layer to the top:
MENU_POPUPBACKGROUND (if the background contains transparency)
MENU_POPUPGUTTER (if a gutter is required)
MENU_POPUPSEPARATOR (if the item is a separator)
MENU_POPUPITEM
MENU_POPUPCHECKBACKGROUND (if rendering a check mark)
MENU_POPUPCHECK (if rendering a check mark)
Finally, draw the theme text using the DrawThemeText(…, MENU_POPUPITEM, …) function.
For the complete parameter list for DrawThemeText(), see the last statement in the CVistaOwnerDraw::_DrawMenuItem() function below, which demonstrates this process.
void CVistaOwnerDrawMenu::_DrawMenuItem(__in MENUITEM *pmi,
__in DRAWITEMSTRUCT *pdis)
{
POPUPITEMSTATES iStateId = _pMetrics->ToItemStateId(pdis-
>itemState);
DRAWITEMMETRICS dim;
_pMetrics->LayoutMenuItem(pmi, pdis, &dim);
if (IsThemeBackgroundPartiallyTransparent(_pMetrics->hTheme,
MENU_POPUPITEM, iStateId))
{
DrawThemeBackground(_pMetrics->hTheme, pdis->hDC,
MENU_POPUPBACKGROUND, 0, &pdis->rcItem,
NULL);
}
DrawThemeBackground(_pMetrics->hTheme,
pdis->hDC,
MENU_POPUPGUTTER,
0,
&dim.rcGutter,
NULL);
if (pmi->mii.fType & MFT_SEPARATOR)
{
DrawThemeBackground(_pMetrics->hTheme, pdis->hDC,
MENU_POPUPSEPARATOR, 0,
&dim.rgrc[POPUP_SEPARATOR], NULL);
}
else
{
// Draw the last selected item.
if (pmi->mii.wID == _idLastSelected)
{
RECT rc = dim.rcSelection;
SetBkColor(pdis->hDC, _rgbPreviousSelection);
ExtTextOut(pdis->hDC, rc.left, rc.top, ETO_OPAQUE, &rc,
NULL, 0, NULL);
}
// Item selection.
DrawThemeBackground(_pMetrics->hTheme, pdis->hDC,
MENU_POPUPITEM, iStateId, &dim.rcSelection,
NULL);
// Draw the checkbox if necessary.
if (pmi->mii.fState & MFS_CHECKED)
{
DrawThemeBackground(_pMetrics->hTheme,
pdis->hDC,
MENU_POPUPCHECKBACKGROUND,
_pMetrics->ToCheckBackgroundStateId(iStateId),
&dim.rcCheckBackground,
NULL);
DrawThemeBackground(_pMetrics->hTheme,
pdis->hDC,
MENU_POPUPCHECK,
_pMetrics->ToCheckStateId(pmi->mii.fType, iStateId),
&dim.rgrc[POPUP_CHECK],
NULL);
}
// Draw the text.
ULONG uAccel = ((pdis->itemState & ODS_NOACCEL) ? DT_HIDEPREFIX : 0);
DrawThemeText(_pMetrics->hTheme,
pdis->hDC,
MENU_POPUPITEM,
iStateId,
pmi->mii.dwTypeData,
pmi->mii.cch,
DT_SINGLELINE | DT_LEFT | uAccel,
0,
&dim.rgrc[POPUP_TEXT]);
}
}
Special Considerations
Problems can potentially arise when modifying owner-draw code. This section describes some of the problems and possible workarounds.
Allowing the test application to switch between owner-draw and non-owner-draw menus presents an interesting issue. The core operating system code that implements window handles, menus, and so on, does not issue new WM_MEASUREITEM messages when the MFT_OWNERDRAW bit is toggled and, thus, continues to use the old metrics. Fortunately, there is a simple workaround. Make sure the fMask property of the MENUITEMINFO structure has the MIIM_BITMAP set when the SetMenuItemInfo function is called. This causes new WM_MEASUREITEM messages to be sent.
The ResetMenuMetrics helper function clears out all the menu items of the specified HMENU. However, a more efficient way is to set this bit when toggling the MFT_OWNERDRAW bit.
The MakeOwnerDraw() helper function is used by the test application to change between owner-draw and non-owner-draw visual styles. For the complete test application, see Owner-Draw Menus.