|
|
6、CMenuEx与接口类的通信
看到这里,我们已经知道绘制菜单阴影和菜单项特效所需要的条件了,就是相应菜单窗口和菜单所在窗口的几个简单而必要的消息就行了。文章开头我讲过:绘制菜单项和阴影是与CMenuEx实现相分离的,在今后的使用过程中,我们只需要编写菜单阴影和菜单项特效的代码就行了,而不需要去修改CMenuEx,从某种意义上说,这能够保证代码的完整性、可重用性。但是,怎样将我们编写的实现菜单阴影和菜单项的代码与CMenuEx类关联起来呢?这确实是个难题,至少我为这个问题曾经思考了3天时间,当然这和我个人的水平有关,或许,它的实现方法有很多种,你也有比下面提到的方法更简洁和有效的途径,请告诉我!下面的这种实现方法源于在学习菜单特效过程中所看到的一个示例工程《CMenuXP - Office XP风格菜单和控件》,再次我向作者表示感谢。这种方法其实也比较简单,仅仅定义了几个MACRO就行了,鉴于代码相对简单,在此就不再解释了:
#define CMenuEx_Default_NcCalcSize(baseClass) BOOL baseClass::OnNcCalcsize ( HWND hWnd, NCCALCSIZE_PARAMS* lpncsp ) {return FALSE;}#define CMenuEx_Default_NcDestroy(baseClass) BOOL baseClass::OnNcDestroy ( HWND hWnd ) {return FALSE;}#define CMenuEx_Default_ShowWindow(baseClass) BOOL baseClass::OnShowWindow ( HWND hWnd, BOOL bShow ) {return FALSE;}#define CMenuEx_Default_WndPosChanging(baseClass) BOOL baseClass::OnWindowPosChanging ( HWND hWnd, WINDOWPOS* pWindowPos ) {return FALSE;}#define CMenuEx_Default_Print(baseClass) BOOL baseClass::OnPrint ( HWND hWnd, CDC* pDC ) {return FALSE;}#define CMenuEx_Default_InitMenuPopup(baseClass) BOOL baseClass::OnInitMenuPopup ( HWND hWnd, HMENU hMenu, UINT nIndex, BOOL bSysMenu ){return FALSE;}#define CMenuEx_Default_MeasureItem(baseClass) BOOL baseClass::OnMeasureItem ( HWND hWnd, LPMEASUREITEMSTRUCT lpMIS ) {return FALSE;}#define CMenuEx_Default_DrawItem(baseClass) BOOL baseClass::OnDrawItem ( HWND hWnd, LPDRAWITEMSTRUCT lpDIS ) {return FALSE;}#define CMenuEx_Default_MenuChar(baseClass) LRESULT baseClass::OnMenuChar ( HWND hWnd, HMENU hMenu, UINT nChar, UINT nFlags ) {return 0;}#define CMenuEx_Default_NcPaint(baseClass) BOOL baseClass::OnNcPaint ( HWND hWnd ) { CWindowDC dc(CWnd::FromHandle(hWnd)); return (baseClass::OnPrint(hWnd,&dc)); };#define CMenuEx_Implement_NcCalcSize(theClass, baseClass) BOOL baseClass::OnNcCalcsize ( HWND hWnd, NCCALCSIZE_PARAMS* lpncsp ) {return (theClass::OnNcCalcsize( hWnd,lpncsp )); }#define CMenuEx_Implement_NcDestroy(theClass, baseClass) BOOL baseClass::OnNcDestroy ( HWND hWnd ) {return (theClass::OnNcDestroy(hWnd)); }#define CMenuEx_Implement_ShowWindow(theClass, baseClass) BOOL baseClass::OnShowWindow ( HWND hWnd, BOOL bShow ) {return (theClass::OnShowWindow(hWnd,bShow)); }#define CMenuEx_Implement_WndPosChanging( theClass, baseClass) BOOL baseClass::OnWindowPosChanging ( HWND hWnd, WINDOWPOS* pWindowPos ) {return (theClass::OnWindowPosChanging(hWnd,pWindowPos)); }#define CMenuEx_Implement_Print(theClass, baseClass) BOOL baseClass::OnPrint ( HWND hWnd, CDC* pDC ) {return (theClass::OnPrint(hWnd,pDC)); }#define CMenuEx_Implement_InitMenuPopup( theClass, baseClass) BOOL baseClass::OnInitMenuPopup ( HWND hWnd, HMENU hMenu, UINT nIndex, BOOL bSysMenu ){return (theClass::OnInitMenuPopup(hWnd, hMenu, nIndex, bSysMenu )); }#define CMenuEx_Implement_MeasureItem(theClass, baseClass) BOOL baseClass::OnMeasureItem ( HWND hWnd, LPMEASUREITEMSTRUCT lpMIS ) {return (theClass::OnMeasureItem(hWnd, lpMIS )); }#define CMenuEx_Implement_DrawItem( theClass, baseClass) BOOL baseClass::OnDrawItem ( HWND hWnd, LPDRAWITEMSTRUCT lpDIS ) {return (theClass::OnDrawItem(hWnd, lpDIS )); }#define CMenuEx_Implement_MenuChar( theClass, baseClass) LRESULT baseClass::OnMenuChar ( HWND hWnd, HMENU hMenu, UINT nChar, UINT nFlags ) {return (theClass::OnMenuChar(hWnd, hMenu, nChar, nFlags )); }#define CMenuEx_Implement_NcPaint(theClass, baseClass) BOOL baseClass::OnNcPaint ( HWND hWnd ) { CWindowDC dc(CWnd::FromHandle(hWnd)); return (theClass::OnPrint(hWnd,&dc)); }#define CMenuEx_Default_Shadow(baseClass) CMenuEx_Default_Print(baseClass) CMenuEx_Default_NcPaint(baseClass) CMenuEx_Default_NcDestroy(baseClass) CMenuEx_Default_NcCalcSize(baseClass) CMenuEx_Default_ShowWindow(baseClass) CMenuEx_Default_WndPosChanging(baseClass)#define CMenuEx_Default_Item(baseClass) CMenuEx_Default_InitMenuPopup(baseClass) CMenuEx_Default_MeasureItem(baseClass) CMenuEx_Default_DrawItem(baseClass) CMenuEx_Default_MenuChar(baseClass)#define CMenuEx_Implement_Shadow(theClass, baseClass) CMenuEx_Implement_Print(theClass, baseClass) CMenuEx_Implement_NcPaint(theClass, baseClass) CMenuEx_Implement_NcDestroy(theClass, baseClass) CMenuEx_Implement_NcCalcSize(theClass, baseClass) CMenuEx_Implement_ShowWindow(theClass, baseClass) CMenuEx_Implement_WndPosChanging(theClass, baseClass)#define CMenuEx_Implement_Item(theClass, baseClass) CMenuEx_Implement_InitMenuPopup(theClass, baseClass) CMenuEx_Implement_MeasureItem(theClass, baseClass) CMenuEx_Implement_DrawItem(theClass, baseClass) CMenuEx_Implement_MenuChar(theClass, baseClass) #define CMenuEx_Default(baseClass) CMenuEx_Default_Shadow(baseClass) CMenuEx_Default_Item(baseClass) #define CMenuEx_Implement(theClass, baseClass) CMenuEx_Implement_Shadow(theClass, baseClass) CMenuEx_Implement_Item(theClass, baseClass)
使用发发亦很简单,当你重载了CMenuEx的那几个虚函数以后,使用CMenuEx_Implement(theClass, CMenuEx)就可以将你的theClass类映射到CMenuEx类里面去,注意你重载的函数名与参数必须与CMenuEx中所定义的一致。
到此为止,我已经将实现菜单阴影和菜单项的方法讲解清楚了,剩余的就是编写具体实现阴影和特效的代码了。
四、实现菜单阴影
为了实现菜单阴影,假设我们编写的类名称为CMenuShadow。在该类中实现菜单阴影的原则是:在响应OnWindowPosChanging()和OnNcCalcSize()时调整菜单的尺寸和位置,并将菜单后面的图像保存起来,然后再OnNcPaint()和OnPrint()时绘制菜单边框,并在前面保存的图像上绘制阴影效果。
Class CMenuShaodw{public: static int m_iShadow; //定义了阴影的宽度,本例中,阴影宽度可为2px或3px宽 static BOOL OnNcCalcsize ( HWND hWnd, NCCALCSIZE_PARAMS* lpncsp ); //响应WM_NCCALCSIZE消息 static BOOL OnPrint ( HWND hWnd, CDC* pDC ); //响应WM_PRINT消息 static BOOL OnNcPaint ( HWND hWnd ); //响应WM_NCPAINT消息 static BOOL OnWindowPosChanging ( HWND hWnd, WINDOWPOS* pWindowPos ); //响应WM_WINDOWPOSCHANGING消息 static BOOL OnShowWindow ( HWND hWnd, BOOL bShow ); //响应WM_SHOWWINDOW消息 static BOOL OnNcDestroy ( HWND hWnd ); //响应WM_NCDESTROY消息 static BOOL IsShadowEnabled ( ); //判断系统是否已经开启了阴影效果 static HBITMAP GetScreenBitmap ( LPCRECT pRect ); //拷贝菜单后面的图像 static void DrawShadow ( CDC *pDC, CRect rect, COLORREF crBorder);//绘制菜单阴影 static COLORREF GetGradientColor( COLORREF crStart, COLORREF crEnd, float percent); //供DrawShadow调用 static COLORREF m_crMenuBorderOuter; //菜单外边框的颜色,阴影的颜色将与外边框颜色相匹配 static COLORREF m_crMenuBorderInner; //菜单内边框的颜色 static COLORREF m_crMenuBorderBkgnd; //菜单内、外边框之间的颜色} int CMenuShadow::m_iShadow = 2; //可为3,效果不一样 COLORREF CMenuShadow::m_crMenuBorderOuter = RGB(255,0,0); //红色,直接影响了阴影的颜色 COLORREF CMenuShadow::m_crMenuBorderInner = ::GetSysColor(COLOR_MENU); COLORREF CMenuShadow::m_crMenuBorderBkgnd = ::GetSysColor(COLOR_MENU);BOOL CMenuShadow::IsShadowEnabled() //判断系统是否已经开启了阴影效果了?既然已经开启了,我们还忙活什么呢?{ BOOL bEnabled = FALSE; if (SystemParametersInfo(0x1024, 0, &bEnabled, 0)) //#define SPI_GETDROPSHADOW 0x1024,defined in vc7.0 return bEnabled; return FALSE;}HBITMAP CMenuShadow::GetScreenBitmap(LPCRECT pRect) //在菜单窗口显示前,现保存菜单窗口后面面图像,以便绘制透明效果{ HDC hDC; HDC hMemDC; HBITMAP hNewBitmap = NULL; if ((hDC = ::GetDC(NULL)) != NULL ) { if ((hMemDC = ::CreateCompatibleDC(hDC)) != NULL) { if ((hNewBitmap = ::CreateCompatibleBitmap(hDC, pRect->right - pRect->left, pRect->bottom - pRect->top)) != NULL) { HBITMAP hOldBitmap = (HBITMAP)::SelectObject(hMemDC, hNewBitmap); ::BitBlt(hMemDC, 0, 0, pRect->right - pRect->left, pRect->bottom - pRect->top, hDC, pRect->left, pRect->top, SRCCOPY); ::SelectObject(hMemDC, (HGDIOBJ)hOldBitmap); } : eleteDC(hMemDC); } ::ReleaseDC(NULL, hDC); } return hNewBitmap;}BOOL CMenuShadow::OnWindowPosChanging( HWND hWnd, WINDOWPOS* pWindowPos ){ LPWINDOWPOS pWndPos = (LPWINDOWPOS)lParam; if (!IsShadowEnabled()) { pWndPos->cx += m_iShadow; //如果需要绘制菜单阴影的话,那先把菜单窗口加宽加高以不影响既有菜单显示效果 pWndPos->cy += m_iShadow; } //将菜单窗口后面的图像复制过来保存到菜单窗口中去,绘制阴影的时候才会有透明效果嘛 if (!IsWindowVisible(hWnd) && !IsShadowEnabled()) { CBitmap* bmpBack = (CBitmap*)GetProp(hWnd, strMenuWndBmp); if( bmpBack != NULL) if (bmpBack->m_hObject != NULL ) bmpBack->DeleteObject(); bmpBack->Attach(GetScreenBitmap(CRect(pWndPos->x, pWndPos->y, pWndPos->cx, pWndPos->cy))); SetProp(hWnd, strMenuWndBmp, bmpBack); } return TRUE;}BOOL CMenuShadow::OnNcCalcsize ( HWND hWnd, NCCALCSIZE_PARAMS* lpncsp ){ if (IsShadowEnabled()) return FALSE; ((NCCALCSIZE_PARAMS*)lParam)->rgrc[0].right -= m_iShadow; //同样,调整菜单窗口非客户区域的尺寸 ((NCCALCSIZE_PARAMS*)lParam)->rgrc[0].bottom -= m_iShadow; return TRUE;}BOOL CMenuShadow::OnNcPaint ( HWND hWnd ){ CWindowDC dc(CWnd::FromHandle(hWnd)); return OnPrint(hWnd,&dc);}BOOL CMenuShadow::OnPrint ( HWND hWnd, CDC* pDC ){ CRect rc; GetWindowRect(m_hWnd, &rc); rc.OffsetRect(-rc.TopLeft()); if (!IsShadowEnabled()) //将菜单背后的图像拷贝出来,在绘制菜单阴影, { CDC cMemDC; cMemDC.CreateCompatibleDC (pDC); CBitmap* bmpBack = (CBitmap*)GetProp(hWnd, strMenuWndBmp); HGDIOBJ hOldBitmap = ::SelectObject (cMemDC.m_hDC, bmpBack); pDC->BitBlt (0, rc.bottom - 4, rc.Width() - 4, 4, &cMemDC, 0, rc.bottom - 4, SRCCOPY); pDC->BitBlt (rc.right - 4, 0, 4, rc.Height(), &cMemDC, rc.right - 4, 0, SRCCOPY); //::SelectObject(cMemDC.m_hDC, hOldBitmap); DrawShadow(pDC, rc, m_crMenuBorderOuter); //绘制菜单阴影 rc.right -= m_iShadow; rc.bottom -= m_iShadow; } pDC->Draw3dRect(rc,m_crMenuBorderOuter, m_crMenuBorderOuter); //菜单的最外边的边框 rc.DeflateRect (1, 1); pDC->Draw3dRect(rc,m_crMenuBorderInner, m_crMenuBorderInner); //菜单外边框与菜单项之间2px的颜色 rc.DeflateRect (1, 1); pDC->Draw3dRect(rc,m_crMenuBorderBkgnd, m_crMenuBorderBkgnd); //菜单外边框与菜单项之间2px的颜色 return TRUE;}void CMenuShadow::DrawShadow (CDC *pDC, CRect rect, COLORREF crBorder){ int i, j, x, y; float percent = (float)0.40; //最终值0.90时效果最好,试验比较得出 for (j = 0; j < m_iShadow; j++) //m_iShadow:阴影宽度为3px { for (i = 4; i <= rect.bottom-m_iShadow; i++) // 右边的阴影 { x = rect.right - (m_iShadow - j); y = (m_iShadow==MENUSHADOW_THICK)?(i+j-1) i+j); float per = (float)((m_iShadow==MENUSHADOW_THICK)?(percent+j*0.25):0.9); pDC->SetPixel( x, y, GetGradientColor( crBorder, pDC->GetPixel(x, y), per )); } for (i = 4; i <= rect.right-m_iShadow; i++) // 底部的阴影 { x = i + j; y = rect.bottom - (m_iShadow - j); float per = (float)((m_iShadow==MENUSHADOW_THICK)?(percent+j*0.25):0.9); pDC->SetPixel( x, y, GetGradientColor( crBorder, pDC->GetPixel(x, y), per )); } }}COLORREF CMenuShadow::GetGradientColor( COLORREF crStart, COLORREF crEnd, float percent ){ int rStart = GetRValue( crStart ); //Red color1 int gStart = GetGValue( crStart ); //Green color1 int bStart = GetBValue( crStart ); //blue color1 int rEnd = GetRValue( crEnd ); //red color2 int gEnd = GetGValue( crEnd ); //green color2 int bEnd = GetBValue( crEnd ); //blue color2 int dr = (int) ( (float) (rEnd - rStart) * percent ); int dg = (int) ( (float) (gEnd - gStart) * percent ); int db = (int) ( (float) (bEnd - bStart) * percent ); return RGB( rStart+dr, gStart+dg, bStart+db ); //将某像素的颜色按一定百分比加深}
至于OnShowWindow()和OnNCDestroy()函数,是留给使用者的一个接口,如不需使用,可简单的返回FALSE即可,通过上面的代码,即可实现我们想要的菜单阴影效果,当然,你可能有更好的阴影绘制效果,do it yourself!
五、实现特效菜单项
同理,假设我们定义了一个CMenuItem类来实现菜单项特效,只需要响应几个Windows消息即可。在OnInitMenuPopup()时将菜单转换成带OwnerDraw属性的菜单,在OnMeasureItem()时调整菜单项的宽度,在OnDrawItem()是绘制菜单项,在OnMenuChar()时响应用户的键盘的操作。
六、待续说明
在此附上的工程仅仅是实现了菜单阴影,对于CMenuItem实现菜单项,具体细节我还有一点疑问未弄清楚,待搞清楚了以后再附上完整的工程例子。如要使用CMenuEx类,只需在App的InitInstance()中InstallMenuEx()和在ExitInstance()中UnInstallMenuEx()即可,详见附后的工程例子。 |
|