Click here to Skip to main content
14,422,431 members

Yet another fully functional ownerdraw menu

Rate this:
4.85 (6 votes)
Please Sign up or sign in to vote.
4.85 (6 votes)
14 Jan 2020CPOL
Another fully functional ownerdraw menu with minimal effort - this time based on Win32, with icons instead of bitmaps, with accelerators and tested for ReactOS and WinNT 4.0 to Windows 10.

Image 1 Download Code::Blocks project (complete source and debug build of sample app).zip

Introduction

Yes, I know - in CODE PROJECT the topic ownerdraw menu has been covered several times already. Nevertheless I have still not found, what fits my requirements:

  • C/C++ and Win32 (no MFC or WTL)
  • no kooks or dirty tricks

Here is a selection of the hits for ownerdraw menu in CODE PROJECT:

... and so on, and so on. And I have already left out everything in my listing that is VB, C# or .NET! For a deeper dive into ownerdraw menus I'd recommend to read *** marked artikles.

How does it look

I wanted the ownerdraw menus to integrate as seamlessly as possible and ideally be indistinguishable from native menus. For testing purposes I have compared an ownerdraw menu and a native menu for ReactOS 0.4.11 and different Windows versions.

Windows version ownerdraw menu native menu

Win2000
⇒ Version 5.0

Image 2 Image 3
WinXP
⇒ No theme
⇒ Version 5.1
Image 4 Image 5
WinXP
⇒ Ahorn theme
⇒ Version 5.1
Image 6 Image 7
ReactOS 0.4.11
⇒ No theme
⇒ Version 5.1
Image 8 Image 9
ReactOS
⇒ Modern theme
⇒ Version 5.1
Image 10 Image 11
Windows Vista
⇒ Aero theme
⇒ Version 6.0
Image 12 Image 13
Windows Vista
⇒ No theme
⇒ Version 6.0
Image 14 Image 15
Windows 7
⇒ Aero theme
⇒ Version 6.1
Image 16 Image 17
Windows 7
⇒ Contrast black
⇒ Version 6.1
Image 18 Image 19
Windows 8
⇒ Aero theme
⇒ Version 6.2
Image 20 Image 21
Windows 10
⇒ Aero theme
⇒ Version 6.2
Image 22 Image 23

As you can see - there are two drawbacks:

  1. The ownerdraw menu item height is the not quite correct for Windows Viata and higher, if not the Aero theme but a classic theme is used. All I found to work around this problem is to check if a valid theme file has been loaded.
  2. The ownerdraw menu item fluent rectangle is not drawn for Windows Viata, 7 and 8/8.1, if the Aero theme is used. It should be easy to fix this by checking the version number (if you have overcome the difficulty to get the correct version number).

Using the code

Work arounds

These drawbacks can be solved by checking the theme file name (whethwe there is any) and the version number like this (since the determination of theme file name is quite time-consuming, I save the calculation result on bThemedDimensions):

if (Utils_GetOsVersion() >= 6.0f && OgwwThemes::Init() && OgwwThemes::Themed())
{
    LPWSTR wszThemeFileName = (LPWSTR)::GlobalAlloc(GPTR, MAX_PATH);
    OgwwThemes::GetCurrentThemeName(wszThemeFileName, MAX_PATH - 1, NULL, 0, NULL, 0);
    if (wcslen(wszThemeFileName) > 0)
        bThemedDimensions = true;
    ::GlobalFree((HGLOBAL)wszThemeFileName);
}

...

if (bThemedDimensions)
{
    nVistaOffsetX = 6; // Icon size grows (13px to 15px) and icon spacing grows.
    nVistaOffsetY = 3; // Icon spacing grow.
}

...

if (bThemedDimensions && Utils_GetOsVersion() < 10)
{

    COLORREF crCurrPen = 0;
    crCurrPen = ::GetSysColor(COLOR_3DLIGHT);

    int     nEdgeWidth = ::GetSystemMetrics(SM_CYEDGE);
    int     nImageOffsetX = ::GetSystemMetrics(SM_CXMENUCHECK) + nEdgeWidth + nEdgeWidth;
    HPEN    hNewPen = ::CreatePen(PS_SOLID, 1, crCurrPen);
    HGDIOBJ hOldPen = ::SelectObject(lpDIS->hDC, hNewPen);
    ::MoveToEx(lpDIS->hDC, lpDIS->rcItem.left + nImageOffsetX + 1 + nVistaOffsetX,
               lpDIS->rcItem.top, NULL);
    ::LineTo(lpDIS->hDC, lpDIS->rcItem.left + nImageOffsetX + 1 + nVistaOffsetX,
             lpDIS->rcItem.bottom);
    ::SelectObject(lpDIS->hDC, ::GetStockObject(WHITE_PEN));
    ::MoveToEx(lpDIS->hDC, lpDIS->rcItem.left + nImageOffsetX + 2 + nVistaOffsetX,
               lpDIS->rcItem.top, NULL);
    ::LineTo(lpDIS->hDC, lpDIS->rcItem.left + nImageOffsetX + 2 + nVistaOffsetX,
             lpDIS->rcItem.bottom);
    ::SelectObject(lpDIS->hDC, hOldPen);

}

The Utils_GetOsVersion() is based on ::RtlGetVersion() because ::GetVersionEx() is marked to be deprecated and ::VerifyVersionInfo() returns the manifested version of the application, rather than the runtime version of the OS, starting with Windows 8.1 (version 6.3). This is how it works:

#if defined(__GNUC__) || defined(__MINGW32__)
#else

typedef void (WINAPI * RtlGetVersion_FUNC) (OSVERSIONINFOEXW *);

/// <summary>
/// Gets the current OS version information.
/// </summary>
/// <param name="dwMajorVersion">The buffer for the version information to get.</param>
/// <returns>Returns <c>TRUE</c> on success, or <c>FALSE</c> otherwise.</returns>
BOOL RtlGetVersionEx(OSVERSIONINFOEX * os)
{
    HMODULE hMod;
    RtlGetVersion_FUNC func;
#ifdef UNICODE
    OSVERSIONINFOEXW * osw = os;
#else
    OSVERSIONINFOEXW o;
    OSVERSIONINFOEXW * osw = &o;
#endif

    hMod = LoadLibrary(TEXT("ntdll.dll"));
    if (hMod)
    {
        func = (RtlGetVersion_FUNC)GetProcAddress(hMod, "RtlGetVersion");
        if (func == 0)
        {
            FreeLibrary(hMod);
            return FALSE;
        }
        ZeroMemory(osw, sizeof(*osw));
        osw->dwOSVersionInfoSize = sizeof(*osw);
        func(osw);
#ifndef UNICODE
        os->dwBuildNumber = osw->dwBuildNumber;
        os->dwMajorVersion = osw->dwMajorVersion;
        os->dwMinorVersion = osw->dwMinorVersion;
        os->dwPlatformId = osw->dwPlatformId;
        os->dwOSVersionInfoSize = sizeof(*os);
        DWORD sz = sizeof(os->szCSDVersion);
        WCHAR * src = osw->szCSDVersion;
        unsigned char * dtc = (unsigned char *)os->szCSDVersion;
        while (*src)
            * Dtc++ = (unsigned char)* src++;
        *Dtc = '\ 0';
#endif
    }
    else
        return FALSE;
    FreeLibrary(hMod);
    return TRUE;
}
#endif

...

/// <summary>
/// Gets the OS version as float.
/// </summary>
/// <returns>Returns the OS version as float.</returns>
/// <remarks>
/// Windows 10             : 10.0
/// Windows 8.1            : 6.3
/// Windows 8.0            : 6.2
/// Windows Server 2012    : 6.15
/// Windows 7              : 6.1
/// Windows Server 2008 R2 : 6.05
/// Windows Vista          : 6.0
///</remarks>
__declspec(dllexport) float __cdecl Utils_GetOsVersion()
{
    if (Utils::fOsVersion != 0.0f)
        return Utils::fOsVersion;

    OSVERSIONINFOEX info;
    ::ZeroMemory(&info, sizeof(OSVERSIONINFOEX));
    info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

#if defined(__GNUC__) || defined(__MINGW32__)

    ::GetVersionEx((LPOSVERSIONINFOW)&info);

    Utils::fOsVersion = (info.dwMajorVersion * 1.0f) + (info.dwMinorVersion * 0.1f)
        - ((info.wProductType != VER_NT_WORKSTATION) ? 0.05f : 0.0f);

#else

    // Starting with Windows 8.1, '::GetVersionEx()' always returns 6.2 except the
    // application is manifested for a higher version. Deprecated with Windows 8.1.
#pragma warning(suppress : 4996)
    ::GetVersionEx((LPOSVERSIONINFOW)&info);

    Utils::fOsVersion = (info.dwMajorVersion * 1.0f) + (info.dwMinorVersion * 0.1f)
                      - ((info.wProductType != VER_NT_WORKSTATION) ? 0.05f : 0.0f);

    if (Utils::fOsVersion > 6.1f)
    {
        if (RtlGetVersionEx(&info) == TRUE)
        {
            Utils::fOsVersion = (info.dwMajorVersion * 1.0f) + (info.dwMinorVersion * 0.1f)
                              - ((info.wProductType != VER_NT_WORKSTATION) ? 0.05f : 0.0f);
        }
    }

#endif

The result is quite satisfactory:

Windows version ownerdraw menu native menu
Windows 7
⇒ Aero theme
⇒ Version 6.1
Image 24 Image 25
Windows 7
⇒ Contrast black
⇒ Version 6.1
Image 26 Image 27

The implementation

All my menus are handled by the main frames' WindowProcedure():

case WM_INITMENUPOPUP: // 279
{
    if (window == NULL)
        break;

    window->OnInitMenuPopup((HMENU)wParam);
    break;
}
/* https://www.codeproject.com/articles/16529/simple-menus-that-display-icons-minimalistic-appro */
case WM_MEASUREITEM: // 44
{
    if (window == NULL)
        break;

    // ReactOS calls it every time a menu item hast to be draws.
    // Windows10 calls it only, if menu item hasn't been measured.
    window->OnMeasureItem((LPMEASUREITEMSTRUCT)lParam);
    break;
}
/* https://www.codeproject.com/articles/16529/simple-menus-that-display-icons-minimalistic-appro */
case WM_DRAWITEM: // 43
{
    if (window == NULL)
        break;

    window->OnDrawItem((LPDRAWITEMSTRUCT)lParam);
    break;
}

The challenge when calculating the menu size is the correct handling of the accelerators. The data structures of Win32 do not support this and so I introduced the helper variables _hCurrentlyInitializingMenu_nCurrentlyInitializingMenuMaxCaptionWidth and _nCurrentlyInitializingMenuMaxAcceleratorWidth.

It can be safely assumed that WM_INITMENUPOPUP is always called before WM_MEASUREITEM and WM_MEASUREITEM always before WM_DRAWITEM. On ReactOS, WM_INITMENUPOPUP is called at every menu startup, on Windows only at the first menu startup.

The calculation result from _nCurrentlyInitializingMenuMaxCaptionWidth and _nCurrentlyInitializingMenuMaxAcceleratorWidth must then be saved for WM_DRAWITEM. The structure MENUITEMINFO is designed handle this (together with the storage of the icon).

To provide a consistent look & feel, all menu items of a menu must be ownerdraw - once at least one menu item of a menu is ownerdraw. OnInitMenuPopup() ensures this.

/// <summary>
/// Processes the WM_INITMENUPOPUP message.
/// </summary>
/// <param name="hMenu">The menu to initialize.</param>
void OgwwMainFrame::OnInitMenuPopup(HMENU hMenu)
{
    // We must ensure that NONE or ALL menu items are MF_OWNERDRAW.
    // Otherwise we can't ensure the shortcuts are displayed correctly.
    UINT  nSysDrawn = 0;
    UINT  nOwnerDrawn = 0;

    if (hMenu != NULL)
    {
        int nMaxItem = ::GetMenuItemCount(hMenu);

        // Check whether menu items must upgraded to MF_OWNERDRAW.
        for (int nItemIdx = 0; nItemIdx < nMaxItem; nItemIdx++)
        {
            MENUITEMINFO    mii;
            memset(&mii, 0, sizeof(MENUITEMINFO));
            mii.cbSize = sizeof(MENUITEMINFO);
            mii.fMask = MIIM_STATE | MIIM_TYPE;
            ::GetMenuItemInfo(hMenu, nItemIdx, TRUE, &mii);

            if ((mii.fType & MFT_SEPARATOR) != MFT_SEPARATOR)
            {
                if ((mii.fType & MF_OWNERDRAW) != MF_OWNERDRAW)
                    nSysDrawn++;
                else
                    nOwnerDrawn++;
            }
        }

        // Upgrade menu items to MF_OWNERDRAW.
        for (int nItemIdx = 0;
             nOwnerDrawn != 0 && nSysDrawn != 0 && nItemIdx < nMaxItem; nItemIdx++)
        {
            MENUITEMINFO    mii;
            memset(&mii, 0, sizeof(MENUITEMINFO));
            mii.cbSize = sizeof(MENUITEMINFO);
            mii.fMask = MIIM_STATE | MIIM_TYPE;
            ::GetMenuItemInfo(hMenu, nItemIdx, TRUE, &mii);

            if ((mii.fType & MFT_SEPARATOR) != MFT_SEPARATOR)
            {
                if ((mii.fType & MF_OWNERDRAW) != MF_OWNERDRAW)
                {
                    int nItemID = ::GetMenuItemID(hMenu, nItemIdx);
                    ::ModifyMenu(hMenu, nItemIdx, mii.fState | MF_BYPOSITION | MF_OWNERDRAW,
                                 nItemID, NULL);
                }
            }
        }
        _hCurrentlyInitializingMenu = hMenu;
        _nCurrentlyInitializingMenuMaxCaptionWidth = 0;
        _nCurrentlyInitializingMenuMaxAcceleratorWidth = 0;
    }
}

After OnInitMenuPopup() has reset the helper variables _hCurrentlyInitializingMenu_nCurrentlyInitializingMenuMaxCaptionWidth and _nCurrentlyInitializingMenuMaxAcceleratorWidth, OnMeasureItem() and OnDrawItem() can assume to be provided with a defined state of this helper variables.

/// <summary>
/// Processes the WM_MEASUREITEM message.
/// </summary>
/// <param name="lpMIS">The menu item measure structure.</param>
void OgwwMainFrame::OnMeasureItem(LPMEASUREITEMSTRUCT lpMIS)
{
    if (lpMIS->CtlType == ODT_MENU)
    {
        int    nEdgeWidth = ::GetSystemMetrics(SM_CYEDGE);
        lpMIS->itemWidth = ::GetSystemMetrics(SM_CXMENUCHECK) + nEdgeWidth + nEdgeWidth;
        lpMIS->itemHeight = 13 + nEdgeWidth + nEdgeWidth;

        LPMENUITEMDATA lpMID = (LPMENUITEMDATA)lpMIS->itemData;
        if (lpMID != NULL && lpMID->hMenu)
        {
            WCHAR wszBuffer[256];
            int   nCharCount = ::GetMenuString(lpMID->hMenu, lpMIS->itemID, wszBuffer,
                                               255, MF_BYCOMMAND);
            if (nCharCount > 0)
            {
                int nAcceleratorDelimiter;
                for (nAcceleratorDelimiter = 0;
                     nAcceleratorDelimiter < nCharCount; nAcceleratorDelimiter++)
                    if (wszBuffer[nAcceleratorDelimiter] == L'\t' ||
                        wszBuffer[nAcceleratorDelimiter] == L'\b')
                        break;

                RECT   rTextMetric = { 0, 0, 0, 0 };
                HDC    hDC = ::GetDC(HWnd());

                if (hDC != NULL)
                {
                    // Obviously, at least ReactOS does not guarantee the selection
                    // of the proper menu font.
                    if (_hMenuFontNormal == NULL)
                    {
                        NONCLIENTMETRICSW nm;
                        nm.cbSize = sizeof(NONCLIENTMETRICS);
                        VERIFY(::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS,
                                                       nm.cbSize, &nm, 0) != FALSE);
                        _hMenuFontNormal = ::CreateFontIndirect(&(nm.lfMenuFont));
                    }
                    VERIFY(_hMenuFontNormal != NULL);

                    HFONT hOldFont = NULL;
                    if (_hMenuFontNormal != NULL)
                        hOldFont = (HFONT)::SelectObject(hDC, _hMenuFontNormal);
                    // DEBUG-BEGIN
                    // TEXTMETRIC tm;
                    // ::GetTextMetrics(hDC, &tm);
                    // DEBUG-END

                    ::DrawTextW(hDC, wszBuffer, nAcceleratorDelimiter, &rTextMetric,
                                DT_CALCRECT);
                    _nCurrentlyInitializingMenuMaxCaptionWidth =
                        Math::Max(_nCurrentlyInitializingMenuMaxCaptionWidth,
                                  rTextMetric.right - rTextMetric.left);

                    if (nAcceleratorDelimiter < nCharCount - 1)
                    {
                        ::DrawTextW(hDC, &(wszBuffer[nAcceleratorDelimiter + 1]),
                                    nCharCount - nAcceleratorDelimiter - 1, &rTextMetric,
                                    DT_CALCRECT);
                        _nCurrentlyInitializingMenuMaxAcceleratorWidth =
                           Math::Max(_nCurrentlyInitializingMenuMaxAcceleratorWidth,
                           rTextMetric.right - rTextMetric.left);
                    }
                    if (hOldFont == NULL)
                        ::SelectObject(hDC, hOldFont);

                    ::ReleaseDC(HWnd(), hDC);

                    lpMIS->itemWidth = _nCurrentlyInitializingMenuMaxCaptionWidth +
                                       _nCurrentlyInitializingMenuMaxAcceleratorWidth +
                                       (_nCurrentlyInitializingMenuMaxAcceleratorWidth > 0 ?
                                        1 + MENU_FONT_AVERAGE_CHAR_WIDTH : 0) + 2;
                    lpMIS->itemWidth += ::GetSystemMetrics(SM_CXMENUCHECK) +
                                        nEdgeWidth + nEdgeWidth;
                }
            }
        }

        if (Utils_GetOsVersion() >= 6.0f && OgwwThemes::Init() && OgwwThemes::Themed())
        {
            LPWSTR wszThemeFileName = (LPWSTR)::GlobalAlloc(GPTR, MAX_PATH);
            OgwwThemes::GetCurrentThemeName(wszThemeFileName, MAX_PATH - 1, NULL, 0, NULL, 0);
            if (wcslen(wszThemeFileName) > 0)
            {
                lpMIS->itemWidth += 14; // Icon size grows (13px to 15px) and icon spacing grows.
                lpMIS->itemHeight += 5; // Icon spacing grows.
            }
            ::GlobalFree((HGLOBAL)wszThemeFileName);
        }
    }
}

After OnMeasureItem() has been called for every menu item, OnDrawItem() can assume that  _nCurrentlyInitializingMenuMaxCaptionWidth and _nCurrentlyInitializingMenuMaxAcceleratorWidth have meaningful values.

/// <summary>
/// Processes the WM_DRAWITEM message.
/// </summary>
/// <param name="lpDIS">The menu item draw structure.</param>
void OgwwMainFrame::OnDrawItem(LPDRAWITEMSTRUCT lpDIS)
{
    if (lpDIS->CtlType == ODT_MENU)
    {
        BOOL bDisabled = lpDIS->itemState & ODS_GRAYED;
        BOOL bSelected = lpDIS->itemState & ODS_SELECTED;
        BOOL bChecked = lpDIS->itemState & ODS_CHECKED;

        bool bThemedDimensions = false;
        bool bThemedBg = false;
        LONG nVistaOffsetX = 0;
        LONG nVistaOffsetY = 0;
        LPMENUITEMDATA lpMID = (LPMENUITEMDATA)lpDIS->itemData;

        if (Utils_GetOsVersion() >= 6.0f && OgwwThemes::Init() && OgwwThemes::Themed())
        {
            LPWSTR wszThemeFileName = (LPWSTR)::GlobalAlloc(GPTR, MAX_PATH);
            OgwwThemes::GetCurrentThemeName(wszThemeFileName, MAX_PATH - 1, NULL, 0, NULL, 0);
            if (wcslen(wszThemeFileName) > 0)
                bThemedDimensions = true;
            ::GlobalFree((HGLOBAL)wszThemeFileName);
        }
        if (bThemedDimensions)
        {
            nVistaOffsetX = 6; // Icon size grows (13px to 15px) and icon spacing grows.
            nVistaOffsetY = 3; // Icon spacing grows.
        }

        // Background.
        HBRUSH   hbrBG = ::CreateSolidBrush(::GetSysColor(COLOR_MENU));
        ::FillRect(lpDIS->hDC, &(lpDIS->rcItem), hbrBG);

        // Delimiter.
        if (bThemedDimensions && Utils_GetOsVersion() < 10)
        {
            COLORREF crCurrPen = 0;
            crCurrPen = ::GetSysColor(COLOR_3DLIGHT);

            int     nEdgeWidth = ::GetSystemMetrics(SM_CYEDGE);
            int     nImageOffsetX = ::GetSystemMetrics(SM_CXMENUCHECK) +
                                    nEdgeWidth + nEdgeWidth;
            HPEN    hNewPen = ::CreatePen(PS_SOLID, 1, crCurrPen);
            HGDIOBJ hOldPen = ::SelectObject(lpDIS->hDC, hNewPen);
            ::MoveToEx(lpDIS->hDC, lpDIS->rcItem.left + nImageOffsetX + 1 +
                       nVistaOffsetX, lpDIS->rcItem.top, NULL);
            ::LineTo(lpDIS->hDC, lpDIS->rcItem.left + nImageOffsetX + 1 + nVistaOffsetX,
                     lpDIS->rcItem.bottom);
            ::SelectObject(lpDIS->hDC, ::GetStockObject(WHITE_PEN));
            ::MoveToEx(lpDIS->hDC, lpDIS->rcItem.left + nImageOffsetX + 2 +
                       nVistaOffsetX, lpDIS->rcItem.top, NULL);
            ::LineTo(lpDIS->hDC, lpDIS->rcItem.left + nImageOffsetX + 2 +
                     nVistaOffsetX, lpDIS->rcItem.bottom);
            ::SelectObject(lpDIS->hDC, hOldPen);
        }

        // Highlight.
        if (bSelected)
        {
            bThemedBg = false;
            if (OgwwThemes::Themed() == true)
            {
                HTHEME hTheme = OgwwThemes::OpenThemeData((HWND)_hHandle, L"MENU");
                if (hTheme != NULL)
                {
                    OgwwThemes::DrawThemeBackground(hTheme, lpDIS->hDC,
                        OgwwThemes::PART__MENU_POPUPITEM,
                        OgwwThemes::STATE__MPI_HOT, &(lpDIS->rcItem), NULL);
                    OgwwThemes::CloseThemeData(hTheme);
                    bThemedBg = true;
                }
            }
            if (bThemedBg == false)
            {
                HBRUSH   hbrBG = ::CreateSolidBrush(::GetSysColor(COLOR_HIGHLIGHT));
                ::FillRect(lpDIS->hDC, &(lpDIS->rcItem), hbrBG);
            }
        }

        if (bThemedDimensions)
        {
            nVistaOffsetX += 8;
            nVistaOffsetX += 2;
        }

        // Caption.
        WCHAR wszBuffer[256];
        int   nCharCount = ::GetMenuString((HMENU)lpDIS->hwndItem, lpDIS->itemID, wszBuffer,
                                           255, MF_BYCOMMAND);
        if (nCharCount > 0)
        {
            COLORREF crPrevText = 0;
            COLORREF crCurrText = 0;
            bThemedBg = false;
            if (OgwwThemes::Themed() == true)
            {
                HTHEME hTheme = OgwwThemes::OpenThemeData((HWND)_hHandle, L"MENU");
                if (hTheme != NULL)
                {
                    crCurrText = OgwwThemes::GetThemeSysColor(hTheme,
                                     bDisabled ? COLOR_GRAYTEXT :
                                         bSelected ? COLOR_HIGHLIGHTTEXT : COLOR_MENUTEXT);
                    OgwwThemes::CloseThemeData(hTheme);
                    bThemedBg = true;
                }
                // Fix problem with Vista and above:
                if (Utils_GetOsVersion() >= 6.0f && bSelected)
                    crCurrText = ::GetSysColor(COLOR_MENUTEXT);
            }
            if (bThemedBg == false)
                crCurrText = ::GetSysColor(bDisabled ? COLOR_GRAYTEXT :
                                           bSelected ? COLOR_HIGHLIGHTTEXT : COLOR_MENUTEXT);
            crPrevText = ::SetTextColor(lpDIS->hDC, crCurrText);

            int nAcceleratorDelimiter;
            for (nAcceleratorDelimiter = 0;
                 nAcceleratorDelimiter < nCharCount; nAcceleratorDelimiter++)
                if (wszBuffer[nAcceleratorDelimiter] == L'\t' ||
                    wszBuffer[nAcceleratorDelimiter] == L'\b')
                    break;

            // Obviously, at least ReactOS does not guarantee the selection of
            // the proper menu font.
            if (_hMenuFontNormal == NULL)
            {
                NONCLIENTMETRICSW nm;
                nm.cbSize = sizeof(NONCLIENTMETRICS);
                VERIFY(::SystemParametersInfoW(SPI_GETNONCLIENTMETRICS,
                                               nm.cbSize, &nm, 0) != FALSE);
                _hMenuFontNormal = ::CreateFontIndirect(&(nm.lfMenuFont));
            }
            VERIFY(_hMenuFontNormal != NULL);

            HFONT hOldFont = NULL;
            if (_hMenuFontNormal != NULL)
                hOldFont = (HFONT)::SelectObject(lpDIS->hDC, _hMenuFontNormal);

            int nOldBkMode = ::SetBkMode(lpDIS->hDC, TRANSPARENT);

            int    nEdgeWidth = ::GetSystemMetrics(SM_CYEDGE);
            int    nImageOffsetX = ::GetSystemMetrics(SM_CXMENUCHECK) +
                                   nEdgeWidth + nEdgeWidth;
            lpDIS->rcItem.left += nImageOffsetX + nVistaOffsetX;
            lpDIS->rcItem.top += nVistaOffsetY;
            ::DrawTextW(lpDIS->hDC, wszBuffer, nAcceleratorDelimiter, &(lpDIS->rcItem), 0);

            LONG nMenuMaxCaptionWidth = _nCurrentlyInitializingMenuMaxCaptionWidth;
            if (lpMID != NULL)
            {
                if (lpMID->nMenuMaxCaptionWidth != 0)
                    nMenuMaxCaptionWidth = lpMID->nMenuMaxCaptionWidth;
                else
                    lpMID->nMenuMaxCaptionWidth = _nCurrentlyInitializingMenuMaxCaptionWidth;
            }

            if (nAcceleratorDelimiter < nCharCount - 1)
            {
                lpDIS->rcItem.left += nMenuMaxCaptionWidth + 1 + MENU_FONT_AVERAGE_CHAR_WIDTH;
                if (wszBuffer[nAcceleratorDelimiter] == L'\t')
                    ::DrawTextW(lpDIS->hDC, &(wszBuffer[nAcceleratorDelimiter + 1]),
                                nCharCount - nAcceleratorDelimiter - 1, &(lpDIS->rcItem),
                                DT_LEFT | DT_SINGLELINE);
                else
                    ::DrawTextW(lpDIS->hDC, &(wszBuffer[nAcceleratorDelimiter + 1]),
                                nCharCount - nAcceleratorDelimiter - 1, &(lpDIS->rcItem),
                                DT_RIGHT | DT_SINGLELINE);
                lpDIS->rcItem.left -= nMenuMaxCaptionWidth + 1 + MENU_FONT_AVERAGE_CHAR_WIDTH;
            }
            lpDIS->rcItem.left -= nImageOffsetX + nVistaOffsetX;
            lpDIS->rcItem.top -= nVistaOffsetY;

            ::SetBkMode(lpDIS->hDC, nOldBkMode);
            ::SetTextColor(lpDIS->hDC, crPrevText);

            if (hOldFont == NULL)
                ::SelectObject(lpDIS->hDC, hOldFont);
        }

        MENUITEMINFOW mii;
        ::ZeroMemory(&mii, sizeof(mii));
        mii.cbSize = sizeof(mii);
        mii.fMask = MIIM_CHECKMARKS | MIIM_DATA;

        if (bThemedDimensions)
        {
            nVistaOffsetX = 6; // Icon size grows (13px to 15px) and icon spacing grows.
            nVistaOffsetY = 3; // Icon spacing grows.
        }

        GetMenuItemInfo((HMENU)lpDIS->hwndItem, lpDIS->itemID, FALSE, &mii);
        if ((bChecked && mii.hbmpChecked != NULL) || (mii.hbmpUnchecked != NULL))
        {
            HDC     hdcMem = ::CreateCompatibleDC(lpDIS->hDC);
            HGDIOBJ oldBitmap = ::SelectObject(hdcMem, bChecked ? mii.hbmpChecked :
                                                                  mii.hbmpUnchecked);

            //::GetObject(mii.hbmpItem, sizeof(bitmap), &bitmap);
            ::BitBlt(lpDIS->hDC, lpDIS->rcItem.left + nVistaOffsetX,
                     lpDIS->rcItem.top + nVistaOffsetY, 13, 13, hdcMem, 0, 0, SRCCOPY);

            ::SelectObject(hdcMem, oldBitmap);
            ::DeleteDC(hdcMem);
        }

        if (lpMID != NULL && lpMID->hIcon)
        {
            ::DrawIconEx(lpDIS->hDC, lpDIS->rcItem.left + nVistaOffsetX + 1,
                         lpDIS->rcItem.top + nVistaOffsetY + 2,
                lpMID->hIcon, 13, 13, 0, NULL, DI_NORMAL);
        }
    }
}

Conclusion

The download at the beginning of this article contains the complete source code as the Code::Blocks project for ReactOS and a pre-built compilate. The source code can also be compiled with Visual Studio on Windows without any changes. To follow the progress of the Ogww library, I recommend a look at my CODE PROJECT article A basic icon editor running on ReactOS.

History

Initial version from 14. January 2020.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Steffen Ploetz
CEO Ploetz + Zeller GmbH
Germany Germany
No Biography provided

Comments and Discussions

 
QuestionTry Charles Petzold 'Programming Windows' book Pin
steveb15-Jan-20 9:33
Membersteveb15-Jan-20 9:33 
QuestionClean simple code! Pin
davercadman15-Jan-20 7:49
Memberdavercadman15-Jan-20 7:49 
QuestionErm, as I cannot see Pin
Pete Lomax Member 1066450514-Jan-20 10:17
professionalPete Lomax Member 1066450514-Jan-20 10:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Tip/Trick
Posted 14 Jan 2020

Stats

1.9K views
65 downloads
5 bookmarked