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





5.00/5 (12投票s)
另一个功能齐全的属主绘制菜单,只需最小的努力——这次基于Win32,使用图标而非位图,带有快捷键,并已在ReactOS和WinNT 4.0至Windows 10上进行了测试
引言
是的,我知道——在Code Project上,关于属主绘制菜单或带有位图/图标的菜单的主题已经讨论过很多次了。尽管如此,我仍然没有找到符合我要求的东西
以下是Code Project上关于属主绘制菜单的搜索结果精选
- 遗憾的是 MFC:一个强大的属主绘制菜单,作者 Neil Yao
⇒ 优点:使用图标而非位图,背景高度可配置 - 遗憾的是 MFC:透明菜单,作者 Derick Cyril Thomas
⇒ 缺点:使用钩子 - 遗憾的是 MFC:位图菜单,作者 Flaviu
⇒ 优点:工作量很小
⇒ 缺点:使用位图而非图像 - 遗憾的是非常基础:为 OWNERDRAW 控件添加 XP 可视化样式支持,作者 David Y. Zhao
***
⇒ 优点:完整的 theme API 方法原型集
***
⇒ 缺点:不特定于菜单 - 遗憾的是 MFC:带图标、标题和阴影的属主绘制菜单,作者 Bruno Podetti
⇒ 优点:非常强大 - 遗憾的是非常基础:使用 HTHEME 包装器为 OWNERDRAW 控件添加 XP 可视化样式支持,作者 Pål K Tønder
***
⇒ 优点:完整的 theme API 方法原型集
***
⇒ 缺点:不特定于菜单 - 遗憾的是 MFC:CMenuXP Office XP 风格菜单,作者 Jean-Michel LE FOL
- 遗憾的是 WTL:CMenuXP - OfficeXP 菜单(WTL 版本),作者 Jean-Michel LE FOL
- 遗憾的是 MFC:CMenuEx - 属主绘制菜单,作者 C. Bügenburg
- 遗憾的是 MFC:CMenuEX - 位图菜单类,作者 NormDroid
- 遗憾的是 MFC:带图标、淡入淡出 2D-3D 水平/垂直的属主绘制菜单,作者 Dascalescu Romy
⇒ 优点:非常强大 - 遗憾的是非常基础:不使用 MFC 的原生 Win32 主题感知属主绘制控件,作者 Ewan Ward
- 遗憾的是 MFC:自定义绘制菜单的革命性新方法,作者 .dan.g.;通过 不使用钩子的菜单子类化 进行优化,作者 Kancleris
***
⇒ 优点:还涵盖了其他应用程序的属主绘制菜单,引入了隐藏的(未文档化的)消息0x01e5
- 遗憾的是 MFC:带位图的酷炫属主绘制菜单 - 版本 3.03,作者 Brent Corkum
- 遗憾的是 MFC:两行代码的属主绘制菜单,作者 Roger Allen
- 遗憾的是 MFC:显示图标的简单菜单 - 极简方法,作者 Alex Cohn
- 遗憾的是 MFC:如何分步创建属主绘制菜单,作者 Kevin Stumpf
***
⇒ 优点:处理流畅矩形 - 遗憾的不是属主绘制:从初学者角度审视菜单,作者 Roger Allen
***
⇒ 优点:非常详细的描述 - 遗憾的是 MFC:MFC 扩展库 - 用于处理属主绘制菜单的插件,作者 Roger Allen
......等等,等等。而且我已经省略了我列表中的所有 VB、C# 或 .NET 内容!要深入了解属主绘制菜单,我建议阅读标记为 ***
的文章。
更新 1
在此期间,我在 Code Project 上发现了出色的 Win32++ 类库。它比 MFC 或 wxWidgets 提供了更薄的 Win32 API 层。它可以在旧版 Windows、ReactOS 上运行,并为 Code::Blocks、Dev-C++ 和 Visual Studio 2003 至 2019 提供了大量的示例项目。请参阅此提示 Win32++ 在 ReactOS 上使用 Code::Blocks 以编程方式创建的菜单,以了解 Win32++ 菜单的外观。
外观如何
我希望 OWNERDRAW 菜单能够尽可能无缝地集成,并且理想情况下与原生菜单无法区分。为了进行测试,我将 OWNERDRAW 菜单与 ReactOS 0.4.11 和不同 Windows 版本的原生菜单进行了比较。
Windows 版本 | OWNERDRAW 菜单 | 原生菜单 |
---|---|---|
Win2000 | ![]() | ![]() |
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 | ![]() | ![]() |
正如您所见——有两个缺点
- 如果未使用 Aero 主题而是使用经典主题,则 OWNERDRAW 菜单项的高度对于 Windows Viata 及更高版本的 Windows 来说并不完全正确。我发现解决此问题的唯一方法是检查是否已加载有效的主题文件。
- 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