简单的菜单|:简约的方法 - 显示图标{A}
{S0}简介
在CodeProject上的精彩文章,菜单上的图片处理要求的深刻理解,大量的自定义代码,并呈现完全依赖新类的应用程序。我们尝试了所有的用户输入的是与自然的工具,即嵌入到微软Visual Studio的资源编辑器生成一个简单的复制粘贴结构。
代码已建立与Visual Studio 2005和Visual C 6,并在Windows XP和Windows 2000测试。你需要建立你的MFC应用程序
首先,确保您的应用程序正常工作。然后,添加以下三个功能到你的CMainFrame类(或对话框,如果您的应用程序是基于对话框的): afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis);
afx_msg void OnInitMenuPopup(CMenu* pMenu, UINT nIndex, BOOL bSysMenu);
afx_msg void OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis);
HMENU GetIconForItem(UINT itemID) const;
添加这些消息映射条目:{C}
BEGIN_MESSAGE_MAP(CMainFrame中,CFrameWnd中)和END_MESSAGE_MAP()之间。
最后,粘贴到您的CPP文件中的这四个功能:HICON CMainFrame::GetIconForItem(UINT itemID) const
{
HICON hIcon = (HICON)0;
if (IS_INTRESOURCE(itemID))
{
hIcon = (HICON)::LoadImage(::AfxGetResourceHandle(),
MAKEINTRESOURCE(itemID), IMAGE_ICON, 0, 0,
LR_DEFAULTCOLOR | LR_SHARED);
}
if (!hIcon)
{
CString sItem; // look for a named item in resources
GetMenu()->GetMenuString(itemID, sItem, MF_BYCOMMAND);
sItem.Replace(_T(' '), _T('_'));
// cannot have resource items with space in name
if (!sItem.IsEmpty())
hIcon = (HICON)::LoadImage(::AfxGetResourceHandle(), sItem,
IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR | LR_SHARED);
}
return hIcon;
}
void CMainFrame::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
if ((lpdis==NULL)||(lpdis->CtlType != ODT_MENU))
{
CFrameWnd::OnDrawItem(nIDCtl, lpdis);
return; //not for a menu
}
HICON hIcon = GetIconForItem(lpdis->itemID);
if (hIcon)
{
ICONINFO iconinfo;
::GetIconInfo(hIcon, &iconinfo);
BITMAP bitmap;
::GetObject(iconinfo.hbmColor, sizeof(bitmap), &bitmap);
::DeleteObject(iconinfo.hbmColor);
::DeleteObject(iconinfo.hbmMask);
::DrawIconEx(lpdis->hDC, lpdis->rcItem.left, lpdis->rcItem.top,
hIcon, bitmap.bmWidth, bitmap.bmHeight, 0, NULL, DI_NORMAL);
// ::DestroyIcon(hIcon); // we use LR_SHARED instead
}
}
void CMainFrame::OnInitMenuPopup(CMenu* pMenu, UINT nIndex, BOOL bSysMenu)
{
AfxTrace(_T(__FUNCTION__) _T(": %#0x\n"), pMenu->GetSafeHmenu());
CFrameWnd::OnInitMenuPopup(pMenu, nIndex, bSysMenu);
if (bSysMenu)
{
pMenu = GetSystemMenu(FALSE);
}
MENUINFO mnfo;
mnfo.cbSize = sizeof(mnfo);
mnfo.fMask = MIM_STYLE;
mnfo.dwStyle = MNS_CHECKORBMP | MNS_AUTODISMISS;
pMenu->SetMenuInfo(&mnfo);
MENUITEMINFO minfo;
minfo.cbSize = sizeof(minfo);
for (UINT pos=0; pos < pMenu->GetMenuItemCount(); pos++)
{
minfo.fMask = MIIM_FTYPE | MIIM_ID;
pMenu->GetMenuItemInfo(pos, &minfo, TRUE);
HICON hIcon = GetIconForItem(minfo.wID);
if (hIcon && !(minfo.fType & MFT_OWNERDRAW))
{
AfxTrace(_T("replace for \"%s\" id=%u width=%d\n"),
(LPCTSTR)sItem, (WORD)minfo.wID, 0); // size.cx);
minfo.fMask = MIIM_FTYPE | MIIM_BITMAP;
minfo.hbmpItem = HBMMENU_CALLBACK;
minfo.fType = MFT_STRING;
pMenu->SetMenuItemInfo(pos, &minfo, TRUE);
}
else
AfxTrace(_T("keep for %s id=%u\n"), (LPCTSTR)sItem, (WORD)minfo.wID);
// ::DestroyIcon(hIcon); // we use LR_SHARED instead
}
}
void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
if ((lpmis==NULL)||(lpmis->CtlType != ODT_MENU))
{
CFrameWnd::OnMeasureItem(nIDCtl, lpmis); //not for a menu
return;
}
lpmis->itemWidth = 16;
lpmis->itemHeight = 16;
CString sItem;
GetMenu()->GetMenuString(lpmis->itemID, sItem, MF_BYCOMMAND);
HICON hIcon = GetIconForItem(lpmis->itemID);
if (hIcon)
{
ICONINFO iconinfo;
::GetIconInfo(hIcon, &iconinfo);
BITMAP bitmap;
::GetObject(iconinfo.hbmColor, sizeof(bitmap), &bitmap);
::DeleteObject(iconinfo.hbmColor);
::DeleteObject(iconinfo.hbmMask);
lpmis->itemWidth = bitmap.bmWidth;
lpmis->itemHeight = bitmap.bmHeight;
AfxTrace(_T(__FUNCTION__) _T(": %d \"%s\"%dx%d ==> %dx%d\n"),
(WORD)lpmis->itemID, (LPCTSTR)sItem, bitmap.bmWidth,
bitmap.bmHeight, lpmis->itemWidth, lpmis->itemHeight);
}
}
现在,你可以编译您的应用程序,看到这一切都没有改变。为了你的截图上看到的一些菜单项,添加图像,您只需图标添加到您的资源。图标ID应该是菜单ID相同。这是所有。
它的图标呈现好听的责任。大小并不重要。一个良好的图标编辑器(我用的图标插件)可以创建任意大小和颜色的图标。
有时候,这是不够的。不幸的是,菜单项包含子菜单没有菜单的ID。或者至少,你可以不设置这样的资源编辑器的ID。对于这些情况,您可以添加一个图标,其名称对应的子菜单的名称。像这样:ICONS ICON "res\\lock.ico"
...
IDR_MAINFRAME MENU
BEGIN
POPUP "&File"
BEGIN
POPUP "Icons"
该方法映射菜单图标的文字使用下划线(_)替换空格字符;此外,请注意,您可以使用的放大器;图标标识符的字符,但有一个陷阱:在Windows资源管理器将其识别为第一个这样的一个图标列表中,并用它来代表你的可执行文件。解决方法:设置标识符放大器;用来调用IDR_MAINFRAME的图标(一个字符)。一些披露的法宝
我们扫描,因为他们要显示菜单,并添加一个标志,该项目应自行绘制位图。如果资源文件为这个项目提供了一个图标,位图中提取图标。 WM_MEASUREITEM消息,只要求为位图的大小。
注意,所有的样式像变灰,默认情况下,等数据仍然可用。不幸的是,灰色的图标是在全彩显示项目时,突出显示(选择)。您将需要一个特殊的函数(在评论中发表的由B GA)来覆盖此行为。
我们设置菜单风格MNS_CHECKORBMP纯粹审美原因。但是,如果有些连接的图标,这些项目的检查,复选标记将覆盖自定义绘制的回调。另一方面,提出的方法可以很容易地推广到显示自定义丰富多彩的检查标志。关于菜单栏有些话
这里介绍的技术与菜单栏(即以上的客户区窗口,文件编辑查看帮助总是可见的一部分,这是)工作,但结果是小于完美(例如,下划线绘制在图像),并要求搞乱Windows提供的OnDrawItem函数的矩形。无论如何,附加代码(压缩演示)绘制一个图标,在菜单栏中。如果你有32位(XP风格)的图标和Win2K?
这可能听起来好笑,但直到最近,我遇到一个真彩色32位的图标显示在Windows 2000上的要求。在Windows XP中,所有你所要做的的是调用DrawIconEx(HDC,左,上,惠康,宽度,高度,0,NULL,则DI_NORMAL);不过,在Windows 2K,alpha通道是忽略这个API。如下片段,与旧版的Windows兼容。需要注意的是32位的图标是一个简单的的方式来表示带有alpha通道的位图。图标格式是没有办法仅限于预定义平方米大小,实际上是比32位的BMP格式支持。我个人使用Paint.NET"保险公司条例"插件生成等资源。
鸣谢和更新static inline unsigned int alphaBlend(const unsigned int bg, const unsigned int src)
{
unsigned int a = src >> 24; // sourceColor alpha
// If source pixel is transparent, just return the background
if (0 == a) return bg;
if (255 == a) return src;
// alpha-blend the src and bg colors
unsigned int rb = (((src & 0x00ff00ff) * a) +
((bg & 0x00ff00ff) * (0xff - a))) & 0xff00ff00;
unsigned int g = (((src & 0x0000ff00) * a) +
((bg & 0x0000ff00) * (0xff - a))) & 0x00ff0000;
return (src & 0xff000000) | ((rb | g) >> 8);
}
void MyDrawIcon(HDC hdc, int iconID, int left=0, int top=0, int width=0, int height=0)
{
if (iconID <= 0)
return;
HICON hIcon = LoadIcon(iconID);
if (!hIcon)
{
#ifdef _DEBUG
static bool once = true;
if (once)
{
once = false;
char str[100];
HWND hwnd = WindowFromDC(hdc);
if (GetDlgCtrlID(hwnd))
{
sprintf_s(str, "iconID=%d is unknown for control=%d",
iconID, GetDlgCtrlID(hwnd));
MessageBoxA(GetParent(hwnd), str, "Debug", MB_OK | MB_APPLMODAL);
}
else
{
sprintf_s(str, "iconID=%d is unknown for window=%#x", iconID, hwnd);
MessageBoxA(hwnd, str, "Debug", MB_OK | MB_APPLMODAL);
}
}
#endif
return;
}
#if 1 // WIN2K
ICONINFO iconInfo;
GetIconInfo(hIcon, &iconInfo);
if (iconInfo.hbmMask)
{
BITMAP bm;
GetObject(iconInfo.hbmMask, sizeof(bm), &bm);
DeleteBitmap(iconInfo.hbmMask);
}
if (!iconInfo.hbmColor)
{
#ifdef _DEBUG
static bool once = true;
if (once)
{
once = false;
char str[100];
HWND hwnd = WindowFromDC(hdc);
if (GetDlgCtrlID(hwnd))
{
sprintf_s(str, "iconInfo.hbmColor is NULL for control=%d",
GetDlgCtrlID(hwnd));
MessageBoxA(GetParent(hwnd), str, "Debug", MB_OK | MB_APPLMODAL);
}
else
{
sprintf_s(str, "iconInfo.hbmColorhbmColor is NULL for window=%#x", hwnd);
MessageBoxA(hwnd, str, "Debug", MB_OK | MB_APPLMODAL);
}
}
#endif
return;
}
BITMAP bm;
GetObject(iconInfo.hbmColor, sizeof(bm), &bm);
if (width == 0)
width = bm.bmWidth;
if (height == 0)
height = bm.bmHeight;
if (bm.bmBitsPixel != 32)
{
#ifdef _DEBUG
static bool once = true;
if (once)
{
once = false;
char str[100];
HWND hwnd = WindowFromDC(hdc);
if (GetDlgCtrlID(hwnd))
{
sprintf_s(str, "iconInfo.hbmColor Bits/Pixel=%d" +
" is not correct for control=%d",
bm.bmBitsPixel, GetDlgCtrlID(hwnd));
MessageBoxA(GetParent(hwnd), str, "Debug", MB_OK | MB_APPLMODAL);
}
else
{
sprintf_s(str, "iconInfo.hbmColor Bits/Pixel=%d" +
" is not correct for window=%#x", bm.bmBitsPixel, hwnd);
MessageBoxA(hwnd, str, "Debug", MB_OK | MB_APPLMODAL);
}
}
DeleteBitmap(iconInfo.hbmColor);
#endif
return;
}
BITMAPINFO bmi = { sizeof(BITMAPINFOHEADER) };
// get bitmap info
GetDIBits(hdc, iconInfo.hbmColor, 0, bm.bmHeight, NULL, &bmi, DIB_RGB_COLORS);
// prepare pixel buffer; note we use 32 bits per pixel
LPDWORD iconBits = (LPDWORD)malloc(bmi.bmiHeader.biSizeImage);
// get pixels
GetDIBits(hdc, iconInfo.hbmColor, 0, bm.bmHeight, iconBits, &bmi, DIB_RGB_COLORS);
// if width and height are specified, use these for destination bitmap
bmi.bmiHeader.biWidth = width;
bmi.bmiHeader.biHeight = height;
HDC hdcMem = CreateCompatibleDC(hdc);
LPDWORD pBitsDest = NULL;
HBITMAP hBmpDest = CreateDIBSection(hdcMem, &bmi, DIB_RGB_COLORS,
(void **)&pBitsDest, NULL, 0);
HBITMAP hOld = SelectBitmap(hdcMem, hBmpDest);
// copy the background to memory DC; the pBitsDest buffer will reflect the change
HWND hwnd = WindowFromDC(hdc);
if (IsWindow(hwnd) && GetDlgCtrlID(hwnd)) // this is a dialog child
{
RECT rc;
GetWindowRect(hwnd, &rc);
ScreenToClient(GetParent(hwnd), (LPPOINT)&rc);
HDC parentDC = GetDC(GetParent(hwnd));
BitBlt(hdcMem, 0, 0, width, height, parentDC, rc.left+left, rc.top+top, SRCCOPY);
ReleaseDC(GetParent(hwnd), parentDC);
}
else
{
BitBlt(hdcMem, 0, 0, width, height, hdc, left, top, SRCCOPY);
}
// tile the alpha mask image if the size does not fit
for (int y=0, ys=0; y < height; y++, (++ys < bm.bmHeight) || (ys = 0))
{
for (int x=0, xs=0; x < width; x++, (++xs < bm.bmWidth) || (xs = 0))
{
*pBitsDest = alphaBlend(*pBitsDest, iconBits[xs + ys*bm.bmWidth]);
pBitsDest++;
}
}
// the bitmap has changed, select it and draw it
SelectBitmap(hdcMem, hBmpDest);
BitBlt(hdc, left, top, width, height, hdcMem, 0, 0, SRCCOPY);
SelectBitmap(hdcMem, hOld);
DeleteDC(hdcMem);
DeleteBitmap(iconInfo.hbmColor);
DeleteBitmap(hBmpDest);
free(iconBits);
#else
DrawIconEx(hdc, left, top, hIcon, width, height, 0, NULL, DI_NORMAL);
#endif
}
感谢所有提意见,特别是Gernot弗里希,到B GA,并DarkWeaver5455和乔鹧鸪代码审查。请注意评论,其中B GA显示一个图标如何可以得出反映突出或停用状态。
2007年2月25日更新,解决资源泄漏,指出乔鹧鸪。演示项目的zip文件被更新,以反映在文章中公布的代码。在VC6编译,(这些变化都不会反映在文章正文)。
最新的更新(1/21/2008)显示,32位图标可以显示在Windows 2000。