Visual Style Menus

Visual Style Menus

Introduction

Visual styles are specifications that determine the appearance of windows and controls. The appearance includes color, size, texture, and font. Visual styles enable configuration of the visual interface to make it consistent with the application interface and provide a way for Windows applications to apply visual styles.

Microsoft Windows® XP introduced visual styles as a mechanism for providing visual appeal to windows controls; however, menus were not rendered using visual styles. The common controls included in Windows XP are based on the Microsoft Win32® common controls and user controls.

New in Windows Vista

In Windows XP, owner-draw menus show icons, but Windows Vista® uses alpha-blended bitmaps instead.

Windows Vista provides menus that are part of the visual schema. These menus are rendered using visual styles, which can be added to existing applications. Adding code for new features to existing code must be done carefully to avoid breaking existing application behavior. Certain situations can cause visual styling to be disabled in an application. These situations include:

  • Customizing menus using owner-draw menu items (MFT_OWNERDRAW)

  • Using menu breaks (MFT_MENUBREAK or MFT_MENUBARBREAK)

  • Using HBMMENU_CALLBACK to defer bitmap rendering

  • Using a destroyed menu handle

These situations prevent visual style menus from being rendered. For more information, see Owner-Draw Menus.

Using Owner-Draw Menus

In previous versions of the Windows operating system, applications marked menu items as owner-draw to enable customization of style rendering. Owner-draw menus can be used in Windows Vista, but the menus will not be visually styled.

If you use code that uses owner-draw menu items in Windows Vista, you can do one of the following:

  • Do nothing — the rendering of the owner-draw menu item(s) will not change. However, if the code uses a mixture of standard menu items and owner-draw items, users will see a mix of the standard and owner-draw rendering methods.

  • Remove the owner-draw menu item — do this when the benefits of your custom rendering do not outweigh the benefit of system-rendered visually styled menus.

  • Convert the menu icons to bitmaps — the rendering of icons next to menu items is the most common reason to implement owner-draw menu items in the shell.

Using Alpha-Blended Menu Bitmaps

Windows Vista provides alpha-blended bitmaps, which enables menu items to be shown without using owner-draw menu items. Windows Vista also provides visual style APIs that can be used to render owner-draw menus. For detailed information about owner-draw menus, see Owner-Draw Menus.

A common reason for using owner-draw menus is to show icons. It seems logical to add an hIcon field to the MENUITEMINFO structure and then add the code that does the drawing using hIcon, but Windows Vista does not support hIcon. Windows Vista was designed to support alpha-blended menu bitmaps to prevent application compatibility issues.

Alpha-blended menu bitmaps allow icons to be displayed and provide more flexibility than simply specifying hIcon. Windows Vista is designed to apply alpha-blend to a menu bitmap that satisfies all the following requirements:

  • The bitmap is a 32bpp DIB section.

  • The DIB section has BI_RGB compression.

  • The bitmap contains pre-multiplied alpha pixels.

  • The bitmap is stored in hbmpChecked, hbmpUnchecked, or hbmpItem fields.

Note

MFT_BITMAP items do not support PARGB32 bitmaps.

Converting a hIcon to a PARGB32 bitmap depends on the graphics system you are comfortable with, but the easiest solution is to use the Windows Imaging Component (WIC).

Sample Code

The following C++ code example creates a PARGB32 bitmap from hIcon and displays the bitmap.

IWICImagingFactory *pFactory;
HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, 
             CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFactory));
    if (SUCCEEDED(hr))
    {
        IWICBitmap *pBitmap;
        hr = pFactory->CreateBitmapFromHICON(hicon, &pBitmap);
        if (SUCCEEDED(hr))
        {
            UINT cx, cy;
            hr = pBitmap->GetSize(&cx, &cy);
            if (SUCCEEDED(hr))
            {
                const SIZE sizIcon = { (int)cx, -(int)cy };
                BYTE *pbBuffer;
                hr = Create32BitHBITMAP(NULL, &sizIcon,   
                                        reinterpret_cast<void  
                                        **>(&pbBuffer), &hbmp);
                if (SUCCEEDED(hr))
                {
                    const UINT cbStride = cx * sizeof(ARGB);
                    const UINT cbBuffer = cy * cbStride;
                    hr = pBitmap->CopyPixels(NULL, cbStride, 
                                             cbBuffer, pbBuffer);
                }
            }
            pBitmap->Release();
        }
        pFactory->Release();
    }

The sizIcon height is negative because the CreateBitmapFromHICON() function creates a top-down bitmap; therefore, a top-down DIB section must be created to match it.

The Create32BitHBITMAP() function is a wrapper function for the CreateDIBSection() function that creates a 32bpp and a BI_RGB DIB section.

Sample Application

The following C++ code creates a simple application that demonstrates Windows Vista style menu features. The application uses the WIC to convert icons to bitmaps.

CVistaMenuApp.cpp

#define STRICT_TYPED_ITEMIDS    // In case you use IDList, you want this on for better type safety.
#include <windows.h>
#include <windowsx.h>           // For WM_COMMAND handling macros
#include <shlobj.h>             // For shell
#include <shlwapi.h>            // QISearch, easy way to implement QI
#include <commctrl.h>
#include <wincodec.h>           // WIC
#include "resource.h"

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

// 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='*'\"")

HINSTANCE g_hinst = 0;

// AddIconToMenuItem and its supporting functions.
// Note: BufferedPaintInit/BufferedPaintUnInit should be called to
// improve performance.
// In this sample they are called in _OnInitDlg/_OnDestroyDlg.
// In a full application you would call these during WM_NCCREATE/WM_NCDESTROY.

typedef DWORD ARGB;

void InitBitmapInfo(__out_bcount(cbInfo) BITMAPINFO *pbmi, ULONG cbInfo, LONG cx, LONG cy, WORD bpp)
{
    ZeroMemory(pbmi, cbInfo);
    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biPlanes = 1;
    pbmi->bmiHeader.biCompression = BI_RGB;

    pbmi->bmiHeader.biWidth = cx;
    pbmi->bmiHeader.biHeight = cy;
    pbmi->bmiHeader.biBitCount = bpp;
}

HRESULT Create32BitHBITMAP(HDC hdc, const SIZE *psize, __deref_opt_out void **ppvBits, __out HBITMAP* phBmp)
{
    *phBmp = NULL;

    BITMAPINFO bmi;
    InitBitmapInfo(&bmi, sizeof(bmi), psize->cx, psize->cy, 32);

    HDC hdcUsed = hdc ? hdc : GetDC(NULL);
    if (hdcUsed)
    {
        *phBmp = CreateDIBSection(hdcUsed, &bmi, DIB_RGB_COLORS, ppvBits, NULL, 0);
        if (hdc != hdcUsed)
        {
            ReleaseDC(NULL, hdcUsed);
        }
    }
    return (NULL == *phBmp) ? E_OUTOFMEMORY : S_OK;
}

HRESULT AddBitmapToMenuItem(HMENU hmenu, int iItem, BOOL fByPosition, HBITMAP hbmp)
{
    HRESULT hr = E_FAIL;

    MENUITEMINFO mii = { sizeof(mii) };
    mii.fMask = MIIM_BITMAP;
    mii.hbmpItem = hbmp;
    if (SetMenuItemInfo(hmenu, iItem, fByPosition, &mii))
    {
        hr = S_OK;
    }

    return hr;
}

HRESULT AddIconToMenuItem(__in IWICImagingFactory *pFactory,
                          HMENU hmenu, int iMenuItem, BOOL fByPosition, HICON hicon, BOOL fAutoDestroy, __out_opt HBITMAP *phbmp)
{
    HBITMAP hbmp = NULL;

    IWICBitmap *pBitmap;
    HRESULT hr = pFactory->CreateBitmapFromHICON(hicon, &pBitmap);
    if (SUCCEEDED(hr))
    {
        IWICFormatConverter *pConverter;
        hr = pFactory->CreateFormatConverter(&pConverter);
        if (SUCCEEDED(hr))
        {
            hr = pConverter->Initialize(pBitmap, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, NULL, 0.0f, WICBitmapPaletteTypeCustom);
            if (SUCCEEDED(hr))
            {
                UINT cx, cy;
                hr = pConverter->GetSize(&cx, &cy);
                if (SUCCEEDED(hr))
                {
                    const SIZE sizIcon = { (int)cx, -(int)cy };
                    BYTE *pbBuffer;
                    hr = Create32BitHBITMAP(NULL, &sizIcon, reinterpret_cast<void **>(&pbBuffer), &hbmp);
                    if (SUCCEEDED(hr))
                    {
                        const UINT cbStride = cx * sizeof(ARGB);
                        const UINT cbBuffer = cy * cbStride;
                        hr = pConverter->CopyPixels(NULL, cbStride, cbBuffer, pbBuffer);
                    }
                }
            }

            pConverter->Release();
        }

        pBitmap->Release();
    }

    if (SUCCEEDED(hr))
    {
        hr = AddBitmapToMenuItem(hmenu, iMenuItem, fByPosition, hbmp);
    }

    if (FAILED(hr))
    {
        DeleteObject(hbmp);
        hbmp = NULL;
    }

    if (fAutoDestroy)
    {
        DestroyIcon(hicon);
    }

    if (phbmp)
    {
        *phbmp = hbmp;
    }

    return hr;
}

//-------------------------------------------------------------------
//-- TrackPopupIconMenuEx -------------------------------------------
// Wrapper to TrackPopupMenuEx that shows how to use AddIconToMenuItem // and how to manage the lifetime of the created bitmaps
struct ICONMENUENTRY
{
    UINT        idMenuItem;
    HINSTANCE   hinst;
    LPCWSTR     pIconId;
};

BOOL TrackPopupIconMenuEx(__in IWICImagingFactory *pFactory,
                          HMENU hmenu, UINT fuFlags, int x, int y, HWND hwnd, __in_opt LPTPMPARAMS lptpm,
                          UINT nIcons, __in_ecount_opt(nIcons) const ICONMENUENTRY *pIcons)
{
    HRESULT hr = S_OK;
    BOOL fRet = FALSE;

    MENUINFO menuInfo = { sizeof(menuInfo) };
    menuInfo.fMask = MIM_STYLE;

    if (nIcons)
    {
        for (UINT n = 0; SUCCEEDED(hr) && n < nIcons; ++n)
        {
            HICON hicon;
            hr = LoadIconMetric(pIcons[n].hinst, pIcons[n].pIconId, LIM_SMALL, &hicon);
            if (SUCCEEDED(hr))
            {
                /* hr = */ AddIconToMenuItem(pFactory, hmenu, pIcons[n].idMenuItem, FALSE, hicon, TRUE, NULL);
            }
        }

        GetMenuInfo(hmenu, &menuInfo);

        MENUINFO menuInfoNew = menuInfo;
        menuInfoNew.dwStyle = (menuInfo.dwStyle & ~MNS_NOCHECK) | MNS_CHECKORBMP;
        SetMenuInfo(hmenu, &menuInfoNew);
    }

    if (SUCCEEDED(hr))
    {
        fRet = TrackPopupMenuEx(hmenu, fuFlags, x, y, hwnd, lptpm) ? S_OK : E_FAIL;
    }

    if (nIcons)
    {
        for (UINT n = 0; n < nIcons; ++n)
        {
            MENUITEMINFO mii = { sizeof(mii) };
            mii.fMask = MIIM_BITMAP;

            if (GetMenuItemInfo(hmenu, pIcons[n].idMenuItem, FALSE, &mii))
            {
                DeleteObject(mii.hbmpItem);
                mii.hbmpItem = NULL;
                SetMenuItemInfo(hmenu, pIcons[n].idMenuItem, FALSE, &mii);
            }
        }

        SetMenuInfo(hmenu, &menuInfo);
    }

    return fRet;
}

//---------------------------------------------------------------------

class CShellSimpleApp : public IUnknown
{
public:
    CShellSimpleApp() : _cRef(1), _hdlg(NULL), _hrOleInit(E_FAIL), _pWICFactory(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 _OnRightClick(WPARAM wParam, int x, int y);

    long _cRef;
    HWND _hdlg;
    HRESULT _hrOleInit;
    IWICImagingFactory *_pWICFactory;
};

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::_OnInitDlg()
{
    _SetDialogIcon(_hdlg, SIID_APPLICATION);

    CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_pWICFactory));

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

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

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

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

void CShellSimpleApp::_OnRightClick(WPARAM wParam, int x, int y)
{
    POINT ptClient = { x, y };
    ClientToScreen(_hdlg, &ptClient);
    
    HMENU hmenu = LoadMenu(g_hinst, MAKEINTRESOURCE(IDM_CONTEXTMENU));
    if (hmenu)
    {
        ICONMENUENTRY aIcons[] = {
            { IDM_INFORMATION, NULL, IDI_INFORMATION },
            { IDM_ELEVATE, NULL, IDI_SHIELD }
        };
        TrackPopupIconMenuEx(_pWICFactory, 
            GetSubMenu(hmenu, 0), 0, ptClient.x, ptClient.y, _hdlg, NULL, ARRAYSIZE(aIcons), aIcons);
        DestroyMenu(hmenu);
    }
}

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));
        }
        break;

    case WM_RBUTTONUP:
        _OnRightClick(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        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;
}

Sample Application: GDI Conversion

The following C++ code example creates a simple application that demonstrates Windows Vista menu features. This example uses the Graphics Device Interface (GDI) to convert icons to bitmaps.

GDI_CVistaMenuApp.cpp

//********************************************************************
#define STRICT_TYPED_ITEMIDS    // In case you use IDList, you want this on for better type safety.
#include <windows.h>
#include <windowsx.h>           // For WM_COMMAND handling macros
#include <shlobj.h>             // For shell
#include <shlwapi.h>            // QISearch, easy way to implement QI
#include <commctrl.h>
#include <uxtheme.h>
#include "resource.h"

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

// 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='*'\"")

HINSTANCE g_hinst = 0;

// AddIconToMenuItem and supporting functions.
// Note: BufferedPaintInit/BufferedPaintUnInit should be called to
// improve performance.
// In this sample they are called in _OnInitDlg/_OnDestroyDlg.
// In a full application you would call these during WM_NCCREATE/WM_NCDESTROY.

typedef DWORD ARGB;

void InitBitmapInfo(__out_bcount(cbInfo) BITMAPINFO *pbmi, ULONG cbInfo, LONG cx, LONG cy, WORD bpp)
{
    ZeroMemory(pbmi, cbInfo);
    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biPlanes = 1;
    pbmi->bmiHeader.biCompression = BI_RGB;

    pbmi->bmiHeader.biWidth = cx;
    pbmi->bmiHeader.biHeight = cy;
    pbmi->bmiHeader.biBitCount = bpp;
}

HRESULT Create32BitHBITMAP(HDC hdc, const SIZE *psize, __deref_opt_out void **ppvBits, __out HBITMAP* phBmp)
{
    *phBmp = NULL;

    BITMAPINFO bmi;
    InitBitmapInfo(&bmi, sizeof(bmi), psize->cx, psize->cy, 32);

    HDC hdcUsed = hdc ? hdc : GetDC(NULL);
    if (hdcUsed)
    {
        *phBmp = CreateDIBSection(hdcUsed, &bmi, DIB_RGB_COLORS, ppvBits, NULL, 0);
        if (hdc != hdcUsed)
        {
            ReleaseDC(NULL, hdcUsed);
        }
    }
    return (NULL == *phBmp) ? E_OUTOFMEMORY : S_OK;
}

HRESULT AddBitmapToMenuItem(HMENU hmenu, int iItem, BOOL fByPosition, HBITMAP hbmp)
{
    HRESULT hr = E_FAIL;

    MENUITEMINFO mii = { sizeof(mii) };
    mii.fMask = MIIM_BITMAP;
    mii.hbmpItem = hbmp;
    if (SetMenuItemInfo(hmenu, iItem, fByPosition, &mii))
    {
        hr = S_OK;
    }

    return hr;
}

HRESULT ConvertToPARGB32(HDC hdc, __inout ARGB *pargb, HBITMAP hbmp, SIZE& sizImage, int cxRow)
{
    BITMAPINFO bmi;
    InitBitmapInfo(&bmi, sizeof(bmi), sizImage.cx, sizImage.cy, 32);

    HRESULT hr = E_OUTOFMEMORY;
    HANDLE hHeap = GetProcessHeap();
    void *pvBits = HeapAlloc(hHeap, 0, bmi.bmiHeader.biWidth * 4 * bmi.bmiHeader.biHeight);
    if (pvBits)
    {
        hr = E_UNEXPECTED;
        if (GetDIBits(hdc, hbmp, 0, bmi.bmiHeader.biHeight, pvBits, &bmi, DIB_RGB_COLORS) == bmi.bmiHeader.biHeight)
        {
            ULONG cxDelta = cxRow - bmi.bmiHeader.biWidth;
            ARGB *pargbMask = static_cast<ARGB *>(pvBits);

            for (ULONG y = bmi.bmiHeader.biHeight; y; --y)
            {
                for (ULONG x = bmi.bmiHeader.biWidth; x; --x)
                {
                    if (*pargbMask++)
                    {
                        // transparent pixel
                        *pargb++ = 0;
                    }
                    else
                    {
                        // opaque pixel
                        *pargb++ |= 0xFF000000;
                    }
                }

                pargb += cxDelta;
            }

            hr = S_OK;
        }

        HeapFree(hHeap, 0, pvBits);
    }

    return hr;
}

bool HasAlpha(__in ARGB *pargb, SIZE& sizImage, int cxRow)
{
    ULONG cxDelta = cxRow - sizImage.cx;
    for (ULONG y = sizImage.cy; y; --y)
    {
        for (ULONG x = sizImage.cx; x; --x)
        {
            if (*pargb++ & 0xFF000000)
            {
                return true;
            }
        }

        pargb += cxDelta;
    }

    return false;
}

HRESULT ConvertBufferToPARGB32(HPAINTBUFFER hPaintBuffer, HDC hdc, HICON hicon, SIZE& sizIcon)
{
    RGBQUAD *prgbQuad;
    int cxRow;
    HRESULT hr = GetBufferedPaintBits(hPaintBuffer, &prgbQuad, &cxRow);
    if (SUCCEEDED(hr))
    {
        ARGB *pargb = reinterpret_cast<ARGB *>(prgbQuad);
        if (!HasAlpha(pargb, sizIcon, cxRow))
        {
            ICONINFO info;
            if (GetIconInfo(hicon, &info))
            {
                if (info.hbmMask)
                {
                    hr = ConvertToPARGB32(hdc, pargb, info.hbmMask, sizIcon, cxRow);
                }

                DeleteObject(info.hbmColor);
                DeleteObject(info.hbmMask);
            }
        }
    }

    return hr;
}

HRESULT AddIconToMenuItem(HMENU hmenu, int iMenuItem, BOOL fByPosition, HICON hicon, BOOL fAutoDestroy, __out_opt HBITMAP *phbmp)
{
    HRESULT hr = E_OUTOFMEMORY;
    HBITMAP hbmp = NULL;

    SIZE sizIcon;
    sizIcon.cx = GetSystemMetrics(SM_CXSMICON);
    sizIcon.cy = GetSystemMetrics(SM_CYSMICON);

    RECT rcIcon;
    SetRect(&rcIcon, 0, 0, sizIcon.cx, sizIcon.cy);

    HDC hdcDest = CreateCompatibleDC(NULL);
    if (hdcDest)
    {
        hr = Create32BitHBITMAP(hdcDest, &sizIcon, NULL, &hbmp);
        if (SUCCEEDED(hr))
        {
            hr = E_FAIL;

            HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcDest, hbmp);
            if (hbmpOld)
            {
                BLENDFUNCTION bfAlpha = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
                BP_PAINTPARAMS paintParams = {0};
                paintParams.cbSize = sizeof(paintParams);
                paintParams.dwFlags = BPPF_ERASE;
                paintParams.pBlendFunction = &bfAlpha;

                HDC hdcBuffer;
                HPAINTBUFFER hPaintBuffer = BeginBufferedPaint(hdcDest, &rcIcon, BPBF_DIB, &paintParams, &hdcBuffer);
                if (hPaintBuffer)
                {
                    if (DrawIconEx(hdcBuffer, 0, 0, hicon, sizIcon.cx, sizIcon.cy, 0, NULL, DI_NORMAL))
                    {
                        // If icon did not have an alpha channel, we need to convert buffer to PARGB.
                        hr = ConvertBufferToPARGB32(hPaintBuffer, hdcDest, hicon, sizIcon);
                    }

                    // This will write the buffer contents to the
// destination bitmap.
                    EndBufferedPaint(hPaintBuffer, TRUE);
                }

                SelectObject(hdcDest, hbmpOld);
            }
        }

        DeleteDC(hdcDest);
    }

    if (SUCCEEDED(hr))
    {
        hr = AddBitmapToMenuItem(hmenu, iMenuItem, fByPosition, hbmp);
    }

    if (FAILED(hr))
    {
        DeleteObject(hbmp);
        hbmp = NULL;
    }

    if (fAutoDestroy)
    {
        DestroyIcon(hicon);
    }

    if (phbmp)
    {
        *phbmp = hbmp;
    }

    return hr;
}

//-----------------------------------------------------------------

//-- TrackPopupIconMenuEx -----------------------------------------
// Wrapper to TrackPopupMenuEx that shows how to use AddIconToMenuItem and how to manage the lifetime of the created bitmaps

struct ICONMENUENTRY
{
    UINT        idMenuItem;
    HINSTANCE   hinst;
    LPCWSTR     pIconId;
};

BOOL TrackPopupIconMenuEx(HMENU hmenu, UINT fuFlags, int x, int y, HWND hwnd, __in_opt LPTPMPARAMS lptpm,
                          UINT nIcons, __in_ecount_opt(nIcons) const ICONMENUENTRY *pIcons)
{
    HRESULT hr = S_OK;
    BOOL fRet;

    MENUINFO menuInfo = { sizeof(menuInfo) };
    menuInfo.fMask = MIM_STYLE;

    if (nIcons)
    {
        for (UINT n = 0; SUCCEEDED(hr) && n < nIcons; ++n)
        {
            HICON hicon;
            hr = LoadIconMetric(pIcons[n].hinst, pIcons[n].pIconId, LIM_SMALL, &hicon);
            if (SUCCEEDED(hr))
            {
                hr = AddIconToMenuItem(hmenu, pIcons[n].idMenuItem, FALSE, hicon, TRUE, NULL);
            }
        }

        GetMenuInfo(hmenu, &menuInfo);

        MENUINFO menuInfoNew = menuInfo;
        menuInfoNew.dwStyle = (menuInfo.dwStyle & ~MNS_NOCHECK) | MNS_CHECKORBMP;
        SetMenuInfo(hmenu, &menuInfoNew);
    }

    if (SUCCEEDED(hr))
    {
        fRet = TrackPopupMenuEx(hmenu, fuFlags, x, y, hwnd, lptpm) ? S_OK : E_FAIL;
        hr = fRet ? S_OK : E_FAIL;
    }

    if (nIcons)
    {
        for (UINT n = 0; n < nIcons; ++n)
        {
            MENUITEMINFO mii = { sizeof(mii) };
            mii.fMask = MIIM_BITMAP;

            if (GetMenuItemInfo(hmenu, pIcons[n].idMenuItem, FALSE, &mii))
            {
                DeleteObject(mii.hbmpItem);
                mii.hbmpItem = NULL;
                SetMenuItemInfo(hmenu, pIcons[n].idMenuItem, FALSE, &mii);
            }
        }

        SetMenuInfo(hmenu, &menuInfo);
    }

    return SUCCEEDED(hr) ? fRet : FALSE;
}

//---------------------------------------------------------------------

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

    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 _OnRightClick(WPARAM wParam, int x, int y);

    long _cRef;
    HWND _hdlg;
    HRESULT _hrOleInit;
};

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::_OnInitDlg()
{
    _SetDialogIcon(_hdlg, SIID_APPLICATION);

    BufferedPaintInit();

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

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

    BufferedPaintUnInit();

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

void CShellSimpleApp::_OnRightClick(WPARAM wParam, int x, int y)
{
    POINT ptClient = { x, y };
    ClientToScreen(_hdlg, &ptClient);
    
    HMENU hmenu = LoadMenu(g_hinst, MAKEINTRESOURCE(IDM_CONTEXTMENU));
    if (hmenu)
    {
        ICONMENUENTRY aIcons[] = {
            { IDM_INFORMATION, NULL, IDI_INFORMATION },
            { IDM_ELEVATE, NULL, IDI_SHIELD }
        };
        TrackPopupIconMenuEx(GetSubMenu(hmenu, 0), 0, ptClient.x, ptClient.y, _hdlg, NULL, ARRAYSIZE(aIcons), aIcons);
        DestroyMenu(hmenu);
    }
}

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

    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));
        }
        break;

    case WM_RBUTTONUP:
        _OnRightClick(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        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;
}

The following code is the resource file.

Resource.h

//************************************
#define IDD_DIALOG1                     101
#define IDM_CONTEXTMENU                 102
#define IDM_INFORMATION                 103
#define IDM_ELEVATE                     104
#define IDC_STATIC                       -1

Examples of Windows Context Menus

The following illustration shows an example of a Windows Classic context menu.

Classic Windows Context Menu

Bb757020.Top10_F01_VisualStyle2_ClassicMenu(en-us,MSDN.10).gif

The following illustration shows an example of a Windows Vista theme context menu.

Windows Vista Theme Context Menu

Bb757020.Top10_F01_VisualStyle2_VistaMenu(en-us,MSDN.10).gif

The following function creates the context menus shown above.

void CShellSimpleApp::_OnRightClick(WPARAM wParam, int x, int y)

{

POINT ptClient = { x, y };

ClientToScreen(_hdlg, &ptClient);

HMENU hmenu = LoadMenu(g_hinst, MAKEINTRESOURCE(IDM_CONTEXTMENU));

if (hmenu)

{

ICONMENUENTRY aIcons[] = {

{ IDM_INFORMATION, NULL, IDI_INFORMATION },

{ IDM_ELEVATE, NULL, IDI_SHIELD }

};

TrackPopupIconMenuEx(_pWICFactory, GetSubMenu(hmenu, 0), 0,

ptClient.x, ptClient.y, _hdlg, NULL,

ARRAYSIZE(aIcons), aIcons);

DestroyMenu(hmenu);

}

}

Using Comctl32 Version 6 loads WindowsCodecs.dll and makes WIC a good solution, but using only GDI requires more effort. The following procedure outlines the required steps.

To use GDI only

  1. Create a 32bpp, BI_RGB DIB Section and select it into a DC.

  2. Use BeginPaintBuffer() to target the DC/Bitmap from Step 1. Use this function to take advantage of the bitmap caching (as opposed to calling CreateDIBSection) of the Paint Buffer API. Call BufferedPaintInit() at the start of the program to enable bitmap caching. The paint buffer is an ARGB32 surface.

  3. Use DrawIconEx() to draw or scale the icon into the paint buffer.

  4. Check the paint buffer to see if it contains alpha values other than 0 or 255. If it does not, then convert the buffer to pre-multiplied alpha pixels.

  5. Use EndPaintBuffer() to transfer the converted icon to the bitmap created in Step 1.

  6. Use SetMenuItemInfo() to add the bitmap to the menu item.

  7. Clean up and return the bitmap.

Important

Notice how the BufferedPaint API is initialized, particularly the_OnInitDialog and_OnDestroyDlg functions. Calling BufferedPaintInitialize() is not required for the code examples cited in this section to execute, but doing so will leverage the bitmap cache and makes the conversion of multiple bitmaps faster. TrackPopupIconMenuEx() uses the LoadIconMetric function to create high-quality hIcons.

Note

One of the sample icons is IDI_SHIELD, which adds a UAC (User Account Control) shield to a menu item.

Call SetMenuInfo() with MNS_CHECKORBMP (see TrackPopupIconMenuEx()) to make the menu look good in the Windows Classic color scheme.

Interesting Functions

The functions described in this section can be used to manipulate icons and bitmaps.

The AddIconToMenuItem() function

HRESULT AddIconToMenuItem(__in IWICImagingFactory *pFactory, HMENU hmenu,

int iMenuItem, BOOL fByPosition, HICON hicon,

BOOL fAutoDestroy, __out_opt HBITMAP *phbmp)

AddIconToMenuItem() converts a hIcon to a bitmap and adds it to the specified hbmpItem field of HMENU item. The caller is responsible for destroying the bitmap generated. The icon will be destroyed iffAutoDestroy is set to true. The hbmpItem field of the menu item can be used to keep track of the bitmap by passing NULL to phbmp.

The following is a list and description of the parameters of the AddIconToMenuItem function.

  • __in IWICImagingFactory *pFactory
    Factory previously created by CoCreateInstance of CLSID_WICImagingFactory

  • HMENU hmenu
    Menu handle that contains the item to which an icon will be added

  • int iMenuItem
    ID or position of the menu item that receives the icon

  • BOOL fByPosition
    TRUE if iMenuItem specifies the position of the menu item; FALSE if iMenuItem specifies the ID of the item

  • HICON hicon
    Icon to add to the menu

  • BOOL fAutoDestroy
    TRUE if AddIconToMenuItem destroys the icon before returning

  • __out_opt HBITMAP *phbmp
    Location where the bitmap representation of the icon is stored

The TrackPopupIconMenuEx() function

BOOL TrackPopupIconMenuEx(HMENU hmenu, UINT fuFlags, int x, int y,

HWNDhwnd, __in_opt LPTPMPARAMS lptpm,

UINT cchIcons, __in_ecount_opt(cchIcons) const

ICONMENUENTRY *pIcons)

The TrackPopupIconMenuEx() function adds a list of icon resources to the menu, calls TrackPopupMenuEx(), and deletes the bitmaps. The parameters of this function are the same as those of the TrackPopupMenuEx function except that TrackPopupIconMenuEx() takes an array of ICONMENUENTRY structures. For detailed information about TrackPopupMenuEx(), see "TrackPopupMenuEx Function" in the MSDN Library.

The following is a list and description of the parameters of the TrackPopupIconMenuEx() function.

  • HMENU hmenu
    Menu handle that contains the item to which an icon will be added.

  • UINT fuFlags
    Flag used to specify how the function positions the shortcut menu horizontally. For more information about the Flags, see "TrackPopupMenuEx Function" in the MSDN Library.

  • int x
    Horizontal location of the shortcut menu, in screen coordinates.

  • int y
    Vertical location of the shortcut menu, in screen coordinates.

  • HWNDhwnd
    Handle to the window that owns the shortcut menu. This window receives all messages from the menu.

  • __in_opt LPTPMPARAMS lptpm
    Array of ICONMENUENTRY structures

  • UINT cchIcons
    Number of icons in pIcons table.

  • ICONMENUENTRY *pIcons
    Table of menu IDs and icons to insert using AddIconToMenuItem.

See Also

Concepts

Owner-Draw Menus

Modifying Owner-Draw Code