65.9K
CodeProject 正在变化。 阅读更多。
Home

又一个功能齐全的属主绘制菜单

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2020年1月14日

CPOL

6分钟阅读

viewsIcon

21724

downloadIcon

661

另一个功能齐全的属主绘制菜单,只需最小的努力——这次基于Win32,使用图标而非位图,带有快捷键,并已在ReactOS和WinNT 4.0至Windows 10上进行了测试

引言

是的,我知道——在Code Project上,关于属主绘制菜单带有位图/图标的菜单的主题已经讨论过很多次了。尽管如此,我仍然没有找到符合我要求的东西

  • C/C++ 和 Win32(不使用 MFCWTLwxWidgets——因为它们太重了)
  • 没有钩子或脏操作

以下是Code Project上关于属主绘制菜单的搜索结果精选

......等等,等等。而且我已经省略了我列表中的所有 VB、C# 或 .NET 内容!要深入了解属主绘制菜单,我建议阅读标记为 *** 的文章。

更新 1

在此期间,我在 Code Project 上发现了出色的 Win32++ 类库。它比 MFCwxWidgets 提供了更薄的 Win32 API 层。它可以在旧版 Windows、ReactOS 上运行,并为 Code::BlocksDev-C++Visual Studio 2003 至 2019 提供了大量的示例项目。请参阅此提示 Win32++ 在 ReactOS 上使用 Code::Blocks 以编程方式创建的菜单,以了解 Win32++ 菜单的外观。

外观如何

我希望 OWNERDRAW 菜单能够尽可能无缝地集成,并且理想情况下与原生菜单无法区分。为了进行测试,我将 OWNERDRAW 菜单与 ReactOS 0.4.11 和不同 Windows 版本的原生菜单进行了比较。

Windows 版本 OWNERDRAW 菜单 原生菜单

Win2000
⇒ 版本 5.0

WinXP
⇒ 无主题
⇒ 版本 5.1
WinXP
⇒ Ahorn 主题
⇒ 版本 5.1
ReactOS 0.4.11
⇒ 无主题
⇒ 版本 5.1
ReactOS
⇒ 现代主题
⇒ 版本 5.1
Windows Vista
⇒ Aero 主题
⇒ 版本 6.0
Windows Vista
⇒ 无主题
⇒ 版本 6.0
Windows 7
⇒ Aero 主题
⇒ 版本 6.1
Windows 7
⇒ 对比黑色
⇒ 版本 6.1
Windows 8
⇒ Aero 主题
⇒ 版本 6.2
Windows 10
⇒ Aero 主题
⇒ 版本 6.2

正如您所见——有两个缺点

  1. 如果未使用 Aero 主题而是使用经典主题,则 OWNERDRAW 菜单项的高度对于 Windows Viata 及更高版本的 Windows 来说并不完全正确。我发现解决此问题的唯一方法是检查是否已加载有效的主题文件。
  2. OWNERDRAW 菜单项的流畅矩形在 Windows Viata、Windows 7 和 Windows 8/8.1 上,如果使用 Aero 主题,则不会绘制。通过检查版本号(如果您已经克服了获取正确版本号的困难)应该很容易修复这个问题。

Using the Code

变通方法

可以通过检查主题文件名(是否存在)和版本号来解决这些缺点,如下所示(由于确定主题文件名相当耗时,因此我将计算结果保存在 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);
}

Utils_GetOsVersion() 基于 ::RtlGetVersion(),因为 ::GetVersionEx() 被标记为已弃用,并且从 Windows 8.1 (版本 6.3) 开始,::VerifyVersionInfo() 返回应用程序的声明版本,而不是操作系统的运行时版本。它是这样工作的

#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

结果相当令人满意

Windows 版本 OWNERDRAW 菜单 原生菜单
Windows 7
⇒ Aero 主题
⇒ 版本 6.1
Windows 7
⇒ 对比黑色
⇒ 版本 6.1

实现

我所有的菜单都由主框架的 WindowProcedure() 处理

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

    window->OnInitMenuPopup((HMENU)wParam);
    break;
}
/* https://codeproject.org.cn/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://codeproject.org.cn/articles/16529/simple-menus-that-display-icons-minimalistic-appro */
case WM_DRAWITEM: // 43
{
    if (window == NULL)
        break;

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

计算菜单大小时的挑战在于正确处理快捷键。Win32 的数据结构不支持这一点,因此我引入了辅助变量 _hCurrentlyInitializingMenu_nCurrentlyInitializingMenuMaxCaptionWidth_nCurrentlyInitializingMenuMaxAcceleratorWidth

可以安全地假定 WM_INITMENUPOPUP 始终在 WM_MEASUREITEM 之前调用,而 WM_MEASUREITEM 始终在 WM_DRAWITEM 之前调用。在 ReactOS 上,WM_INITMENUPOPUP 在每次菜单启动时调用,而在 Windows 上仅在第一次菜单启动时调用。

_nCurrentlyInitializingMenuMaxCaptionWidth_nCurrentlyInitializingMenuMaxAcceleratorWidth 的计算结果必须保存起来供 WM_DRAWITEM 使用。MENUITEMINFO 结构就是为此设计的(连同图标的存储)。

为了提供一致的外观和感觉,菜单中的所有菜单项都必须是 OWNERDRAW——一旦菜单中的至少一个菜单项是 OWNERDRAW。OnInitMenuPopup() 确保了这一点。

/// <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;
    }
}

OnInitMenuPopup() 重置辅助变量 _hCurrentlyInitializingMenu_nCurrentlyInitializingMenuMaxCaptionWidth_nCurrentlyInitializingMenuMaxAcceleratorWidth 之后,OnMeasureItem()OnDrawItem() 可以假定这些辅助变量处于已定义的で状态。

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

在为每个菜单项调用 OnMeasureItem() 之后,OnDrawItem() 可以假定 _nCurrentlyInitializingMenuMaxCaptionWidth_nCurrentlyInitializingMenuMaxAcceleratorWidth 具有有意义的值。

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

结论

本文开头的下载包含完整的源代码,作为 ReactOS 的 Code::Blocks 项目和一个预编译的版本。源代码也可以在 Windows 上的 Visual Studio 中编译而无需任何更改。要跟进 Ogww 库的进展,我建议您查看我的 CodeProject 文章 在 ReactOS 上运行的基本图标编辑器

历史

  • 2020 年 1 月 14 日:初始版本
  • 2020 年 1 月 24 日:更新 1
© . All rights reserved.