65.9K
CodeProject 正在变化。 阅读更多。
Home

如何创建拥有者绘制菜单——分步教程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.34/5 (14投票s)

2004年5月17日

3分钟阅读

viewsIcon

114839

downloadIcon

2587

本教程将分步介绍如何创建拥有者绘制菜单。

Sample Image

引言

这只是一个关于如何逐步创建您自己的 - 自定义绘制 - 菜单的教程!

教程

步骤 1

首先,您应该创建自己的类。我将其命名为 COwnMenu。基类应该是 CMenu。然后创建一个 struct,它可能看起来像这样

struct MenuObject { 
HICON m_hIcon; 
CString m_strCaption; 
};

然后,您必须在您的主类(例如,CMainFrameCTestDlg)中声明一个 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; 
   /*I don't use GetMenuItemID because 
     it doesn't return 0/-1 when it's a Popup
      (so the MSDN is wrong)*/
   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);// COLORREF(RGB(192,192,192));
  }

  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 风格的 Rect。 我制作了一个名为 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 将其绘制到图标 rect 中... 如果有人对此有问题 - 欢迎提问 :) ... 但我认为这并不难!

步骤 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 示例。

结束

现在,我们完成了... 理解起来真的不难...是吗? 我希望我能帮助你们中的一些人。

许可证

本文没有明确的许可证附加到它,但可能包含在文章文本或下载文件本身中的使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.