您的位置:首页 > 其它

利用钩子实现菜单阴影效果2

2012-07-04 11:26 363 查看
我们再来看看,怎么"登记"它们:

CMenuWndHook* CMenuWndHook::AddWndHook(HWND hwnd)

{

CMenuWndHook* pWnd = NULL;

if (m_WndMenuMap.Lookup(hwnd, pWnd))

{

// 有这个人了,不用再登记了。

return pWnd;

}

// 给它分配个房间(牢房! 嘿嘿)

pWnd = new CMenuWndHook(hwnd);

if (pWnd != NULL)

{

m_WndMenuMap.SetAt(hwnd, pWnd);

}

return pWnd;

}

// 另外还可有一个对应的查找函数:

CMenuWndHook* CMenuWndHook::GetWndHook(HWND hwnd)

{

CMenuWndHook* pWnd = NULL;

if (m_WndMenuMap.Lookup(hwnd, pWnd))

{

return pWnd;

}

return NULL;

}

上面的函数和变量大部分都是静态成员,因为hook系统只要有一套就可以了到这里为止,坚巨的任务已经完成了一半,做下面的事,就得心应手多了。下面是窗口的新过程,依然为一个静态的函数。

LRESULT CALLBACK CMenuWndHook::CoolMenuProc(HWND hWnd,

UINT uMsg,

WPARAM wParam,

LPARAM lParam)

{

WNDPROC oldWndProc = (WNDPROC)::GetProp(hWnd, CoolMenu_oldProc);

CMenuWndHook* pWnd = NULL;

switch (uMsg)

{

// 计算非客户区的大小--------------------------

case WM_NCCALCSIZE:

{

LRESULT lResult = CallWindowProc(oldWndProc,

hWnd,

uMsg,

wParam,

lParam);

if ((pWnd = GetWndHook(hWnd)) != NULL)

{

pWnd->OnNcCalcsize((NCCALCSIZE_PARAMS*)lParam);

}

return lResult;

}

break;

// 当窗口的位置将要发生改变, 在这里它一般发生在菜单被弹出之前,

// 给你最后一次机会设置它的位置.

case WM_WINDOWPOSCHANGING:

{

if ((pWnd = GetWndHook(hWnd)) != NULL)

{

pWnd->OnWindowPosChanging((LPWINDOWPOS)lParam);

}

} break;

// 为什么要响应这个消息呢? 我也不知道啊,我只知道,当菜单是以动画的方式弹出的时候

// 系统是通过发送这个消息来绘制菜单的,wParam是对应的设备上下文句柄,不过我也不知

// 道它到底是属于谁的.

case WM_PRINT:

{

LRESULT lResult = CallWindowProc(oldWndProc,

hWnd,

uMsg,

wParam,

lParam);

if ((pWnd = GetWndHook(hWnd)) != NULL)

{

pWnd->OnPrint(CDC::FromHandle((HDC)wParam));

}

return lResult;

}

break;

//这个就不同说了吧.

case WM_NCPAINT:

{

if ((pWnd = GetWndHook(hWnd)) != NULL)

{

pWnd->OnNcPaint();

return 0;

}

}

break;

// 菜单窗口被隐藏的时候,我也不知道这种情况会不会发生, :(, 主要是看到人家这样处理了.

case WM_SHOWWINDOW:

{

if ((pWnd = GetWndHook(hWnd)) != NULL)

{

pWnd->OnShowWindow(wParam != NULL);

}

}

break;

// 菜单窗口被销毁的时候

case WM_NCDESTROY:

{

if ((pWnd = GetWndHook(hWnd)) != NULL)

{

pWnd->OnNcDestroy();

}

}

break;

}

return CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);

}

下面就看如何慢慢实现这些消息的响应函数吧: void CMenuWndHook::OnWindowPosChanging(WINDOWPOS *pWindowPos)

{

if (!IsShadowEnabled())

{

//加一块区域来显示阴影-------

pWindowPos->cx += 4;

pWindowPos->cy += 4;

}



// 为了绘制阴影,我们须要先保存这个区域的图像,以便绘制半透明的阴影.

if (!IsWindowVisible(m_hWnd) && !IsShadowEnabled())

{

if (m_bmpBack.m_hObject != NULL)

{

m_bmpBack.DeleteObject();

}

m_bmpBack.Attach(GetScreenBitmap(CRect(pWindowPos->x,

pWindowPos->y,

pWindowPos->cx,

pWindowPos->cy)));

}

}





void CMenuWndHook::OnNcCalcsize(NCCALCSIZE_PARAMS* lpncsp)

{

if (!IsShadowEnabled())

{

//留出一点区域来显示阴影-------

lpncsp->rgrc[0].right -= 4;

lpncsp->rgrc[0].bottom -= 4;

}

}

上面我用到了两个全局函数, 其中IsShadowEnabled是检测系统是否开启了菜单阴影(主要针对于Windows XP, Windows 2003及他更高的版本) 如果系统已经给我们开启了阴影,我们还忙乎什么哦。BOOL WINAPI IsShadowEnabled()

{

BOOL bEnabled = FALSE;

if (SystemParametersInfo(SPI_GETDROPSHADOW, 0, bEnabled,0))

{

return bEnabled;

}

return FALSE;

}

其中 SPI_GETDROPSHADOW 在VC6里面没有被声明,你需要自已声明它:

#ifndef SPI_GETDROPSHADOW

#define SPI_GETDROPSHADOW 0x1024

#endif

另外还有 GetScreenBitmap 函数用于截取屏幕上指定区域内的图像: HBITMAP WINAPI 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);

}

::DeleteDC(hMemDC);

}

::ReleaseDC(NULL, hDC);

}

return hNewBitmap;

}

下面这两个函数要做的事就差不多了: void CMenuWndHook::OnNcPaint()

{

CWindowDC dc(CWnd::FromHandle(m_hWnd));

OnPrint(&dc);

}

void CMenuWndHook::OnPrint(CDC *pDC)

{

CRect rc;

GetWindowRect(m_hWnd, &rc);

rc.OffsetRect(-rc.TopLeft());

// 绘制阴影

if (!IsShadowEnabled())

{

CDC cMemDC;

cMemDC.CreateCompatibleDC (pDC);

HGDIOBJ hOldBitmap = ::SelectObject (cMemDC.m_hDC, m_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);



DrawShadow(pDC, rc);

rc.right -= 4;

rc.bottom -= 4;

}

// 绘制边框

pDC->Draw3dRect(rc, m_crFrame[0], m_crFrame[1]);

rc.DeflateRect (1, 1);

pDC->Draw3dRect(rc, m_crFrame[2], m_crFrame[3]);

}

在指定的矩形区域内绘制阴影的全局函数(当然这些函数不一定都要做成全局函数,我把它们写成了全局函数是因为在好几个类中都用到了它们, 写成全局函数便于调用) 也许你会觉得这不符合面向对象编程的思想,其实面向过程的编程思想,并不一定就比面向对象的思想落后,我把这些比较独立的函数写成全局函数,当作API函数用,还是觉得很方便的,如果硬要将它们塞到一个类里面,反而觉得很郁闷
。:-). void DrawShadow(CDC *pDC, CRect rect);

void DrawShadow(CDC *pDC, CRect rect)

{

COLORREF oldcolor = RGB(255, 255, 255);

BYTE newValR, newValG, newValB;

BYTE AlphaArray[] = {140, 170, 212, 240};

BYTE AlphaArray2[] = {170, 205, 220, 240, 240, 250, 255};

// 底部的阴影 -----------------------------------------

int i, j;

for (j = 0; j < 4; j++)

{

for (i = 6; i <= rect.right - 5; i++)

{

oldcolor = pDC->GetPixel(i, rect.bottom - (4 - j));

newValR = GetRValue(oldcolor) * AlphaArray[j] / 255;

newValG = GetGValue(oldcolor) * AlphaArray[j] / 255;

newValB = GetBValue(oldcolor) * AlphaArray[j] / 255;

pDC->SetPixel(i, rect.bottom - (4 - j), RGB(newValR, newValG, newValB));

}

}

// 右边的阴影 -----------------------------------------

for (i = 0; i < 4; i++)

{

for (j = 6; j <= rect.bottom - 5; j++)

{

oldcolor = pDC->GetPixel(rect.right - (4 - i), j);

newValR = GetRValue(oldcolor) * AlphaArray[i] / 255;

newValG = GetGValue(oldcolor) * AlphaArray[i] / 255;

newValB = GetBValue(oldcolor) * AlphaArray[i] / 255;

pDC->SetPixel(rect.right - (4 - i), j, RGB(newValR, newValG, newValB));

}

}

// 角上的阴影 --------------------------------------

for (i = 0; i < 4; i++)

{

for (j = 0; j < 4; j++)

{

if ((i + j) > 6) break;



oldcolor = pDC->GetPixel(rect.right - 4 + i, rect.bottom - 4 + j);

newValR = GetRValue(oldcolor) * AlphaArray2[i + j] / 255;

newValG = GetGValue(oldcolor) * AlphaArray2[i + j] / 255;

newValB = GetBValue(oldcolor) * AlphaArray2[i + j] / 255;

pDC->SetPixel(rect.right - 4 + i,

rect.bottom - 4 + j,

RGB(newValR,

newValG,

newValB));



oldcolor = pDC->GetPixel(rect.right - 4 + i, rect.top + 5 - j);

newValR = GetRValue(oldcolor) * AlphaArray2[i + j] / 255;

newValG = GetGValue(oldcolor) * AlphaArray2[i + j] / 255;

newValB = GetBValue(oldcolor) * AlphaArray2[i + j] / 255;

pDC->SetPixel(rect.right - 4 + i,

rect.top + 5 - j,

RGB(newValR,

newValG,

newValB));



oldcolor = pDC->GetPixel(rect.left - i + 5, rect.bottom - 4 + j);

newValR = GetRValue(oldcolor) * AlphaArray2[i + j] / 255;

newValG = GetGValue(oldcolor) * AlphaArray2[i + j] / 255;

newValB = GetBValue(oldcolor) * AlphaArray2[i + j] / 255;

pDC->SetPixel(rect.left - i + 5,

rect.bottom - 4 + j,

RGB(newValR,

newValG,

newValB));

}

}

}

这么复杂? 唉! 还不是想让它把阴影画得更好看一点, 速度?...在我机子上还过得去。毕竟菜单是不会被频繁地重画的. 这样实现阴影确实有点笨拙,且在意外的时候可能会出现一些不愉快的绘图上的bug. 但是要实现Windows XP 那样完美的菜单阴影还是很难的。我希望已经知道的高手,能指点指点! 谢了先。

下面是处理清理工作了: void CMenuWndHook::OnNcDestroy()

{

delete this; // 自杀!

}

void CMenuWndHook::OnShowWindow(BOOL bShow)

{

if (!bShow)

{

delete this; // 自杀2!

}

}

... ..., 好狠哦! 嘿嘿!

扫尾工作还由是~CMenuWndHook它老人家做, 在delete自己的时候会自动调用它的: CMenuWndHook::~CMenuWndHook()

{

WNDPROC oldWndProc = (WNDPROC)::GetProp(m_hWnd, CoolMenu_oldProc);

if (oldWndProc != NULL)

{

::SetWindowLong(m_hWnd, GWL_WNDPROC,(DWORD)(ULONG)oldWndProc);

::RemoveProp(m_hWnd, CoolMenu_oldProc);

}

m_WndMenuMap.RemoveKey(m_hWnd);

if (m_bmpBack.m_hObject != NULL)

{

m_bmpBack.DeleteObject();

}

}

这个类基本上写完了,如果我还有什么没讲清的地方,你就再去看看我的源代码吧。我们可以在APP类里面调用它: ............

#include "MenuWndHook.h"

...........

BOOL CNewmenuApp::InitInstance()

{

.......

CMenuWndHook::InstallHook();

}

int CNewmenuApp::ExitInstance()

{

CMenuWndHook::UnInstallHook();

return CWinApp::ExitInstance();

}

我时常听见人说 Delhpi 程序界面比VC程序的界面如何如何好? 如果是说默认的那些控件的外观,VC确实不如Delphi,(微软也真小气,自已产品的界面做得那么"华丽"(像Office XP/2003, Windows XP,VS.NET...), 而给我们用的这些控件的外观却这么"老土")...总之真正的精美的有个性的界面是大家自已做出来的,这正是我钟爱VC的理由之一。呵呵。



补充一点:点击菜单发送的消息顺序如下:

//////////////////////////////////////////////////////////////////////////////

// When mouse click menu bar item , message send sequence

// WINXP

// visual effect no:

// WM_NCPAINT -- WM_ERASEBKGND -- WM_DRAWITEM

// visual effect fade effect

// WM_ERASEBKGND -- WM_DRAWITEM -- WM_PRINT

// scroll effect

// WM_ERASEBKGND -- WM_DRAWITEM -- WM_PRINT -- WM_NCPAINT -- WM_ERASEBKGND -- WM_DRAWITEM

// WIN2k

// visual effect no:

// WM_NCPAINT -- WM_ERASEBKGND -- WM_DRAWITEM

// visual effect fade effect

// WM_PRINT -- WM_ERASEBKGND -- WM_DRAWITEM

// scroll effect

// WM_PRINT -- WM_ERASEBKGND -- WM_DRAWITEM -- WM_NCPAINT -- WM_ERASEBKGND -- WM_DRAWITEM

// there are three messages before all the case:

// WM_WINDOWPOSCHANGING -- WM_NCCALCSIZE -- WM_WINDOWPOSCHANGING

///////////////////////////////////////////////////////////////////////////////

其顺序与菜单的过渡效果有关,以xp为例,可以在“显示器\属性\外观\效果”来设定它的过渡效果:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: