-- 作者:admin -- 发布时间:2005-10-17 9:20:00
-- 二、编程步骤
1、 启动Visual C++6.0生成一个单文档应用程序框架,去除文档支持,将应用程序命名为Hello;
2、 在CMainFrame类中添加CMenuEx类的成员变量m_menu;
3、 使用Class Wizard在CMainFrame类添加WM_INITMENU、WM_DRAWITEM、WM_MEASUREITEM消息响应函数、在ChildView类中添加WM_INITMENUPOPUP、WM_CONTEXTMENU消息响应函数;
4、 将需要显示图标的菜单的ID识别号与工具条上响应的按钮的ID识别号统一起来;
5、 在CChildView类中添加成员变量CToolBar * m_pToolBar和 CMenuEx m_menu。m_pToolBar对象主要是用来存放程序中的工具条,从而提供给上下文菜单m_menu自画时所需要的图标信息。
6、添加代码,编译运行程序。
三、程序代码
//////////////////////////////////////////////CMenuEx类的文件 #if !defined(AFX_MENUEX_H__FE677F6B_2315_11D7_8869_BB2B2A4F4D45__INCLUDED_) #define AFX_MENUEX_H__FE677F6B_2315_11D7_8869_BB2B2A4F4D45__INCLUDED_ #include "afxtempl.h" #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 typedef struct tagMENUITEM //该结构用存放菜单自绘时所需要的信息; { CString strText; //菜单项的文本; UINT uID; //菜单的ID标识号; UINT uIndex; //菜单项所要画的图标在工具条图标序列中的索引号; int uPositionImageLeft; //当前菜单项在纵向菜单中的位置; }MENUITEM; typedef MENUITEM * LPMENUITEM; ////////////////////////////////////////////////////////////// class CMenuEx : public CMenu { public: void InitPopupMenu(CMenu *pPopupMenu,UINT uToolBar,CToolBar *pToolBar); void ChangeStyle(CMenu *pMenu,CToolBar *pToolBar,BOOL bIsMainMenu=FALSE); void SetHighLightColor(COLORREF crColor); void SetBackColor(COLORREF); void SetTextColor(COLORREF); void SetImageLeft(UINT idBmpLeft); void MeasureItem(LPMEASUREITEMSTRUCT lpMIS); void InitMenu(CMenu *pMenu,UINT uToolBar,CToolBar *pToolBar); void DrawItem(LPDRAWITEMSTRUCT lpDIS); CMenuEx(); virtual ~CMenuEx();
protected: int m_nSeparator; CSize m_szImageLeft; //纵向位图尺寸; CBitmap m_bmpImageLeft; //纵向位图对象; int m_nWidthLeftImage; //纵向位图的宽度; BOOL m_bHasImageLeft; //是否有纵向位图 BOOL m_bInitial; //菜单是否已初始化,即设定了自绘风格 int GetImageFromToolBar(UINT uToolBar,CToolBar *pToolBar,COLORREF crMask=RGB(192,192,192)); //从工具条中获取图标信息; CList<MENUITEM *,MENUITEM *> m_ListMenu; COLORREF m_colMenu; COLORREF m_colTextSelected; void DrawImageLeft(CDC *pDC,CRect &rect,LPMENUITEM lpItem); //画纵向位图; void TextMenu(CDC *pDC,CRect &rect,CRect rtText,BOOL bSelected,BOOL bGrayed,LPMENUITEM lpItem); //画菜单文本; CImageList m_ImageList; //图像列表; COLORREF m_colText; CSize m_szImage; //菜单项位图的大小 void DrawMenuItemImage(CDC *pDC,CRect &rect,BOOL bSelected,BOOL bChecked,BOOL bGrayed,BOOL bHasImage,LPMENUITEM lpItem);//画菜单图标; void GrayString(CDC *pDC,const CString &str,const CRect rect);//显示灰色字符串; };
#endif
/////////////////////////////////////////////////CMenuEx类的实现文件; #include "stdafx.h" #include "MenuEx.h"
#ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif ///////////////////////////////////////////// //构造函数; CMenuEx::CMenuEx():m_szImage(16,15) { m_colMenu =::GetSysColor(COLOR_MENU); m_colText =::GetSysColor(COLOR_MENUTEXT); m_colTextSelected =::GetSysColor(COLOR_HIGHLIGHTTEXT); m_bInitial =FALSE; m_bHasImageLeft =FALSE; m_nSeparator = 10; //菜单分割条的默认高度 } //////////////////////////////////////////////析构函数; CMenuEx::~CMenuEx() { m_ImageList.DeleteImageList(); //清空图像列表; while(!m_ListMenu.IsEmpty()) //清空菜单项信息列表; delete m_ListMenu.RemoveHead(); if(m_bHasImageLeft) //释放纵向位图对象; m_bmpImageLeft.DeleteObject(); } ///////////////////////////////////////////////////当菜单项为不可用时绘制灰色的文本; void CMenuEx::GrayString(CDC *pDC, const CString &str, const CRect rect) { CRect rt(rect); rt.left +=1; rt.top +=1; pDC->SetTextColor(RGB(255,255,255)); pDC->DrawText(str,&rt,DT_EXPANDTABS|DT_VCENTER|DT_SINGLELINE); rt.left -=1; rt.top -=1; pDC->SetTextColor(RGB(127,127,127)); pDC->DrawText(str,&rt,DT_EXPANDTABS|DT_VCENTER|DT_SINGLELINE); } ///////////////////////////////////////////////////绘制菜单项位图 void CMenuEx::DrawMenuItemImage(CDC *pDC, CRect &rect, BOOL bSelected, BOOL bChecked, BOOL bGrayed, BOOL bHasImage,LPMENUITEM lpItem) { CRect rt(rect.left ,rect.top ,rect.left + m_szImage.cx + 4, rect.top + m_szImage.cy + 4); //确定显示图标的位置; if(bChecked) //根据不同的状态画菜单的选择标志; { if(bGrayed) { //菜单不可用 GrayString(pDC,"√",rt); } else { if(bSelected) { //当该菜单项被选中时绘制一个立体矩形 pDC->Draw3dRect(&rt,RGB(255,255,255),RGB(127,127,127)); } rt.InflateRect(-2,-2); //画出"√" pDC->SetBkMode(TRANSPARENT); pDC->SetTextColor(m_colText); pDC->DrawText("√",&rt,DT_EXPANDTABS|DT_VCENTER|DT_SINGLELINE); } rect.left +=m_szImage.cx + 4 +2 ; //重新计算rect的尺寸,为显示菜单文本作准备; return ; } if(bHasImage) //如果菜单有图标 { CPoint pt(rt.left+2 , rt.top+2 ); UINT uStyle =ILD_TRANSPARENT; //CImageList::Draw()绘制位图的风格 if(bGrayed) { uStyle |=ILD_BLEND50; //菜单不可用所以位图较暗 } else { if(bSelected) { //当该项被选中仅多绘制一个立体矩形 pDC->Draw3dRect(&rt,RGB(255,255,255),RGB(127,127,127)); } } m_ImageList.Draw(pDC,lpItem->uIndex,pt,uStyle); //在菜单项中绘制图标; //调整可绘制矩形的大小,位图外接矩形比位图大4,菜单文本与位图外接矩形的间隔为2 rect.left +=m_szImage.cx + 4 + 2; } } ///////////////////////////////////////////////////绘制菜单项文本 void CMenuEx::TextMenu(CDC *pDC, CRect &rect,CRect rtText,BOOL bSelected, BOOL bGrayed, LPMENUITEM lpItem) { //选中状态的菜单项要先画出立体矩形 if(bSelected) pDC->Draw3dRect(&rect,RGB(127,127,127),RGB(255,255,255)); if(bGrayed) { GrayString(pDC,lpItem->strText,rtText); } else { pDC->DrawText(lpItem->strText,rtText,DT_LEFT|DT_EXPANDTABS|DT_VCENTER); } }
///////////////////////////////////////////////////画菜单的纵向位图; void CMenuEx::DrawImageLeft(CDC *pDC, CRect &rect,LPMENUITEM lpItem) { if(!m_bHasImageLeft || lpItem->uPositionImageLeft ==-1) return ; CDC memDC; memDC.CreateCompatibleDC(pDC); //装载位图; CBitmap *oldBmp =(CBitmap *) memDC.SelectObject(&m_bmpImageLeft); int cy; //设定该菜单项应从哪画起 if(m_szImageLeft.cy >= lpItem->uPositionImageLeft + rect.Height()) { cy =(int) m_szImageLeft.cy - lpItem->uPositionImageLeft - rect.Height(); ASSERT(cy>=0); } else cy =0; //画图; pDC->BitBlt(rect.left ,rect.top ,m_szImageLeft.cx ,rect.Height(),&memDC,0,cy,SRCCOPY); memDC.SelectObject(oldBmp); memDC.DeleteDC(); rect.left +=m_szImageLeft.cx+1; } /////////////////////////////////////////////////////////////菜单自画; void CMenuEx::DrawItem(LPDRAWITEMSTRUCT lpDIS) { CDC dc; LPMENUITEM lpItem; CRect rect(lpDIS->rcItem); dc.Attach(lpDIS->hDC); //得到自画菜单所需要的信息; lpItem =(LPMENUITEM)lpDIS->itemData; //根据不同的状态设置菜单的文本颜色; if(lpDIS->itemState & ODS_SELECTED) dc.SetTextColor(m_colTextSelected); else dc.SetTextColor(m_colText); //设定背景色 CBrush brush(m_colMenu); dc.FillRect(&rect, &brush); //设定显示模式 dc.SetBkMode(TRANSPARENT); //绘制侧边位图 DrawImageLeft(&dc,rect,lpItem);
if(lpItem->uID==0)//分隔条 { rect.top =rect.Height()/2+rect.top ; rect.bottom =rect.top +2; rect.left +=2; rect.right -=2; dc.Draw3dRect(rect,RGB(64,0,128),RGB(255,255,255)); } else { //得到当前菜单项目的状态; BOOL bSelected =lpDIS->itemState & ODS_SELECTED; BOOL bChecked =lpDIS->itemState & ODS_CHECKED; BOOL bGrayed =lpDIS->itemState & ODS_GRAYED; BOOL bHasImage =(lpItem->uIndex!=-1); //设定菜单文本的区域 CRect rtText(rect.left+m_szImage.cx+4+2, rect.top,rect.right ,rect.bottom ); rtText.InflateRect(-2,-2); //绘制菜单位图 DrawMenuItemImage(&dc,rect,bSelected,bChecked,bGrayed,bHasImage,lpItem); //绘制菜单文本 TextMenu(&dc,rect,rtText,bSelected,bGrayed,lpItem); } dc.Detach(); } ////////////////////////////////////////////////////////////改变菜单风格 //注意第二个参数:FALSE:表示pMenu指向的不是主框架菜单 void CMenuEx::ChangeStyle(CMenu *pMenu,CToolBar *pToolBar,BOOL bIsMainMenu) { ASSERT(pMenu); TRACE("ChangeStyle\\n"); LPMENUITEM lpItem; CMenu *pSubMenu; int m,nPosition=0; //该变量用来绘制纵向位图的位置 int inx; UINT idx,x; for(int i=(int)pMenu->GetMenuItemCount()-1 ;i>=0; i--) { //得到菜单的信息 lpItem =new MENUITEM; lpItem->uID =pMenu->GetMenuItemID(i); if(!bIsMainMenu) //不是第一级菜单 lpItem->uPositionImageLeft =-1;//上下文菜单不支持纵向位图 else lpItem->uPositionImageLeft =nPosition;
if(lpItem->uID >0) { if(bIsMainMenu) nPosition +=m_szImage.cy+4; //保存菜单文本 pMenu->GetMenuString(i,lpItem->strText,MF_BYPOSITION); //由工具栏位图中寻找菜单项的位图,如果没有则uIndex为-1 lpItem->uIndex =-1; if(pToolBar) { for(m=0; m<(pToolBar->GetToolBarCtrl().GetButtonCount()) ;m++) { pToolBar->GetButtonInfo(m,idx,x,inx);
if(idx==lpItem->uID) { lpItem->uIndex=inx; break; } } } //如果该项下还有子菜单,则递归调用该函数来修改其子菜单的风格 pSubMenu =pMenu->GetSubMenu(i); if(pSubMenu) ChangeStyle(pSubMenu,pToolBar); } else { if(bIsMainMenu) nPosition +=m_nSeparator; } //修改菜单风格为自绘 pMenu->ModifyMenu(i,MF_BYPOSITION|MF_OWNERDRAW,lpItem->uID,(LPCTSTR)lpItem); m_ListMenu.AddTail(lpItem); } } /////////////////////////////////////////////由工具栏的位图来产生菜单所用的位图列表m_ImageList; int CMenuEx::GetImageFromToolBar(UINT uToolBar, CToolBar *pToolBar,COLORREF crMask) { if(!pToolBar) return 0; CBitmap bmp; int nWidth,nHeight; BITMAP bmpInfo; bmp.LoadBitmap(uToolBar);//将工具条作为位图资源装载; bmp.GetBitmap(&bmpInfo); //得到位图的高度 nHeight =bmpInfo.bmHeight; int nCount=0; int ret =pToolBar->GetToolBarCtrl().GetButtonCount(); //得到工具栏中位图的个数nCount for(int i=0;i<ret;i++) if(pToolBar->GetItemID(i)!=ID_SEPARATOR) nCount ++; //计算出位图的宽度 nWidth =bmpInfo.bmWidth/nCount; bmp.DeleteObject(); TRACE("Menu Bitmap--width:%d\\theight:%d\\n",nWidth,nHeight); //创建位图列表 m_ImageList.Create(uToolBar,nWidth,nHeight,crMask); m_szImage.cx =nWidth; m_szImage.cy =nHeight; return nCount; } //////////////////////////////////////////////////////////////////初始化菜单对象; void CMenuEx::InitMenu(CMenu *pMenu, UINT uToolBar, CToolBar *pToolBar) { //已设定了风格 if(m_bInitial) return ; GetImageFromToolBar(uToolBar,pToolBar); CMenu *pSubMenu,*pSubsub; MENUITEM *lpItem; UINT i; int j,m; int nPosition; //该变量用来存放纵向位图的位置 for(i=0;i<pMenu->GetMenuItemCount();i++) { pSubMenu =pMenu->GetSubMenu(i); if(pSubMenu) { nPosition =0; //注意j一定要为int类型,如果为UINT是检查不出j>=0! for(j=(int)pSubMenu->GetMenuItemCount()-1;j>=0;j--) { lpItem =new MENUITEM; lpItem->uID =pSubMenu->GetMenuItemID(j); lpItem->uPositionImageLeft =nPosition; if(lpItem->uID>0) { nPosition +=m_szImage.cy+4; pSubMenu->GetMenuString(j,lpItem->strText,MF_BYPOSITION); //由工具栏位图中寻找菜单项的位图,如果没有则uIndex为-1 lpItem->uIndex =-1; for(m=0; m<(pToolBar->GetToolBarCtrl().GetButtonCount()) ;m++) { int inx; UINT idx,x; pToolBar->GetButtonInfo(m,idx,x,inx); if(idx==lpItem->uID) { lpItem->uIndex=inx; break; } } } else { //间隔条; nPosition +=m_nSeparator; } m_ListMenu.AddTail(lpItem); //修改菜单绘制风格; pSubMenu->ModifyMenu(j,MF_BYPOSITION|MF_OWNERDRAW, lpItem->uID,LPCTSTR(lpItem)); pSubsub =pSubMenu->GetSubMenu(j); if(pSubsub) ChangeStyle(pSubsub,pToolBar); } } }
m_bInitial =TRUE; } //////////////////////////////////////////////////////重载CMenu类的MeasureItem()函数; void CMenuEx::MeasureItem(LPMEASUREITEMSTRUCT lpMIS) { MENUITEM *lpItem =(LPMENUITEM)lpMIS->itemData;
if(lpItem->uID==0)//分隔条的高度为10个像素; { lpMIS->itemHeight =m_nSeparator; } else { //填充lpMIS结构; CDC *pDC =AfxGetMainWnd()->GetDC(); CString strText=lpItem->strText; CSize size; size=pDC->GetTextExtent(lpItem->strText); lpMIS->itemWidth = size.cx +m_szImage.cx+4; lpMIS->itemHeight =m_szImage.cy+4; AfxGetMainWnd()->ReleaseDC(pDC); } } ////////////////////////////////////////////////////////设置菜单上的纵向位图; void CMenuEx::SetImageLeft(UINT idBmpLeft) { m_bmpImageLeft.LoadBitmap(idBmpLeft);//装载纵向位图; m_bHasImageLeft = TRUE; BITMAP bmpInfo; m_bmpImageLeft.GetBitmap(&bmpInfo); m_szImageLeft.cx =bmpInfo.bmWidth; m_szImageLeft.cy =bmpInfo.bmHeight; } ////////////////////////////////////////////////////////////////设置菜单上的文本颜色; void CMenuEx::SetTextColor(COLORREF crColor) { m_colText =crColor; } ////////////////////////////////////////////////////////////设置菜单的背景颜色; void CMenuEx::SetBackColor(COLORREF crColor) { m_colMenu =crColor; } //////////////////////////////////////////////////////////设置菜单高亮显示时的颜色; void CMenuEx::SetHighLightColor(COLORREF crColor) { m_colTextSelected =crColor; }
//////////////////////////////////////////////////////初始化上下文菜单; void CMenuEx::InitPopupMenu(CMenu *pPopupMenu,UINT uToolBar, CToolBar *pToolBar) { if(m_bInitial) return ; GetImageFromToolBar(uToolBar,pToolBar); ChangeStyle(pPopupMenu,pToolBar); m_bInitial =TRUE; }
////////////////////////////////////////////////////在程序中使用CMenuEx类对象实现菜单的自画; int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { …………………………….. if (!m_wndStatusBar.Create(this) ||!m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Failed to create status bar\\n"); return -1; // fail to create } m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); m_wndView.m_pToolBar =&m_wndToolBar; // 将视图内的工具条变量附值 return 0; }
///////////////////////////////////////////////////////////////初始自画菜单; void CMainFrame::OnInitMenu(CMenu* pMenu) { CFrameWnd::OnInitMenu(pMenu); // TOD Add your message handler code here m_menu.InitMenu(pMenu,IDR_MAINFRAME,&m_wndToolBar); } ////////////////////////////////////////////////////////////////////通知菜单自画; void CMainFrame::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) { // TOD Add your message handler code here and/or call default if(!nIDCtl) m_menu.DrawItem(lpDrawItemStruct); CFrameWnd::OnDrawItem(nIDCtl, lpDrawItemStruct); } //////////////////////////////////////////////////////////////////通知菜单确定尺寸; void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) { // TOD Add your message handler code here and/or call default if(!nIDCtl) m_menu.MeasureItem(lpMeasureItemStruct); CFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct); } /////////////////////////////////////////////////////////////////初始化上下文菜单; CChildView::CChildView() { m_menu.CreatePopupMenu(); m_menu.AppendMenu(0,ID_EDIT_UNDO,"撤消"); m_menu.AppendMenu(MF_SEPARATOR,0); m_menu.AppendMenu(0,ID_EDIT_COPY,"复制"); m_menu.AppendMenu(0,ID_EDIT_CUT,"剪切"); m_menu.AppendMenu(0,ID_EDIT_PASTE,_T("粘贴")); } /////////////////////////////////////////////////////////////////////////////// CChildView::~CChildView() //销毁上下文菜单; { m_menu.DestroyMenu(); } ///////////////////////////////////////////////////////////////////修改菜单实现自画; void CChildView::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) { CWnd ::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu); // TOD Add your message handler code here if(!bSysMenu) m_menu.InitPopupMenu(pPopupMenu,IDR_MAINFRAME,m_pToolBar); } /////////////////////////////////////////////////////////////显示上下文菜单; void CChildView::OnContextMenu(CWnd* pWnd, CPoint point) { // TOD Add your message handler code here m_menu.TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x,point.y,this); } |
四、小结
到此为止,本例详细介绍了菜单自绘类CMenuEx的实现以及它在应用程序的具体使用方法,相信读者能够从中学习到菜单的自绘制机理。本例中的CMenuEx类稍加改动,就可以实现各种不同类型的菜单效果,如WindowsXP风格的菜单等。
|
-- 作者:admin -- 发布时间:2005-10-17 9:20:00
-- 一种漂亮的自绘菜单 以前还是菜鸟时就觉得QQ的菜单做得很漂亮,想着自已的程序如果有那种菜单多好。 现在积累了一定的知识,就自已设计了一个类似的菜单控件类。并把它发表出来供大家使用和参考,难免有不足的地方请高手不吝赐教! 菜单效果如下:
一、CMenuEx菜单类主要接口函数:
1、void InitMenu(CMenu *pMenu,UINT uToolBar,CToolBar *pToolBar); 说明:这是最主要的一个接口。如果要改变主窗口的菜单则应在主窗口的OnInitMenu(CMenu *pMenu)中调用该函数(如:m_menu.InitMenu(pMenu,IDR_MAINFRAME,&m_wndToolBar)),当然主窗口必须要有工具栏,才会产生菜单项位图。最后还必须重载主窗口的OnMeasureItem()和OnDrawItem()。并在两个函数中分别 调用菜单类的另外两个接口DrawItem()和MeasureItem();
2、void SetHighLightColor(COLORREF crColor); 3、void SetBackColor(COLORREF); 4、void SetTextColor(COLORREF); 以上三个接口应该不用再说明 。
QQ的菜单采用的颜色是 SELECTTEXT_COLOR RGB(0,0,127) TEXT_COLOR RGB(0,0,0) BK_COLOR RGB(143,167,207) 效果不错
5、void SetImageLeft(UINT idBmpLeft) 说明:这也是个重要的接口。要实现纵向位图(像“开始”菜单)。可以在适当的地方调用该函数。参数当然是位图的ID值。注意选择位图时要选好看一点的哦!要记得高宽的比例。 6、void InitPopupMenu(CMenu *pMenu,UINT uToolBar,CToolBar *pToolBar) 与InitMenu不同的是:InitMenu并不修改第一级菜单为自绘风格,而 该函数有包括第一级菜单. 但必须注意:该类的任一实例都只能调用这两个函数中的一个,不能一同使用!!!
二、CMenuEx类的具体使用步骤:2.1 用CMenuEx类加载主窗口菜单的具体步骤:
1、添加CMenuEx成员变量
CMenuEx m_menu
2、CMainFrame类添加WM_INITMENU消息,并在实现函数中加入代码:
m_menu.InitMenu(pMenu,IDR_MAINFRAME,&m_wndToolBar);
IDR_MAINFRAME是工具栏资源ID,m_wndToolBar是工具栏对象 3、CMainFrame类添加WM_DRAWITEM和WM_MEASUREITEM消息,并在实现函数分加入:
if(!nIDCtl) m_menu.DrawItem(lpDrawItemStruct);if(!nIDCtl) m_menu.MeasureItem(lpMeasureItemStruct);
只须这三步就大功造成了! 但是有时你会发现菜单的位图错位了,这不是程序的错。 这是因为“工具栏位图”的个数与“工具栏按钮个数”不符, 你只需把不用的位图删掉就可以,或者添加对应的菜单项,反正使这两个数目一致就对了
4、如果你想要让你的菜单拥有“纵向位图”(像“开始”菜单),就必须在CMainFrame在OnCreate()中加入:
m_menu.SetImageLeft(IDB_BITMAP1);//IDB_BITMAP1是指定位图
2.2 用CMenuEx类加载弹出菜单的具体步骤:
1、在CChildView类中加入成员变量 CMenuEx m_menu和CToolBar *m_pToolBar 为什么要加入m_pToolBar,这里做一下说明: 因为在CMenuEx的接口函数InitPopupMenu()中需要工具栏对象指针,而工具栏对象又偏偏是CMainFrame类的成员 所以设此指针变量指向工具栏对象
2、给CChildView类的成员m_pToolBar赋值。 当CView派生类由不得CxxxxDocTemplate产生时,赋值的地方比较特殊 请在CxxxWinApp类中的InitInstance()的最后加入代码:
CMainFrame *pFrame=(CMainFrame *)m_pMainWnd; CChildView *pView=(CChildView *)pFrame->GetActiveView(); pView->m_pToolBar=&(pFrame->m_wndToolBar);//注意:原m_wndToolBar是私有变量,要改成public的哦!
当CView派生类是CMainFrame类的成员变量时,就很简单,直接在OnCreate()中设置
3、在CChildView的构造函数中加入设置弹出菜单的代码
m_menu.CreatePopupMenu(); m_menu.AppendMenu(0,ID_EDIT_UNDO,"撤消"); m_menu.AppendMenu(MF_SEPARATOR,0); m_menu.AppendMenu(0,ID_EDIT_COPY,"复制"); m_menu.AppendMenu(0,ID_EDIT_CUT,"剪切"); m_menu.AppendMenu(0,ID_EDIT_PASTE,"粘贴");//当菜单ID与工具栏按钮ID一样时就会显示位图
4、在CChildView的析构函数中加入代码:
m_menu.DestroyMenu(); //释放资源
5、在CChildView类中加入WM_INITMENUPOPUP消息,并在其实现函数中加入代码:
if(!bSysMenu) m_menu.InitPopupMenu(pPopupMenu,IDR_MAINFRAME,m_pToolBar);
6、在CChildView类中加入WM_CONTEXTMENU消息,并在其实现函数中加入弹出菜单的代码:
m_menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,point.x,point.y,this);
好了!介绍完了。 大家可以在适当的地方(比如CMainFrame:OnCreate)中加入:
m_menu.SetHighLightColor(RGB(0,0,127));m_menu.SetTextColor(RGB(0,0,0));m_menu.SetBackColor(RGB(143,167,207));
来设置菜单颜色,怎么样:) 欢迎大家多提建议
|
-- 作者:admin -- 发布时间:2005-10-17 17:49:00
--
完美实现真彩自绘菜单
作者:阿福(geforce_zf)
下载源代码一、提出问题
在VCKBASE上读到《自绘菜单的实现》[作者:querw]。应用的我自己的正在进行的工程后发现效果不错,可是有存在许多问题。整个类的设计方面存在很多缺陷(先天,后天的),存在的主要问题如下:
- 当应用在多文档界面(MDI)中的时候,无法对系统自动添加菜单和文档模板菜单进行自绘(比如无法对文件->最近文件(MRU)菜单项中的文件列表就是系统自动添加)。原因是类内部没有对CMainFrame::OnInitPopupMenu()消息进行处理的函数, 因此不具备修改系统自动添加菜单项的功能。(BCMENU有这功能,而且工作的不错)
- 作者提到的 BCMENU 不用映射 WM_DRAWITEM 和 WM_MEASUREITEM 两个消息就能实现自画功能,实际上是错误的。不映射这两个重要的消息,即使能自绘,也是有问题的,不信看图。
菜单编辑器中的模菜单样
使用BCMENU并且映射了这两个消息后的执行情况

使用BCMENU没有映射两个消息的执行情况

原作者分析的自绘的是因为把主菜单(top-level menu)的子菜单都加载成弹出菜单(popupmenu),是不正确的。真正的原因是因为MFC框架会自动调用CMenu的两个虚拟函数MeasureItem()和OnDrawItem()。 因此,当CMenuEx派生于CMenu,并且重写这两个虚拟函数以后。
1、MFC框架调用的GetMenu()->MeasureItem()就相当于调用了CMenuEx::MeasureItem(),从而实现自绘菜单控件尺寸的测量。 2、MFC框架调用GetMenu()->DrawItem()就相当于调用了CMenuEx::DrawItem()来实现自绘菜单控件的自绘操作(不懂??,这正是C++的虚拟的妙用,指向派生类对象的基类指针可以调用派生类的虚拟函数,多么伟大的发明,谁想出来的???)。与子菜单是否为弹出菜单(popupmenu)没有什么关系。以下是摘自WINCORE.CPP的一段程序,也就是WM_MEASUREITEM消息的默认流向的地方,相信大家会从中看出一些端倪。
void CWnd::OnMeasureItem(int /*nIDCtl*/, LPMEASUREITEMSTRUCT lpMeasureItemStruct){ if (lpMeasureItemStruct->CtlType == ODT_MENU) { ...... // 如果没有主菜单 if (pThreadState->m_hTrackingWindow == m_hWnd) { ...... } else { // 如果有主菜单 pMenu = GetMenu(); // 找到窗体的主菜单,注意,pMenu的是CMenu* 类型 } // 在当前菜单中寻找ID匹配的菜单项 pMenu = _AfxFindPopupMenuFromID(pMenu, lpMeasureItemStruct->itemID); if (pMenu != NULL) // 如果找到,就调用MeasureItem() // 这就是所谓的基类指针指向派生类对象,可以调用派生类虚拟函数的情况了 pMenu->MeasureItem(lpMeasureItemStruct); else TRACE1("Warning: unknown WM_MEASUREITEM for menu item 0x%04X.\\n", lpMeasureItemStruct->itemID); } else { ...... } ......}
- 当菜单项中含有子菜单(submenu),而不含有分割条的时候,子菜单项的高度不可调。原因为原CMenuEx程序中将分割条的原COMMAND ID(0)改为菜单项的COMMADN ID(-1), 以欺骗MFC框架调用CMenuEx::MeasureItem()来计算子菜单项(submenu)的高度。(很令我失望,这也是促使我自己动手重写该类的原因之一。不信看程序,看图)
摘录自原CMenuEx.cpp第546-560行
if(uID == 0) //分隔符{ ::AppendMenu(hNewMenu,MF_SEPARATOR,0,NULL); ...... // 注意,就是下面那个-1,把分割条的ID从0改到-1, // 从而是MFC框架误以为找到了ID为-1的菜单项,并且测量了它的尺寸 // 而实际上ID为-1的菜单项是不可能被void CWnd::OnMeasureItem()找到的 ::ModifyMenu(hNewMenu,i,MF_BYPOSITION | MF_OWNERDRAW,-1,(LPCTSTR)pMenuItem);}
菜单编辑器中没有分割条菜单的菜单

原CMenuEx执行的模样

菜单编辑器中有分割条菜单的菜单

原CMenuEx执行的模样
-
代码不够简练,程序粒度划分不好,可读性差(不过比BCMENU的代码可读性强多了:))。
二、解决问题
针对以上遇到的问题,我参考BCMENU和原作者的CMenuEx,对CMenuEx类重新进行了组织,类定义如下:
// 声明,因为下面的结构要用到 CMenuEx*,又不支持向后引用,又什么办法啊!class CMenuEx;//自绘菜单数据项结构,就是要传给系统的那个牛X的LPCTSTR指针所指向的东东class CMenuEx : public CMenu{ DECLARE_DYNAMIC( CMenuEx ) // Constructorpublic: CMenuEx(); virtual ~CMenuEx(); virtual BOOL DestroyMenu(); // Operationpublic: // 加载菜单操作 BOOL LoadMenu(UINT nIDResource); BOOL LoadMenu(LPCTSTR lpszResourceName); BOOL LoadMenu(HMENU hMenu); BOOL LoadMenu(CMenu & Menu); // 菜单项操作,如果当前菜单为主菜单(top-level)就调用相应的CMenu的操作。如果是弹出菜单, // 就将新加入的菜单项定义为自绘菜单 BOOL AppendMenu(UINT nFlags, UINT nIDNewItem = 0,LPCTSTR lpszNewItem = NULL); BOOL InsertMenu(UINT nPosition,UINT nFlags,UINT nIDNewItem=0,LPCTSTR lpszNewItem=NULL ); BOOL ModifyMenu(UINT nPosition,UINT nFlags,UINT nIDNewItem=0,LPCTSTR lpszNewItem=NULL ); BOOL RemoveMenu(UINT nPosition, UINT nFlags); // 加载菜单图像操作 //通过菜单索引表加载图像索引,此操作必须在设置过菜单图像后调用 void SetImageIndex(const UINT* nIDResource,UINT nIDCount); void LoadToolBar(const CToolBar* pToolBar);// 通过工具栏加载图像,和图像索引 // 取自绘菜单项的数据项 UINT GetMenuItemSize() const; LPMENUITEM GetMenuItem(UINT nPosition); // 取子菜单操作,如果位置nPosition存在子菜单,返回该子菜单指针 // 如果不存在子菜单,返回NULL CMenuEx* GetSubMenu(int nPosition); // 在当前菜单和所以子菜单中中寻找相应ID // 如果找到,返回ID所在菜单的指针,没找到返回NULL CMenuEx* FindPopupMenuFromID(UINT nID); // Attributesprotected: // 指示为主菜单(top-level menu or menubar)还是弹出菜单(popupmenu) BOOL m_bPopupMenu; // 分割条的默认高度 int m_nSeparator; // 绘制菜单需要的颜色 COLORREF m_crBackground; // 菜单背景色 COLORREF m_crTextSelected; // 菜单项被选中时的文字颜色 COLORREF m_crText; // 菜单项文字颜色 COLORREF m_crLeft; // 菜单左侧的背景颜色 COLORREF m_crSelectedBroder; // 菜单选中框的线条颜色 COLORREF m_crSelectedFill; // 菜单选中框的填充颜色 // 菜单项图像的尺寸 CSize m_szImage; CImageList* m_pImageList; // 菜单项正常的图像列表 CImageList* m_pDisabledImageList; // 菜单项禁用时的图像列表 CImageList* m_pHotImageList; // 菜单项被选中时的图像列表 protected: // 包含所有菜单项的数组 CArray m_MenuItemArr; public: // 设置颜色操作 void SetTextSelectedColor(COLORREF color); void SetBackgroundColor(COLORREF color); void SetTextColor(COLORREF color); void SetLeftColor(COLORREF color); void SetSelectedBroderColor(COLORREF color); void SetSelectedFillColor(COLORREF color); // 设置图像列表操作 void SetImageList(CImageList* pImageList); void SetDisabledImageList(CImageList* pImageList); void SetHotImageList(CImageList* pImageList); // 设置当前菜单为主菜单还是弹出菜单 void SetPopupMenu(BOOL bPopupMenu); // Implementationpublic: // 绘制菜单项的虚拟函数,由MFC框架自动调用 virtual void DrawItem(LPDRAWITEMSTRUCT lpDIS); // 更新弹出菜单菜单项操作 // 因为有时候系统会通过菜单句柄插入一些非自绘菜单 // 该函数就是更新这些非自绘菜单为自绘菜单 void UpdatePopupMenu(); protected: // 绘制菜单项的辅助函数,想自己的菜单看上去更COOL,就拿他们开刀 void DrawBackground(CDC* pDC,CRect rect); void DrawMenuImage(CDC* pDC,CRect rect,LPDRAWITEMSTRUCT lpDIS); void DrawMenuText(CDC* pDC,CRect rect,LPDRAWITEMSTRUCT lpDIS); void DrawSelected(CDC* pDC,CRect rect,LPDRAWITEMSTRUCT lpDIS); // Static Memberpublic: // 在CMainFrame的OnMeasureItem()消息映射函数中调用它,用来测量所有菜单项尺寸 static void MeasureItem(LPMEASUREITEMSTRUCT lpMIS); // 在CMainFrame的OnInitPopupMenu()消息映射函数中调用它, // 用来更新系统自动添加的菜单项为自绘菜单 static void InitPopupMenu(CMenu* pPopupMenu,UINT nIndex,BOOL bSystem); };#endif // !defined(MENUEX_H)
三、实现方法
有了以上的强有力的武器,就可以对我们的程序下手了:)在MDI或SDI中使用CMenuEx的时候需要修改以下地方。
- 先将MenuEx.h和MenuEx.cpp添加到工程中,在CMainFrame中添加头文件,CMenuEx对象,用于存储菜单图像的CImageList对象和初始化菜单程序。
#include "MenuEx.h" // 添加头文件class CMainFrame : public CMDIFrameWnd{ ...public: HMENU InitMainFrameMenu(); // 初始化主菜单 HMENU InitImageTypeMenu(); // 初始化文档模板菜单 protected: // CMenuEx members CMenuEx m_menuMainFrame; // 主窗体没有打开任何文档时菜单 CMenuEx m_menuImageType; // 主窗体打开文档时菜单(文档模板菜单) protected: // CMenuEx\'\'s image list members CImageList m_imageMenu; // 菜单项正常的图像列表 CImageList m_imageMenuDisable; // 菜单项禁用时的图像列表 CImageList m_imageMenuHot; // 菜单项被选中时的图像列表 ...}
- 撰写菜单图像索引表,初始化菜单程序,初始化菜单图像列表程序, 和两个重要的消息映射函数CMainFrame::OnMeasureItem()和CMainFrame::OnInitPopupMenu()。 (什么?不会添加!,找Clazard帮忙或许有点帮助了:))
// 声明,因为下面的结构要用到 CMenuEx*,又不支持向后引用,又什么办法啊!class CMenuEx;//自绘菜单数据项结构,就是要传给系统的那个牛X的LPCTSTR指针所指向的东东typedef struct tagMENUITEM{ CString strText; // 菜单名称 UINT nID; // 菜单ID号 // 分割条的ID是 0 // 子菜单的ID是 -1 CSize itemSize; // 菜单项的尺寸,不包括菜单图像的尺寸 CImageList* pImageList; // 菜单项的正常图像列表 CImageList* pDisabledImageList; // 菜单项的禁用图像列表 CImageList* pHotImageList; // 菜单项的选中图像列表 UINT nImageIndex; // 菜单项的图像列表索引,-1表示没有图像 BOOL bIsSubMenu; // 表示当前菜单项是否为子菜单项 CMenuEx* pSubMenu; // 如果是一般菜单,该值为NULL // 如果bIsSubMenu为TRUE,该值为指向子菜单项的CMenuEx*指针 } MENUITEM,*LPMENUITEM;///////////////////////////////////////////// 在ManiFram.cpp 中添加菜单图像索引表static UINT nMenuImageIndex[] ={ ID_FILE_OPEN, ID_FILE_SAVE, ID_FILE_PRINT, ID_EDIT_COPY, ID_EDIT_PASTE, ID_EDIT_UNDO, ID_EDIT_REDO, ID_APP_ABOUT, ID_IMAGE_LEVEL, ID_IMAGE_EQUALIZE, ID_IMAGE_SMOOTH, ID_IMAGE_SHARP, ID_IMAGE_SIZE, ID_IMAGE_RA, ID_IMAGE_HISTOGRAM, ID_ZOOMOUT, ID_ZOOMIN,};/////////////////////////////////////////////////////////////////////////////// 在ManiFram.cpp 中添加初始化菜单程序void CMainFrame::InitMenuImage(){ // 初始化菜单图像列表 CBitmap bm; m_imageMenu.Create(20, 20, TRUE | ILC_COLOR24, 9, 0); // 要问我IDB_SMALLMENUCOLOR是什么,当然是是真彩位图了,看图说话了 bm.LoadBitmap(IDB_SMALLMENUCOLOR); m_imageMenu.Add(&bm,(CBitmap*)NULL); bm.Detach(); // 还有IDB_SMALLMENUDISABLE m_imageMenuDisable.Create(20, 20, TRUE | ILC_COLOR24, 9, 0); bm.LoadBitmap(IDB_SMALLMENUDISABLE); m_imageMenuDisable.Add(&bm,(CBitmap*)NULL); bm.Detach(); // 还有IDB_SMALLMENUHOT m_imageMenuHot.Create(20, 20, TRUE | ILC_COLOR24, 9, 0); bm.LoadBitmap(IDB_SMALLMENUHOT); m_imageMenuHot.Add(&bm,(CBitmap*)NULL); bm.Detach(); }/*IDB_SMALLMENUCOLOR IDB_SMALLMENUHOT IDB_SMALLMENUDISABLE
当然,要通过资源编辑器的Import功能将他们导入到资源文件中,不过因为是真彩,所以不能用VC的图片编辑器编辑了。 告诉大家个敲门,我是用windows自带的画笔画的:)
*//////////////////////////////////////////////////////////////////////////////// 在ManiFram.cpp 中添加初始化菜单图像列表程序int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct){ // 在CMainFrame::OnCreate中调用菜单图标初始化程序 。。。。。。 InitMenuImage(); 。。。。。。}/////////////////////////////////////////////////////////////////////////////HMENU CMainFrame::InitMainFrameMenu(){ //初始化主菜单 m_menuMainFrame.LoadMenu(IDR_MAINFRAME); { // 这只加载图像的一种方法,是一种两步方法,先加载图像列表 m_menuMainFrame.SetImageList(&m_imageMenu); m_menuMainFrame.SetDisabledImageList(&m_imageMenuDisable); m_menuMainFrame.SetHotImageList(&m_imageMenuHot); // 再通过菜单图像索引表为菜单加载图像索引, m_menuMainFrame.SetImageIndex(nMenuImageIndex, sizeof(nMenuImageIndex)/sizeof(UINT)); } // 也可以使用另外一种一步方法加载图像 /* // 假设MAINFRAM具有m_wndToolBar成员,并且已经设置了真彩位图 // 关于设置工具栏的真彩位图,请参考 http://www.vckbase.com/document/viewdoc/?id=576 // 或者看我的另外一篇文章 《完美实现真彩工具栏》(还没写出来那:)) // 不过源程序里面已经有实现方法了 // 自己看也可以明白的 m_menuMainFrame.LoadToolBar(&m_wndToolBar); */ return m_menuMainFrame.Detach();}/////////////////////////////////////////////////////////////////////////////HMENU CMainFrame::InitImageTypeMenu(){ // 初始化文档模板菜单 m_menuImageType.LoadMenu(IDR_IMAGETYPE); m_menuImageType.SetImageList(&m_imageMenu); m_menuImageType.SetDisabledImageList(&m_imageMenuDisable); m_menuImageType.SetHotImageList(&m_imageMenuHot); //通过菜单图像索引表为菜单加载图像索引 m_menuImageType.SetImageIndex(nMenuImageIndex,sizeof(nMenuImageIndex)/sizeof(UINT)); return m_menuImageType.Detach();}/////////////////////////////////////////////////////////////////////////////void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) { // 记住,顺序一定不能反,因为有些MFC自动添加的菜单是在CMDIFrameWnd::OnInitMenuPopup() // 中添加的. // 如果反了,当然就找不到新加入的菜单了 CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu); // 静态函数,看好了,别忘了写CMenuEx啊 CMenuEx::InitPopupMenu(pPopupMenu, nIndex, bSysMenu); }/////////////////////////////////////////////////////////////////////////////void CMainFrame::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct) { // 都是她惹的祸"CMDIFrameWnd::OnMeasureItem()",不对子菜单项的尺寸进行测量 // 害的我们只好映射这个函数了 CMDIFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct); // 静态函数,看好了,别忘了写CMenuEx啊 CMenuEx::MeasureItem(lpMeasureItemStruct);}
- 在CXXXApp::InitInstance()中添加代码,XXX代表你自己的程序了
BOOL CXXXApp::InitInstance(){ ...... CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_IMAGETYPE, RUNTIME_CLASS(CImageDoc), RUNTIME_CLASS(CChildFrame), // custom MDI child frame RUNTIME_CLASS(CImageView)); AddDocTemplate(pDocTemplate); // create main MDI Frame window CMainFrame* pMainFrame = new CMainFrame; if (!pMainFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; m_pMainWnd = pMainFrame; // 这些才是要添加的代码,别弄错了 // 初始化文档模板菜单 pDocTemplate->m_hMenuShared=pMainFrame->InitImageTypeMenu(); // 初始化主窗体菜单 pMainFrame->m_hMenuDefault=pMainFrame->InitMainFrameMenu(); // 更新,具体干什么没研究,反正不调用就出错了:) pMainFrame->OnUpdateFrameMenu(pMainFrame->m_hMenuDefault); // 要添加的代码到这结束 ......}
三、总结
说了这么多,也不知道大家看明白没有,没关系,先贴个图,大家看看效果再说了。
效果图一,使用图像索引表加载的小图标菜单

效果图一,工具条加载的大图标菜单
四、结束语
感谢querw和BCMenu的作者,没有他们的辛勤劳动,后人是没办法站在他们肩膀上的!由于程序写的匆忙,难免有不尽人意和错误的地方,欢迎大家任意修改源程序:) 要说这个菜单做的完美,那是吹牛,世界上哪有完美的东西啊 :) 只要自己觉得完美,就够了。 希望大家能从文章中学到点东西,就好。
|