自定义绘制菜单教程

在本教程中,将学习如何使用C++创建一个自定义绘制的菜单。自定义菜单允许完全控制菜单项的外观和行为,这在创建具有独特界面的应用程序时非常有用。将从创建一个自定义类开始,然后逐步实现菜单项的自定义绘制。

步骤1:创建自定义菜单

首先,需要创建一个自定义类,将其命名为COwnMenu,它的基类是CMenu。然后,创建一个结构体来存储菜单项的信息,如下所示:

struct MenuObject { HICON m_hIcon; CString m_strCaption; };

接下来,在主类(例如CMainFrameCTestDlg)中声明一个COwnMenu实例,并建议声明两个向量来存储分配的每个项目的地址。

COwnMenu menu; std::vector<DWORD> deleteItem; std::vector<DWORD> deleteMenu;

步骤2:创建函数以更改菜单项

接下来,需要创建一个函数来将菜单的所有项更改为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的指针,稍后在DrawItemMeasureItem中需要使用它!接下来,检查该项是否为弹出项,这意味着它有子项。如果是,创建一个新的COwnMenu,将其地址添加到deleteMenu,这样就可以在销毁程序时清理整个内存。完成这些后,为这个项目执行整个函数。

步骤3:添加绘制和测量函数

现在,需要添加这两个函数:

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将其绘制到图标矩形中...如果有人对此有疑问,请随时提问...但认为这并不那么困难!

步骤4:清理内存

接下来是清理内存,当结束程序时,这并不难理解...所以看看这个:

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

步骤5:激活菜单

接下来是激活菜单。它可以在CDialog::OnInitDialog()CMainFrame::OnCreate()中完成。它可能看起来像这样:

menu.LoadMenu(IDR_MENU); menu.MakeItemsOwnDraw(TRUE); SetMenu(&menu);

附加信息

如果想让菜单有平面边框,那么必须设置一个WindowsHook,并且在WindowProc中,必须识别窗口是否是菜单...如果不知道这如何工作,可以看看CMenuXP示例。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485