Export (0) Print
Expand All

Owner-Draw Menus

Introduction

Owner-draw menus are custom visual styles that developers can provide in their applications if they want visual styles other than the default styles provided by the system. In previous versions of the Windows® operating system, applications needed to use owner-draw menus to produce menu items that included an image beside the menu item text. This technique resulted in menus that did not conform with the visual style of the system-rendered menus that users are accustomed to using. Windows Vista® provides a way of implementing this common pattern without using owner-draw menus through the use of bitmaps with an alpha channel as the representation of images.

Applications that need more expressiveness in the rendering of menu items can still use owner-draw menus, but can now use the themes supported by the operating system to produce a rendering that is consistent with the design of the system.

This article discusses how to use the Visual Style API provided in Windows Vista to develop owner-draw menus.

Visual Style API Basics

The Visual Style API, provided in Windows Vista, eliminates a common reason to use owner-draw menus, but developers will still need to use owner-draw menus if visual style behaviors other than the default styles provided in Windows Vista are desired.

Note   Windows Vista provides alpha-blended bitmaps, which enable menu items to be shown without using owner-draw menu items. For information about how to avoid owner-draw menus and how to convert icons into bitmaps, see Visual Style Menus.

The Visual Style API enables the measurement, layout, and rendering of many of the visual elements, using the VSCLASS_MENU class. However, a significant amount of work is required to develop and maintain owner-draw menus. Now that it is possible to draw visually appealing menus in Windows Vista and that there is a new way to develop attractive icons using standard menus, the amount of owner-draw menu code that developers need to write is reduced dramatically, leaving more time for the innovation of core products.

The following table describes a few Visual Style API concepts.

Term

Definition

Theme handles (HTHEME) 

HTHEME and the desired visual (VSCLASS_MENU for our purposes) is used to call the OpenThemeData() function. The call returns a NULL handle if the current color scheme has styling disabled.

Important   Pay attention to the WM_SETTINGCHANGE message handler, which handles the message that is broadcasted when a system setting changes and regenerates the HTHEME. The theme handle needs to be reacquired to determine whether the user has changed the theme. For example, if the user switches to a visual scheme such as Windows Classic, which does not have themed elements, a NULL handle is returned when OpenThemeData() is called. If the call returns a NULL handle, consider rendering the visual using a non-Visual Style API.

Theme Parts and States

A visual element is made up of parts. Each part can have multiple states.  For example, a button can have a background part and different states that differentiate between pressed and non-pressed states.  The sample code provided in this article demonstrates how to translate owner-draw fState flags into menu parts and state IDs. Vssym32.h contains the part and state ID for every visual class.

Measurements

Use the GetThemePartSize() and GetThemeMargins() functions to determine the size and margins for a part/state pair respectively. Use the GetThemeTextExtent() function to measure the length of text strings.

Drawing

Use the DrawThemeBackground() and DrawThemeText() functions to render according to the current color scheme. For more information, see "Visual Styles Reference" in the MSDN library.

The sample code provided in this article presents a simplified version of menu rendering to explain the core drawing concepts. 

Sample Application

The following sample code creates a simple application that uses dialogs to demonstrate Windows Vista menu features. It draws an owner-draw menu using Windows XP-style and Windows Vista-style rendering.

The application, named CVistaMenuApp.cpp, demonstrates a simple custom behavior: a context menu that highlights the previously selected item. A drop-down menu enables the user to choose a standard menu, an owner-draw menu with standard rendering, an owner-draw menu using the Visual Style API, or the new Windows Vista menu parts.

NoteNote

The code example in this article does not use every part and state. There is no submenu rendering, it does not cover the menu bar, and it does not line up necessarily to the pixel with the menu rendering of the system.

CVistaMenuApp.cpp

//*******************************************************************
// Simple dialog-based application that demonstrates Windows Vista menu 
// features.
//
// Draw a sample owner-draw menu using Windows XP-style rendering and // Windows Vista-style rendering.
//*********************************************************************
#define STRICT_TYPED_ITEMIDS    // Use for better type safety if 
                                // you use IDList. 
                                
#include <windows.h>
#include <windowsx.h>           // For WM_COMMAND handling macros
#include <shlobj.h>             // For shell 
#include <shlwapi.h>            // QISearch, easier way to implement QI
#include <commctrl.h>
#include <strsafe.h>
#include <uxtheme.h>
#include <vssym32.h>
#include "resource.h"

#pragma comment(lib, "shlwapi") // Default link libs do not include this.
#pragma comment(lib, "comctl32")
#pragma comment(lib, "uxtheme")

HINSTANCE g_hinst = NULL;

// Set up common controls v6 the easy way.
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")

HRESULT MakeOwnerDraw(HMENU hmenu, BOOL fOwnerDraw);
void FreeOwnerDrawData(HMENU hmenu);
void ResetMenuMetrics(HMENU hmenu);

__inline int RectWidth(const RECT &r) { return r.right - r.left; }
__inline int RectHeight(const RECT &r) { return r.bottom - r.top; }

enum POPUPPARTS
{
    POPUP_CHECK,
    POPUP_TEXT,
    POPUP_SEPARATOR,
    POPUP_MAX
};

struct MENUITEMDATA
{
    HMENU   hmenu;
    int     id;
    SIZE    rgPopupSize[POPUP_MAX]; // Dimensions of each menu item 
                                    // component        
                                       
};

struct MENUITEM 
{
    MENUITEMINFO    mii;
    MENUITEMDATA*   pmid;
    WCHAR           szItemText[256];
};

    interface IOwnerDrawMenu
{
    virtual HRESULT Initialize(HWND hwndParent) = 0;
    virtual void MeasureItem(__in MENUITEM *pmi, __inout MEASUREITEMSTRUCT *pmis) = 0;
    virtual void DrawItem(__in MENUITEM *pmi, __in DRAWITEMSTRUCT *pdis) = 0;
    virtual void SelectedItem(int id) = 0;
    virtual HRESULT SettingChange() = 0;
    virtual void Release() = 0;
};

class CClassicOwnerDrawMenu : public IOwnerDrawMenu
{
public:
    CClassicOwnerDrawMenu() :
        _idLastSelected(-1)
    {
    }

    // IOwnerDrawMenu
    __override HRESULT Initialize(HWND hwndParent);
    __override void MeasureItem(__in MENUITEM *pmi, __inout MEASUREITEMSTRUCT *pmis);
    __override void DrawItem(__in MENUITEM *pmi, __in DRAWITEMSTRUCT *pdis);
    __override void SelectedItem(int id);
    __override HRESULT SettingChange();
    __override void Release();

private:
    void _DrawMenuItem(__in MENUITEM *pmi, __in DRAWITEMSTRUCT *pdis);
    void _InitMenuDC(__in MENUITEM *pmi, __in DRAWITEMSTRUCT *pdis);

    HWND _hwndParent;
    int _idLastSelected;

    // Menu metrics
    static const int _cxMenuMargin = 0;
    static const int _cyMenuMargin = 0;
    static const int _cxGutter = 5;
    static const int _cxGutterMargin = 2;
    static const int _cxGutterLine = 1;
    static const int _cxCheckmark = 16;
    static const int _cyCheckmark = 16;
    static const int _cySeparator = 1;
    static const DWORD _rgbPreviousSelection = RGB(251,245,55);
};

HRESULT CClassicOwnerDrawMenu::Initialize(HWND hwndParent)
{
    _hwndParent = hwndParent;
    return S_OK;
}

void CClassicOwnerDrawMenu::MeasureItem(__in MENUITEM *pmi, __inout MEASUREITEMSTRUCT *pmis)
{
    if (pmi->mii.fType & MFT_SEPARATOR)
    {
        pmis->itemWidth = 1; 
        pmis->itemHeight = _cyMenuMargin * 2 + _cySeparator;
    }
    else
    {
        HDC hdc = GetDC(_hwndParent);
        if (hdc)
        {
            SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT));
            SIZE size = {0};
            size_t cch = 0;
            StringCchLength(pmi->szItemText, ARRAYSIZE(
                            pmi->szItemText), &cch);
            GetTextExtentPoint32(hdc, pmi->szItemText,  
                                 static_cast<ULONG>(cch), &size);
            pmis->itemWidth = size.cx + 2 * _cxMenuMargin + 
                             _cxCheckmark + _cxGutter;
            pmis->itemHeight = max(size.cy, _cyCheckmark) + 
                               _cyMenuMargin * 2;

            ReleaseDC(_hwndParent, hdc);
        }
    }
}

void CClassicOwnerDrawMenu::DrawItem(__in MENUITEM *pmi, __in 
                                     DRAWITEMSTRUCT *pdis)
{
    _InitMenuDC(pmi, pdis);
    switch (pdis->itemAction)
    {
        case ODA_DRAWENTIRE:
        case ODA_SELECT:
            _DrawMenuItem(pmi, pdis);
            break;

        case ODA_FOCUS:
            break;
    }
}

void CClassicOwnerDrawMenu::SelectedItem(int id)
{
    _idLastSelected = id;
}

HRESULT CClassicOwnerDrawMenu::SettingChange()
{
    
    return S_OK;
}

void CClassicOwnerDrawMenu::Release()
{
    delete this;
}

void CClassicOwnerDrawMenu::_DrawMenuItem(__in MENUITEM *pmi, __in  
                                          DRAWITEMSTRUCT *pdis)
{
    if (pmi->mii.fType & MFT_SEPARATOR)
    {
        PatBlt(pdis->hDC, pdis->rcItem.left + _cxMenuMargin, 
            pdis->rcItem.top + _cyMenuMargin + 1,
            RectWidth(pdis->rcItem) - 2 * _cxMenuMargin,
            _cySeparator,
            BLACKNESS);
    }
    else
    {
        // Start rect out to be rcItem minus the margins.
        RECT rc = pdis->rcItem;
        InflateRect(&rc, -_cxMenuMargin, -_cyMenuMargin);
        ExtTextOut(pdis->hDC, rc.left, rc.top, ETO_OPAQUE, &rc, NULL, 
                   0, NULL);

        // Draw the checkbox if necessary.
        if (pmi->mii.fState & MFS_CHECKED)
        {
            RECT rcCheck;
            rcCheck.left = rc.left;
            rcCheck.right = rc.left + _cxCheckmark;
            rcCheck.top = rc.top + (RectHeight(rc) - _cyCheckmark + 1)
                          / 2;
            rcCheck.bottom = rc.top + _cyCheckmark;
            PatBlt(pdis->hDC, rcCheck.left, rcCheck.top,
                RectWidth(rcCheck), RectHeight(rcCheck),
                BLACKNESS);
        }
        rc.left += _cxCheckmark;

        // Draw the gutter divider.
        PatBlt(pdis->hDC, rc.left + _cxGutterMargin, rc.top, 
               _cxGutterLine, RectHeight(pdis->rcItem), BLACKNESS);
        rc.left += _cxGutter;

        // Draw the text.
        DrawText(pdis->hDC, pmi->mii.dwTypeData, pmi->mii.cch, &rc, 
        DT_VCENTER | DT_SINGLELINE);
    }
}

void CClassicOwnerDrawMenu::_InitMenuDC(__in MENUITEM *pmi, __in DRAWITEMSTRUCT *pdis)
{
    if (pmi->mii.fState & MFS_GRAYED)
    {
        SetTextColor(pdis->hDC, GetSysColor(COLOR_GRAYTEXT));
        SetBkColor(pdis->hDC, GetSysColor(COLOR_MENU));
    }
    else if (pdis->itemState & ODS_SELECTED)
    {
        SetTextColor(pdis->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
        SetBkColor(pdis->hDC, GetSysColor(COLOR_HIGHLIGHT));
    }
    else if (pmi->mii.wID == _idLastSelected)
    {
        SetTextColor(pdis->hDC, GetSysColor(COLOR_MENUTEXT));
        SetBkColor(pdis->hDC, _rgbPreviousSelection);
    }
    else
    {
        SetTextColor(pdis->hDC, GetSysColor(COLOR_MENUTEXT));
        SetBkColor(pdis->hDC, GetSysColor(COLOR_MENU));
    }
}

//
// ResetMenuMetrics: Menus cache their widths so you have to clear the 
// cache when switching between owner-draw methods. This can be 
// achieved by calling SetMenuItemInfo() with the MIIM_BITMAP flag set // in fMask.
//
void ResetMenuMetrics(HMENU hmenu)
{
    for (int i = GetMenuItemCount(hmenu); i > 0; --i)
    {
        MENUITEMINFO mii = { sizeof(mii) };
        mii.fMask = MIIM_BITMAP;
        if (GetMenuItemInfo(hmenu, i-1, TRUE, &mii))
        {
            SetMenuItemInfo(hmenu, i-1, TRUE, &mii);
        }
    }
}

struct DRAWITEMMETRICS
{
    RECT rcSelection;           // Selection rectangle
    RECT rcGutter;              // Gutter rectangle
    RECT rcCheckBackground;     // Check background rectangle
    RECT rgrc[POPUP_MAX];       // Menu subitem rectangles
};

class CMenuMetrics
{
public:
    HTHEME  hTheme;                     // Theme handle
    HWND    hwndTheme;                  // Window handle used to 
                                        // generate hTheme
    
    MARGINS marPopupCheck;              // Popup check margins
    MARGINS marPopupCheckBackground;    // Popup check background 
                                        // margins
    MARGINS marPopupItem;               // Popup item margins
    MARGINS marPopupText;               // Popup text margins
    MARGINS marPopupAccelerator;        // Popup accelerator margins

    SIZE    sizePopupCheck;             // Popup check size metric
    SIZE    sizePopupSeparator;         // Popup separator size metric
    int     iPopupBorderSize;           // Popup border space between  
                                        // item text and accelerator
    int     iPopupBgBorderSize;         // Popup border space between 
                                        // item text and gutter
    
    int     cyMarCheckBackground;       // Additional amount of 
                                        // vertical space to add to 
                                        // checkbox

public:
    // Initialization / termination
    ~CMenuMetrics();
    
    // Ensure ppMetrics is allocated and properly initialized.
    static HRESULT EnsureInitialized(__in HWND hwnd, __deref_inout  
                                     CMenuMetrics **ppMetrics);

    // Release any resources allocated by the object and setup object    
    // so that next EnsureInitialized() will initialize the object.
    void Flush();

    // Conversion functions
    POPUPITEMSTATES ToItemStateId(UINT uItemState);
    POPUPCHECKBACKGROUNDSTATES ToCheckBackgroundStateId(int iStateId);
    POPUPCHECKSTATES ToCheckStateId(UINT fType, int iStateId);

    void MeasureMenuItem(__inout MENUITEM *pmi, __inout 
                         MEASUREITEMSTRUCT *pmis);
    void LayoutMenuItem(__in MENUITEM *pmi, __in DRAWITEMSTRUCT *pdis, 
                        __out DRAWITEMMETRICS *pdim);

private:
 // Initialize the field members (on entry, hwndTheme is already set 
 // correctly).
    HRESULT Initialize();

    inline void ToMeasureSize(const SIZE *psizeDraw, const MARGINS 
                              *pmargins, __out LPSIZE psizeMeasure)
    {
        psizeMeasure->cx = psizeDraw->cx + pmargins->cxLeftWidth + 
                           pmargins->cxRightWidth;
        psizeMeasure->cy = psizeDraw->cy + pmargins->cyTopHeight + 
                           pmargins->cyBottomHeight;
    }

    inline void ToDrawRect(LPCRECT prcMeasure, const MARGINS *pmargins, 
                           __out LPRECT prcDraw)
    {
        
// Convert the measure rect to a drawing rect.
        SetRect(prcDraw,
                prcMeasure->left    + pmargins->cxLeftWidth,
                prcMeasure->top     + pmargins->cyTopHeight,
                prcMeasure->right   - pmargins->cxRightWidth,
                prcMeasure->bottom  - pmargins->cyBottomHeight);
    }
};

CMenuMetrics::~CMenuMetrics()
{
    Flush();
}

// Initialize the field members (on entry, hwnd is already set 
// correctly).
HRESULT CMenuMetrics::Initialize()
{
    HRESULT hr = E_FAIL;
    
    hTheme = OpenThemeData(hwndTheme, VSCLASS_MENU);
    if (hTheme)
    {
        GetThemePartSize(hTheme, NULL, MENU_POPUPCHECK, 0, NULL, 
                         TS_TRUE, &sizePopupCheck);
        GetThemePartSize(hTheme, NULL, MENU_POPUPSEPARATOR, 0, NULL, 
                         TS_TRUE, &sizePopupSeparator);

        GetThemeInt(hTheme, MENU_POPUPITEM, 0, TMT_BORDERSIZE, 
                    &iPopupBorderSize); 
        GetThemeInt(hTheme, MENU_POPUPBACKGROUND, 0, TMT_BORDERSIZE, 
                    &iPopupBgBorderSize); 

        GetThemeMargins(hTheme, NULL, MENU_POPUPCHECK, 0, 
                        TMT_CONTENTMARGINS, NULL, &marPopupCheck); 
        GetThemeMargins(hTheme, NULL, MENU_POPUPCHECKBACKGROUND, 0, 
                        TMT_CONTENTMARGINS, NULL, 
        &marPopupCheckBackground); 
        GetThemeMargins(hTheme, NULL, MENU_POPUPITEM, 0, 
                        TMT_CONTENTMARGINS, NULL, &marPopupItem); 

        marPopupAccelerator = marPopupItem;
        marPopupAccelerator.cxLeftWidth = 
        marPopupAccelerator.cxRightWidth = 0;

        // Popup text margins
        MARGINS margins = marPopupItem;
        margins.cxRightWidth = iPopupBorderSize;
        margins.cxLeftWidth = iPopupBgBorderSize;
        marPopupText = margins;
        
        cyMarCheckBackground = marPopupCheckBackground.cyTopHeight + 
                               marPopupCheckBackground.cyBottomHeight;

        hr = S_OK;
    }

    return hr;
}

// Ensure ppMetrics is allocated and properly initialized.
HRESULT CMenuMetrics::EnsureInitialized(__in HWND hwnd, __deref_inout 
                                        CMenuMetrics **ppMetrics)
{
    CMenuMetrics *pMetrics = *ppMetrics;
    HRESULT hr = E_NOINTERFACE;

    // Find and allocate a CMenuMetrics structure for this window if 
    // one has not been passed in.
    if (pMetrics == NULL)
    {
        pMetrics = new CMenuMetrics;
        if (pMetrics)
        {
            pMetrics->hTheme = NULL;
            pMetrics->hwndTheme = hwnd;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }

    // Initialize member fields.
    if (pMetrics)
    {
        if (pMetrics->hTheme == NULL)
        {
            hr = pMetrics->Initialize();
        }
        else
        {
            hr = S_OK;
        }
    }

    *ppMetrics = pMetrics;
    return hr;
}

// Release any resources allocated by the object and setup object so 
// that next EnsureInitialized() will initialize the object.
void CMenuMetrics::Flush()
{
    if (hTheme)
    {
        CloseThemeData(hTheme);
        hTheme = NULL;
    }
}

POPUPITEMSTATES CMenuMetrics::ToItemStateId(UINT uItemState)
{
    const bool      fDisabled   = ((uItemState & (ODS_INACTIVE | 
                                   ODS_DISABLED)) != 0);
    const bool      fHot        = ((uItemState & (ODS_HOTLIGHT | 
                                   ODS_SELECTED)) != 0);
    POPUPITEMSTATES iState;

    if (fDisabled)
    {
        iState = (fHot ? MPI_DISABLEDHOT : MPI_DISABLED);
    }
    else if (fHot)
    {
        iState = MPI_HOT;
    }
    else
    {
        iState= MPI_NORMAL;
    }
    
    return iState;
}

POPUPCHECKBACKGROUNDSTATES CMenuMetrics::ToCheckBackgroundStateId(int iStateId)
{
    POPUPCHECKBACKGROUNDSTATES iStateIdCheckBackground;

    // Determine the check background state.
    if (iStateId == MPI_DISABLED || iStateId == MPI_DISABLEDHOT)
    {
        iStateIdCheckBackground = MCB_DISABLED;
    }
    else
    {
        iStateIdCheckBackground = MCB_NORMAL;
    }

    return iStateIdCheckBackground;
}

POPUPCHECKSTATES CMenuMetrics::ToCheckStateId(UINT fType, int iStateId)
{
    POPUPCHECKSTATES iStateIdCheck;
        
    if (fType & MFT_RADIOCHECK)
    {
        if (iStateId == MPI_DISABLED || iStateId == MPI_DISABLEDHOT)
        {
            iStateIdCheck = MC_BULLETDISABLED;
        }
        else
        {
            iStateIdCheck = MC_BULLETNORMAL;
        }
    }
    else
    {
        if (iStateId == MPI_DISABLED || iStateId == MPI_DISABLEDHOT)
        {
            iStateIdCheck = MC_CHECKMARKDISABLED;
        }
        else
        {
            iStateIdCheck = MC_CHECKMARKNORMAL;
        }
    }

    return iStateIdCheck;
}        

void CMenuMetrics::MeasureMenuItem(__inout MENUITEM *pmi, __inout 
                                   MEASUREITEMSTRUCT *pmis)
{
    int cxTotal = 0;
    int cyMax = 0;
    SIZE sizeDraw;

    ZeroMemory(pmi->pmid->rgPopupSize, sizeof(pmi->pmid->rgPopupSize));

    MENUITEMINFO *pmii = &pmi->mii;

    // Size the check rectangle.
    sizeDraw = sizePopupCheck;
    sizeDraw.cy += marPopupCheckBackground.cyTopHeight + 
                   marPopupCheckBackground.cyBottomHeight;
    
    ToMeasureSize(&sizeDraw, &marPopupCheck, &pmi->pmid
                  ->rgPopupSize[POPUP_CHECK]);
    cxTotal += pmi->pmid->rgPopupSize[POPUP_CHECK].cx;

    if (pmii->fType & MFT_SEPARATOR)
    {
        // Size the separator, using the minimum width.
        sizeDraw = sizePopupCheck;
        sizeDraw.cy = sizePopupSeparator.cy;
        
        ToMeasureSize(&sizeDraw, &marPopupItem, &pmi->pmid
                      ->rgPopupSize[POPUP_SEPARATOR]);
    }
    else
    {
        // Add check background horizontal padding.
        cxTotal += marPopupCheckBackground.cxLeftWidth + 
                   marPopupCheckBackground.cxRightWidth;

        if (pmii->cch)
        {
            HDC hdc = GetDC(hwndTheme);
            if (hdc)
            {
                // Size the text subitem rectangle.
                RECT rcText = { 0 };
                GetThemeTextExtent(hTheme, 
                                   hdc, 
                                   MENU_POPUPITEM, 
                                   0, 
                                   pmi->szItemText,
                                   pmii->cch,
                                   DT_LEFT | DT_SINGLELINE,
                                   NULL, 
                                   &rcText);
                sizeDraw.cx = rcText.right;
                sizeDraw.cy = rcText.bottom;

                ToMeasureSize(&sizeDraw, &marPopupText, &pmi->pmid
                              ->rgPopupSize[POPUP_TEXT]);
                cxTotal += pmi->pmid->rgPopupSize[POPUP_TEXT].cx;

                ReleaseDC(hwndTheme, hdc);
            }                
        }

        // Account for selection margin padding.
        cxTotal += marPopupItem.cxLeftWidth + 
                   marPopupItem.cxRightWidth;             
    }
    
    // Calculate maximum menu item height.
    if (pmii->fType & MFT_SEPARATOR)
    {
        cyMax = pmi->pmid->rgPopupSize[POPUP_SEPARATOR].cy;
    }
    else
    {
        for (UINT i = 0; i < POPUP_MAX; i++)
        {
            cyMax = max(cyMax, pmi->pmid->rgPopupSize[i].cy);
        }
    }

    // Return the composite sizes.
    pmis->itemWidth = cxTotal;
    pmis->itemHeight = cyMax;
}

void CMenuMetrics::LayoutMenuItem(__in MENUITEM *pmi, __in DRAWITEMSTRUCT *pdis, __out DRAWITEMMETRICS *pdim)
{
    const RECT *prcItem                 = &pdis->rcItem;
    const int   cyItem                  = RectHeight(pdis->rcItem);
    const SIZE *prgsize                 = pmi->pmid->rgPopupSize;
    const bool  fIsSeparator            = (pmi->mii.fType & MFT_SEPARATOR) != 0;
    int x  = prcItem->left + marPopupItem.cxLeftWidth; // Left gutter margin
    const int y = prcItem->top;
    RECT rcMeasure;

    for (UINT i = 0; i < POPUP_MAX; i++)
    {
        if (prgsize[i].cx)
        {
            int cx;
            int cy;

            switch (i)
            {
            case POPUP_CHECK:
                // Add left padding for the check background rectangle.
                x += marPopupCheckBackground.cxLeftWidth;

                // Right-align the check/bitmap in the column.            
                cx =  prgsize[i].cx;
                cy =  prgsize[i].cy - cyMarCheckBackground;
                break;

            default:
                cx = prgsize[i].cx;
                cy = prgsize[i].cy;
                break;
            }

            // Position and vertically center the subitem.
            SetRect(&rcMeasure, x, y, x + cx, y + cy);
            if (i == POPUP_CHECK)
            {
                ToDrawRect(&rcMeasure, &marPopupCheck, &pdim->rgrc[i]);
            }
            else
            {
                ToDrawRect(&rcMeasure, &marPopupText, &pdim->rgrc[i]);
            }
            OffsetRect(&pdim->rgrc[i], 0, (cyItem - cy) / 2);

            // Add right padding for the check background rectangle.
            if (i == POPUP_CHECK)
            {
                x += marPopupCheckBackground.cxRightWidth;
            }

            // Move to the next subitem.
            x += cx;
        }
    }

    // Calculate the check background draw size.
    SIZE sizeDraw;
    sizeDraw.cx = prgsize[POPUP_CHECK].cx;
    sizeDraw.cy = prgsize[POPUP_CHECK].cy - cyMarCheckBackground;

    // Calculate the check background measure size.
    SIZE sizeMeasure;
    ToMeasureSize(&sizeDraw, &marPopupCheckBackground, &sizeMeasure);

    // Lay out the check background rectangle.
    x = prcItem->left + marPopupItem.cxLeftWidth;

    SetRect(&rcMeasure, x, y, x + sizeMeasure.cx, y + sizeMeasure.cy);
    ToDrawRect(&rcMeasure, &marPopupCheckBackground, 
               &pdim->rcCheckBackground);
    OffsetRect(&pdim->rcCheckBackground, 0, (cyItem - sizeMeasure.cy) /  
               2);

    // Lay out gutter rectangle.
    x = prcItem->left;

    sizeDraw.cx = prgsize[POPUP_CHECK].cx;
    ToMeasureSize(&sizeDraw, &marPopupCheckBackground, &sizeMeasure);

    SetRect(&pdim->rcGutter, x, y, x + marPopupItem.cxLeftWidth + 
            sizeMeasure.cx, y + cyItem);

    if (fIsSeparator)
    {
        // Lay out the separator rectangle.
        x = pdim->rcGutter.right + marPopupItem.cxLeftWidth;

        SetRect(&rcMeasure, x, y, prcItem->right - marPopupItem.cxRightWidth, y + prgsize[POPUP_SEPARATOR].cy);
        ToDrawRect(&rcMeasure, &marPopupItem, &pdim-
                   >rgrc[POPUP_SEPARATOR]);
        OffsetRect(&pdim->rgrc[POPUP_SEPARATOR], 0, (cyItem -  
                   prgsize[POPUP_SEPARATOR].cy) / 2);
    }
    else
    {
        // Lay out selection rectangle.
        x = prcItem->left + marPopupItem.cxLeftWidth;

        SetRect(&pdim->rcSelection, x, y, prcItem->right -  
        marPopupItem.cxRightWidth, y + cyItem);   
    }
}

class CVistaOwnerDrawMenu : public IOwnerDrawMenu
{
public:
    CVistaOwnerDrawMenu() :
        _idLastSelected(-1),
        _pMetrics(NULL)
    {
    }
    ~CVistaOwnerDrawMenu();

    // IOwnerDrawMenu
    __override HRESULT Initialize(HWND hwndParent);
    __override void MeasureItem(__in MENUITEM *pmi, __inout 
                                MEASUREITEMSTRUCT *pmis);
    __override void DrawItem(__in MENUITEM *pmi, __in DRAWITEMSTRUCT 
                             *pdis);
    __override void SelectedItem(int id);
    __override HRESULT SettingChange();
    __override void Release();

private:
    void _DrawMenuItem(__in MENUITEM *pmi, __in DRAWITEMSTRUCT *pdis);
    
    HWND _hwndParent;
    CMenuMetrics *_pMetrics;
    int _idLastSelected;

    static const DWORD _rgbPreviousSelection = RGB(251,245,55);
};

CVistaOwnerDrawMenu::~CVistaOwnerDrawMenu()
{
    delete _pMetrics;
}

HRESULT CVistaOwnerDrawMenu::Initialize(HWND hwndParent)
{
    _hwndParent = hwndParent;
    return CMenuMetrics::EnsureInitialized(_hwndParent, &_pMetrics);
}

void CVistaOwnerDrawMenu::MeasureItem(__in MENUITEM *pmi, __inout 
                                      MEASUREITEMSTRUCT *pmis)
{
    _pMetrics->MeasureMenuItem(pmi, pmis);
}

void CVistaOwnerDrawMenu::DrawItem(__in MENUITEM *pmi, __in 
                                   DRAWITEMSTRUCT *pdis)
{
    switch (pdis->itemAction)
    {
        case ODA_DRAWENTIRE:
        case ODA_SELECT:
            _DrawMenuItem(pmi, pdis);
            break;

        case ODA_FOCUS:
            break;
    }
}

void CVistaOwnerDrawMenu::SelectedItem(int id)
{
    _idLastSelected = id;
}

HRESULT CVistaOwnerDrawMenu::SettingChange()
{
    // Regenerate theme handle to synchronize with new settings
    _pMetrics->Flush();
    return CMenuMetrics::EnsureInitialized(_hwndParent, &_pMetrics);
}

void CVistaOwnerDrawMenu::Release()
{
    delete this;
}

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 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]);

    }
}

class CShellSimpleApp : public IUnknown
{
public:
    CShellSimpleApp() : 
      _cRef(1), 
      _hdlg(NULL), 
      _hrOleInit(E_FAIL),
      _hmenu(NULL),
      _hmenuContext(NULL),
      _pMenu(NULL)
    {
    }

    HRESULT DoModal(HWND hwnd)
    {
        INT_PTR iRet = DialogBoxParam(g_hinst, MAKEINTRESOURCE(IDD_DIALOG1), hwnd, s_DlgProc, (LPARAM)this);
        return (iRet == IDOK) ? S_OK : S_FALSE;
    }
    
    // IUnknown
    STDMETHODIMP QueryInterface(__in REFIID riid, __deref_out void **ppv)
    {
        static const QITAB qit[] = 
        {
            { 0 },
        };
        return QISearch(this, qit, riid, ppv);
    }

    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&_cRef);
    }

    STDMETHODIMP_(ULONG) Release()
    {
        LONG cRef = InterlockedDecrement(&_cRef);
        if (!cRef)
            delete this;
        return cRef;
    }

private:
    ~CShellSimpleApp()
    {
    }

    static BOOL CALLBACK s_DlgProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        #pragma warning(push)
        #pragma warning(disable:4312) // GetWindowLongPtr is not w4 clean.
        CShellSimpleApp *pssa = reinterpret_cast<CShellSimpleApp *>(GetWindowLongPtr(hdlg, DWLP_USER));
        #pragma warning(pop)
 
        if (uMsg == WM_INITDIALOG)
        {
            pssa = reinterpret_cast<CShellSimpleApp *>(lParam);
            pssa->_hdlg = hdlg;
            #pragma warning(push)
            #pragma warning(disable:4244) // SetWindowLongPtr is not w4 clean.
            SetWindowLongPtr(hdlg, DWLP_USER, reinterpret_cast<LONG_PTR>(pssa));
            #pragma warning(pop)
        }
        
        return pssa ? pssa->_DlgProc(uMsg, wParam, lParam) : FALSE;
    }

    BOOL _DlgProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
    void _OnInitDlg();
    void _OnDestroyDlg();
    void _InitMenuTypeComboBox();
    void _OnRightClick(WPARAM wParam, int x, int y);
    BOOL _OnMeasureItem(__inout MEASUREITEMSTRUCT *pmis);
    BOOL _OnDrawItem(__in DRAWITEMSTRUCT *pdis);
    void _OnSelectMenuItem(int idItem);
    void _SetMenuType(int iMenuType);

    static const int MENUTYPE_STANDARD = 0;
    static const int MENUTYPE_CLASSICOWNERDRAW = 1;
    static const int MENUTYPE_VISTAOWNERDRAW = 2;

    long _cRef;
    HRESULT _hrOleInit;
    HMENU _hmenu;
    HMENU _hmenuContext;
    HWND _hdlg;
    int _iMenuType;
    IOwnerDrawMenu *_pMenu;
};

void _SetDialogIcon(HWND hdlg, SHSTOCKICONID siid)
{
    SHSTOCKICONINFO sii = {sizeof(sii)};
    if (SUCCEEDED(SHGetStockIconInfo(siid, SHGFI_ICON | SHGFI_SMALLICON, &sii)))
    {
        SendMessage(hdlg, WM_SETICON, ICON_SMALL, (LPARAM) sii.hIcon);
    }
    if (SUCCEEDED(SHGetStockIconInfo(siid, SHGFI_ICON | SHGFI_LARGEICON, &sii)))
    {
        SendMessage(hdlg, WM_SETICON, ICON_BIG, (LPARAM) sii.hIcon);
    }
}

void _ClearDialogIcon(HWND hdlg)
{
    DestroyIcon((HICON)SendMessage(hdlg, WM_GETICON, ICON_SMALL, 0));
    DestroyIcon((HICON)SendMessage(hdlg, WM_GETICON, ICON_BIG, 0));
}

void CShellSimpleApp::_InitMenuTypeComboBox()
{
    HWND hwnd = GetDlgItem(_hdlg, IDC_MENUTYPE);
    SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)L"Standard Menu");
    SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)L"Classic Owner-draw Menu");
    SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)L"Vista Owner-draw Menu");
}

void CShellSimpleApp::_OnInitDlg()
{
    _SetDialogIcon(_hdlg, SIID_APPLICATION);

    _hrOleInit = OleInitialize(0);  // Needed for drag and drop

    _hmenu = LoadMenu(g_hinst, MAKEINTRESOURCE(IDM_CONTEXTMENU));
    if (_hmenu)
    {
        _hmenuContext = GetSubMenu(_hmenu, 0);
        _InitMenuTypeComboBox();
        SendDlgItemMessage(_hdlg, IDC_MENUTYPE, CB_SETCURSEL, MENUTYPE_VISTAOWNERDRAW, 0);
        _SetMenuType(MENUTYPE_VISTAOWNERDRAW);
    }
}

void CShellSimpleApp::_OnDestroyDlg()
{
    _ClearDialogIcon(_hdlg);

    if (SUCCEEDED(_hrOleInit))
    {
        OleUninitialize();
    }

    if (_hmenu)
    {
        FreeOwnerDrawData(_hmenuContext);
        DestroyMenu(_hmenu);
    }

    if (_pMenu)
    {
        _pMenu->Release();
    }
}

void CShellSimpleApp::_OnRightClick(WPARAM wParam, int x, int y)
{
    POINT ptClient = { x, y };
    ClientToScreen(_hdlg, &ptClient);

    int idSelected = TrackPopupMenuEx(_hmenuContext, TPM_RETURNCMD, ptClient.x, ptClient.y, _hdlg, NULL);
    _OnSelectMenuItem(idSelected);
}

void CShellSimpleApp::_SetMenuType(int iMenuType)
{
    HRESULT hr = S_OK;

    if (_pMenu)
    {
        _pMenu->Release();
        _pMenu = NULL;
    }

    _iMenuType = iMenuType;
    if (MENUTYPE_STANDARD == _iMenuType)
    {
        hr = MakeOwnerDraw(_hmenuContext, false);
    }
    else
    {
        hr = MakeOwnerDraw(_hmenuContext, true);
        if (FAILED(hr))
        {
            _iMenuType = MENUTYPE_STANDARD;
        }

        if (MENUTYPE_VISTAOWNERDRAW == _iMenuType)
        {
            _pMenu = static_cast<IOwnerDrawMenu *>(new  
             CVistaOwnerDrawMenu);
        }
        else
        {
            _pMenu = static_cast<IOwnerDrawMenu *>(new   
           CClassicOwnerDrawMenu);
        }

        if (_pMenu)
        {
            hr = _pMenu->Initialize(_hdlg);
            if (FAILED(hr))
            {
                // If you are not running in a theme that supports menu, 
                // styling falls back onto the classic version or standard version.
                _SetMenuType((MENUTYPE_CLASSICOWNERDRAW == _iMenuType) ? MENUTYPE_STANDARD : MENUTYPE_CLASSICOWNERDRAW);
            }
        }
    }

    // Clear out the menu metrics.
    if (SUCCEEDED(hr))
    {
        ResetMenuMetrics(_hmenuContext);
    }
}

BOOL CShellSimpleApp::_DlgProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    BOOL bRet = TRUE;   // Default for all handled cases in switch below

    switch (uMsg)
    {
    case WM_INITDIALOG:
        _OnInitDlg();
        break;

    case WM_COMMAND:
        switch (GET_WM_COMMAND_ID(wParam, lParam))
        {
        case IDOK:
        case IDCANCEL:
            return EndDialog(_hdlg, IDOK == GET_WM_COMMAND_ID(wParam,   
                             lParam));

        case IDC_MENUTYPE:
            if (CBN_SELENDOK == GET_WM_COMMAND_CMD(wParam, lParam))
            {
                int iCurSel = (int)SendDlgItemMessage(_hdlg,  
                IDC_MENUTYPE, CB_GETCURSEL, 0, 0);
                _SetMenuType(iCurSel);
            }
            break;
        }
        break;

    case WM_RBUTTONUP:
        _OnRightClick(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        break;

    case WM_MEASUREITEM:
        _OnMeasureItem(reinterpret_cast<MEASUREITEMSTRUCT *>(lParam));
        break;

    case WM_DRAWITEM:
        _OnDrawItem(reinterpret_cast<DRAWITEMSTRUCT *>(lParam));
        break;

    case WM_SETTINGCHANGE:
        if (FAILED(_pMenu->SettingChange()))
        {
            _pMenu->Release();
            _pMenu = static_cast<IOwnerDrawMenu *>(new CClassicOwnerDrawMenu);
        }
        break;

    case WM_DESTROY:
        _OnDestroyDlg();
        break;

    default:
        bRet = FALSE;
    }
    return bRet;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
    g_hinst = hInstance;

    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (SUCCEEDED(hr))
    {
        CShellSimpleApp *pdlg = new CShellSimpleApp();
        if (pdlg)
        {
            pdlg->DoModal(NULL);
            pdlg->Release();
        }

        CoUninitialize();
    }

    return 0;
}

// Owner-draw code

HRESULT MakeOwnerDraw(HMENU hmenu, BOOL fOwnerDraw)
{
    HRESULT hr = S_OK;

    for (int i = GetMenuItemCount(hmenu); SUCCEEDED(hr) && i > 0; --i)
    {
        MENUITEMINFO mii = {sizeof(mii) };
        mii.fMask = MIIM_FTYPE | MIIM_DATA;
        if (GetMenuItemInfo(hmenu, i-1, TRUE, &mii))
        {
            if (fOwnerDraw)
            {
                MENUITEMDATA *pmid = (MENUITEMDATA *)LocalAlloc(LPTR, sizeof(*pmid));
                if (pmid)
                {
                    pmid->hmenu = hmenu;
                    pmid->id = i-1;
     
                    mii.dwItemData = reinterpret_cast<ULONG_PTR>(pmid);
                    mii.fType |= MFT_OWNERDRAW;
                }
                else
                { 
                    mii.fType &= ~MFT_OWNERDRAW;
                    hr = E_OUTOFMEMORY;
                }
            }
            else
            {
                LocalFree(reinterpret_cast<HLOCAL>(mii.dwItemData));
                mii.fType &= ~MFT_OWNERDRAW;
            }

            SetMenuItemInfo(hmenu, i-1, TRUE, &mii);
        }
    }

    return hr;
}

void FreeOwnerDrawData(HMENU hmenu)
{
    for (int i = GetMenuItemCount(hmenu); i > 0; --i)
    {
        MENUITEMINFO mii = {sizeof(mii) };
        mii.fMask = MIIM_FTYPE | MIIM_DATA;
        if (GetMenuItemInfo(hmenu, i-1, TRUE, &mii))
        {
            LocalFree(reinterpret_cast<HLOCAL>(mii.dwItemData));
        }
    }
}

BOOL GetMenuItem(ULONG_PTR itemData, __out_bcount(sizeof(MENUITEM)) MENUITEM *pItem)
{
    BOOL fRet = FALSE;

    if (itemData)
    {
        pItem->pmid = reinterpret_cast<MENUITEMDATA *>(itemData);
        pItem->szItemText[0] = 0;
        pItem->mii.cbSize = sizeof(pItem->mii);
        pItem->mii.fMask = MIIM_CHECKMARKS | MIIM_BITMAP | MIIM_STRING | MIIM_SUBMENU | MIIM_STATE | MIIM_FTYPE | MIIM_ID;
        pItem->mii.dwTypeData = pItem->szItemText;
        pItem->mii.cch = ARRAYSIZE(pItem->szItemText);
        fRet = GetMenuItemInfo(pItem->pmid->hmenu, pItem->pmid->id, TRUE, &pItem->mii);
    }

    return fRet;
}

BOOL CShellSimpleApp::_OnMeasureItem(__inout MEASUREITEMSTRUCT *pmis)
{
    MENUITEM menuItem;
    if (GetMenuItem(pmis->itemData, &menuItem))
    {
        _pMenu->MeasureItem(&menuItem, pmis);
    }
    else
    {
        pmis->itemWidth = pmis->itemHeight = 0;
    }
    return TRUE;
}

BOOL CShellSimpleApp::_OnDrawItem(__in DRAWITEMSTRUCT *pdis)
{
    if (!IsRectEmpty(&pdis->rcItem))
    {
        int iSaveDC = SaveDC(pdis->hDC);

        MENUITEM menuItem;
        if (GetMenuItem(pdis->itemData, &menuItem))
        {
            _pMenu->DrawItem(&menuItem, pdis);
        }

        RestoreDC(pdis->hDC, iSaveDC);
    }

    return FALSE;
}

void CShellSimpleApp::_OnSelectMenuItem(int idItem)
{
    if (idItem > 0)
    {
        if (_pMenu)
        {
            _pMenu->SelectedItem(idItem);
        }
    }
}

Structure of the Test Application

The MENUITEMDATA Structure

The MENUITEMDATA structure holds the owner-draw data. The GetMenuItem() function returns a MENUITEM structure that contains all the information that can be retrieved.

struct MENUITEM

{

    MENUITEMINFO    mii;

    MENUITEMDATA    mid;

    WCHAR           szItemText[256];

};

The IOwnerDrawMenu Interface

The test application (CVistaMenuApp.cpp) provides a drop-down list box that enables users to switch between menu types: standard, owner-draw, and owner-draw using the Visual Style APIs. To make this switching easier, the application defines the IOwnerDrawMenu interface and two implementers of the interface (CClassicOwnerDrawMenu and CVistaOwnerDrawMenu).

interface IOwnerDrawMenu

{

    virtual HRESULT Initialize(HWND hwndParent) = 0;

    virtual void MeasureItem(__in MENUITEM *pmi, __inout

MEASUREITEMSTRUCT *pmis) = 0;

    virtual void DrawItem(__in MENUITEM *pmi, __in DRAWITEMSTRUCT

*pdis) = 0;

    virtual void SelectedItem(int id) = 0;

    virtual HRESULT SettingChange() = 0;

    virtual void Release() = 0;

};

The SelectedItem() Method

The SelectedItem() method is used by the class to determine which item should receive special highlighting. The test application calls this method with the previously selected menu item.

The CShellSimpleApp Class

TheCShellSimpleApp class is essentially a dialog box that implements the outer shell of the program. The WM_MEASUREITEM, WM_DRAWITEM, and WM_SETTINGCHANGE message handlers are the relevant portions of this class, along with the _SetMenuType() method, which enables users to switch between menu types.

Community Additions

Show:
© 2014 Microsoft