说到为MFC菜单添加图标,比较容易想到的是通过CMenu类提供的接口SetMenuItemBitmaps来实现这个需求,该函数的原型如下:
BOOL SetMenuItemBitmaps(
UINT nPosition,
UINT nFlags,
const CBitmap* pBmpUnchecked,
const CBitmap* pBmpChecked);
但是使用这个函数有一个问题,就是它提供的参数是CBitmap类型,也就是说它只允许你设置一个位图作为图标,但是如果我们使用Win32API加载的位图是不透明的,使用此方法为菜单设置的图标就总是会有一个背景。因此我们希望能够将一个HICON资源设置为菜单项的图标,但是很遗憾,CMenu类没有并诸如SetMenuItemIcon这样的接口,也没有其他的API能够实现这个需求。
为了达到这个目的,我之前在网上找了很多帖子,但是都没有找到我想要的。如果你去搜索引擎上搜索“MFC如何为菜单添加图标”之类的关键字,网上的帖子只会告诉你使用SetMenuItemBitmaps。
然而,功夫不负有心人,我终于在微软MSDN网站找到了这样篇文章:Visual Style Menus
文章介绍了使用Windows Imaging Component (WIC) 和 GDI 两种方式为菜单项添加具有透明混合 (alpha-blended) 的图标。本文主要介绍前一种方法。
要为菜单项添加具有透明混合的图标,就需要将图标 (HICON) 转换为具有透明混合的位图。透明混合 (alpha-blended) 的位图是从Windows Vista开始支持的功能。这个转换可以通过 Windows Imaging Component (WIC) 来实现。
要使用 WIC,需要使用CoCreateInstance函数创建一个WIC 图像工厂对象:
HRESULT _hrOleInit{};
IWICImagingFactory *m_pWICFactory{};
_hrOleInit = ::OleInitialize(NULL);
CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pWICFactory));
如果创建成功,IWICImagingFactory 的指针会得到一个WIC 图像工厂对象。
以下代码用于把一个HICON转换成一个具有透明混合的位图(代码来自MSDN网站):
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();
}
最后附上我封装整理后的代码:
WIC.h:
#pragma once
class CWCIFactory
{
public:
~CWCIFactory();
static IWICImagingFactory* GetWCI() { return m_instance.m_pWICFactory; }
private:
HRESULT _hrOleInit{};
IWICImagingFactory *m_pWICFactory{};
static CWCIFactory m_instance; //CWCIFactory类唯一的对象
private:
CWCIFactory();
};
class CMenuIcon
{
public:
CMenuIcon();
~CMenuIcon();
//向一个菜单项添加图标
static HRESULT AddIconToMenuItem(HMENU hmenu, int iMenuItem, BOOL fByPosition, HICON hicon);
private:
static HRESULT AddBitmapToMenuItem(HMENU hmenu, int iItem, BOOL fByPosition, HBITMAP hbmp);
static void InitBitmapInfo(__out_bcount(cbInfo) BITMAPINFO *pbmi, ULONG cbInfo, LONG cx, LONG cy, WORD bpp);
static HRESULT Create32BitHBITMAP(HDC hdc, const SIZE *psize, __deref_opt_out void **ppvBits, __out HBITMAP* phBmp);
};
WIC.cpp
#include "stdafx.h"
#include "WIC.h"
CWCIFactory CWCIFactory::m_instance;
CWCIFactory::CWCIFactory()
{
//初始化m_pWICFactory
_hrOleInit = ::OleInitialize(NULL);
CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pWICFactory));
}
CWCIFactory::~CWCIFactory()
{
if (m_pWICFactory)
{
m_pWICFactory->Release();
m_pWICFactory = NULL;
}
if (SUCCEEDED(_hrOleInit))
{
OleUninitialize();
}
}
//
typedef DWORD ARGB;
CMenuIcon::CMenuIcon()
{
}
CMenuIcon::~CMenuIcon()
{
}
HRESULT CMenuIcon::AddIconToMenuItem(HMENU hmenu, int iMenuItem, BOOL fByPosition, HICON hicon)
{
if (CWCIFactory::GetWCI() == nullptr)
return 0;
HBITMAP hbmp = NULL;
IWICBitmap *pBitmap;
HRESULT hr = CWCIFactory::GetWCI()->CreateBitmapFromHICON(hicon, &pBitmap);
if (SUCCEEDED(hr))
{
IWICFormatConverter *pConverter;
hr = CWCIFactory::GetWCI()->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;
}
return hr;
}
HRESULT CMenuIcon::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;
}
void CMenuIcon::InitBitmapInfo(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 CMenuIcon::Create32BitHBITMAP(HDC hdc, const SIZE * psize, void ** ppvBits, 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;
}
在以上代码中,我封装了一个单实例类CWCIFactory来管理 IWICImagingFactory 的指针。该类提供了唯一的公共接口
static IWICImagingFactory* GetWCI()
来获取 IWICImagingFactory 的指针。
CMenuIcon 是一个静态类,使用 CMenuIcon::AddIconToMenuItem 接口即可为菜单项添加一个图标。
static HRESULT AddIconToMenuItem(HMENU hmenu, int iMenuItem, BOOL fByPosition, HICON hicon);
其中 iItem 和 fByPosition 两个参数的用法和CMenu::SetMenuItemBitmaps相同。
下面附上在我开发的音乐播放器MusicPlayer2使用这个方法添加图标后的效果: