在本教程中,将学习如何使用C++创建一个自定义绘制的菜单。自定义菜单允许完全控制菜单项的外观和行为,这在创建具有独特界面的应用程序时非常有用。将从创建一个自定义类开始,然后逐步实现菜单项的自定义绘制。
首先,需要创建一个自定义类,将其命名为COwnMenu
,它的基类是CMenu
。然后,创建一个结构体来存储菜单项的信息,如下所示:
struct MenuObject {
HICON m_hIcon;
CString m_strCaption;
};
接下来,在主类(例如CMainFrame
或CTestDlg
)中声明一个COwnMenu
实例,并建议声明两个向量来存储分配的每个项目的地址。
COwnMenu menu;
std::vector<DWORD> deleteItem;
std::vector<DWORD> deleteMenu;
接下来,需要创建一个函数来将菜单的所有项更改为MF_OWNERDRAW
。创建了一个递归函数来遍历每一个菜单项。分配的项目的地址存储在deleteItem
中,如果是菜单项,则存储在deleteMenu
中。函数可能如下所示:
void COwnMenu::MakeItemsOwnDraw(BOOL bFirst) {
int iMaxItems = GetMenuItemCount();
for (int i = 0; i < iMaxItems; i++) {
MenuObject* pObject = new MenuObject;
deleteItem.push_back((DWORD)pObject);
pObject->m_hIcon = NULL;
GetMenuString(i, pObject->m_strCaption, MF_BYPOSITION);
MENUITEMINFO mInfo;
ZeroMemory(&mInfo, sizeof(MENUITEMINFO));
UINT uID = mInfo.wID;
ModifyMenu(i, MF_BYPOSITION | MF_OWNERDRAW, uID, (char*)pObject);
if (GetSubMenu(i)) {
COwnMenu* pSubMenu = new COwnMenu;
deleteMenu.push_back((DWORD)pSubMenu);
pSubMenu->Attach(GetSubMenu(i)->GetSafeHmenu());
pSubMenu->MakeItemsOwnDraw();
}
}
}
首先遍历所有菜单项,然后创建一个新的MenuObject
并将其地址添加到deleteItem
。如果有图标,可以将pObject->m_hIcon
更改为其地址。接下来,获取项目的标题并将其保存到pObject->m_strCaption
。然后,将菜单的样式更改为MF_OWNERDRAW
。需要理解的是ModifyMenu
的最后一个参数是指向pObject
的指针,稍后在DrawItem
和MeasureItem
中需要使用它!接下来,检查该项是否为弹出项,这意味着它有子项。如果是,创建一个新的COwnMenu
,将其地址添加到deleteMenu
,这样就可以在销毁程序时清理整个内存。完成这些后,为这个项目执行整个函数。
现在,需要添加这两个函数:
void COwnMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) {
CRect rectFull(lpDrawItemStruct->rcItem);
CRect rectIcon(rectFull.left,rectFull.top,rectFull.left+20,rectFull.top+20);
CRect rectText(rectIcon.right,rectFull.top,rectFull.right,rectFull.bottom);
COLORREF IconRectLeft = COLORREF(RGB(246, 245, 244));
COLORREF IconRectRight = COLORREF(RGB(0, 209, 201));
COLORREF TextRect = COLORREF(RGB(249, 248, 247));
CRect rectBorder = rectFull;
rectBorder.right -= 1;
CRect rectFill = rectBorder;
rectFill.left += 1;
rectFill.right -= 1;
rectFill.top += 1;
rectFill.bottom -= 1;
if (((MenuObject*)lpDrawItemStruct->itemData)->bFirstMenu) {
ZeroMemory(&rectIcon, sizeof(CRect));
rectText = rectFull;
TextRect = GetSysColor(COLOR_BTNFACE);
}
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
FillFluentRect(pDC->GetSafeHdc(), rectIcon, 246, 245, 244, 213, 209, 201);
pDC->FillSolidRect(&rectText, TextRect);
if ((lpDrawItemStruct->itemState & ODS_SELECTED) && (lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE))) {
TextRect = COLORREF(RGB(182, 189, 210));
pDC->FillSolidRect(&rectBorder, COLORREF(RGB(10, 36, 106)));
pDC->FillSolidRect(&rectFill, TextRect);
}
pDC->SetBkColor(TextRect);
rectText.left += 5;
rectText.top += 1;
rectText.bottom += 1;
pDC->TextOut(rectText.left, rectText.top, ((MenuObject*)lpDrawItemStruct->itemData)->m_strCaption);
}
void COwnMenu::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) {
lpMeasureItemStruct->itemHeight = 20;
lpMeasureItemStruct->itemWidth = ((MenuObject*)lpMeasureItemStruct->itemData)->m_strCaption.GetLength()*8;
}
在MeasureItem
中,必须告诉菜单项有多大!认为这真的很容易理解。在DrawItem
中,检查项目是否被选中。如果是,绘制这个很酷的XP样式的矩形。创建了一个很酷的函数叫做FillFluentRect
,它为图标位置绘制很酷的特效;它看起来像这样:
void COwnMenu::FillFluentRect(HDC hDC, RECT rect, byte r1, byte g1, byte b1, byte r2, byte g2, byte b2) {
int iWidth = rect.right - rect.left;
int iHeight = rect.bottom - rect.top;
short rDif = r2 - r1;
short gDif = g2 - g1;
short bDif = b2 - b1;
for (int i = 0; i < iWidth; i++) {
byte rCur, gCur, bCur;
rCur = r1 + (short)((float)(((float)rDif/(float)iWidth)*(float)i));
gCur = g1 + (short)((float)(((float)gDif/(float)iWidth)*(float)i));
bCur = b1 + (short)((float)(((float)bDif/(float)iWidth)*(float)i));
for (int y = 0; y < iHeight; y++)
SetPixel(hDC, rect.left + i, rect.top + y, RGB(rCur, gCur, bCur));
}
}
认为不需要在这里解释任何事情。当想在DrawItem
中绘制图标或位图时,只需使用BitBlt
将其绘制到图标矩形中...如果有人对此有疑问,请随时提问...但认为这并不那么困难!
接下来是清理内存,当结束程序时,这并不难理解...所以看看这个:
COwnMenu::~COwnMenu() {
for (int i = 0; i < deleteItem.size(); i++) {
delete ((MenuObject*)deleteItem[i]);
}
for (int i = 0; i < deleteMenu.size(); i++) {
delete ((COwnMenu*)deleteMenu[i]);
}
}
接下来是激活菜单。它可以在CDialog::OnInitDialog()
或CMainFrame::OnCreate()
中完成。它可能看起来像这样:
menu.LoadMenu(IDR_MENU);
menu.MakeItemsOwnDraw(TRUE);
SetMenu(&menu);
如果想让菜单有平面边框,那么必须设置一个WindowsHook,并且在WindowProc
中,必须识别窗口是否是菜单...如果不知道这如何工作,可以看看CMenuXP
示例。