实现菜单阴影效果Word格式.docx
《实现菜单阴影效果Word格式.docx》由会员分享,可在线阅读,更多相关《实现菜单阴影效果Word格式.docx(13页珍藏版)》请在冰点文库上搜索。
下面再添加两个函数来做安装与卸载hook之用,它们都是静态函数:
voidCMenuWndHook:
InstallHook()
{
if(m_hMenuHook==NULL)
m_hMenuHook=:
SetWindowsHookEx(WH_CALLWNDPROC,
WindowHook,
AfxGetApp()->
m_hInstance,
:
GetCurrentThreadId());
}
Windows之下一般用上面的SetWindowsHookExAPI函数来安装HOOK,它的函数原型如下:
HHOOKSetWindowsHookEx(intidHook,//钩子的类型,即它处理的消息类型
HOOKPROClpfn,
//子函数的入口地址,当钩子钩到任何消息后先调用这个函数。
//(如果dwThreadId参数为0,或是一个由别的进程创建的线程的标识,
//lpfn必须指向DLL中的钩子子程。
除此以外,lpfn可以指向当前进
//程的一段钩子子程代码)
HINSTANCEhMod,//应用程序实例的句柄。
标识包含lpfn所指的子程的DLL。
//如果dwThreadId标识当前进程创建的一个线程,
//而且子程代码位于当前进程,hMod必须为NULL。
//可以很简单的设定其为本应用程序的实例句柄。
DWORDdwThreadId//与安装的钩子子程相关联的线程的标识符。
//如果为0,钩子子程与所有的线程关联,即为全局钩子。
//但这时,你钩子只能是放在DLL中。
);
函数成功则返回钩子子程的句柄,失败返回NULL。
我们用到的是WH_CALLWNDPROC类型的钩子,它使你可以监视发送到窗口过程的消息,系统在消息发送到接收窗口过程之前会调用你指定的WH_CALLWNDPROCHook子程,这样你就可以等它们自投罗网,然后就可以对它们为所欲为了。
卸载钩子就简单多了,只需要调用UnhookWindowsHookEx即可,当然,我们还需要额外做一点清理工作:
voidCMenuWndHook:
UnInstallHook()
POSITIONpos=m_WndMenuMap.GetStartPosition();
while(pos!
=NULL)
HWNDhwnd;
CMenuWndHook*pMenuWndHook;
m_WndMenuMap.GetNextAssoc(pos,hwnd,
pMenuWndHook);
deletepMenuWndHook;
pMenuWndHook=NULL;
m_WndMenuMap.RemoveAll();
if(m_hMenuHook!
UnhookWindowsHookEx(m_hMenuHook);
}
在介绍如何安装钩子时,提到要一个钩子子程,这个子程必须按下面的格式声明,否则不能使用:
LRESULTCALLBACKWindowHook(intcode,WPARAMwParam,LPARAMlParam);
函数名随意,同样把它声明为静态函数,下面各位注意了,我们的逮捕行动就是在这个函数中展开的:
LRESULTCALLBACKCMenuWndHook:
WindowHook(intcode,WPARAMwParam,LPARAMlParam)
//如果你安装的是WH_CALLWNDPROC类型的钩子的话,系统就会传递一个这个家伙的指针:
CWPSTRUCT*pStruct=(CWPSTRUCT*)lParam;
while(code==HC_ACTION)
HWND
hWnd=pStruct->
hwnd;
//截获WM_CREATE消息,为了保证不抓错"
,我们必须严格确定这是否是我们要抓的家伙,
//这样我们就可以在它们刚出头就把它们逮住:
if(pStruct->
message!
=
WM_CREATE&
&
pStruct->
=0x01E2)
break;
//是否为菜单类----------------------------------------
TCHARstrClassName[10];
intCount=:
GetClassName(hWnd,
strClassName,
sizeof(strClassName)/sizeof(strClassName[0]));
//再次确认它的身份(菜单窗口类的类名为"
#32768"
,且为6个字符长):
if(Count!
=6||_tcscmp(strClassName,_T("
))!
0)
//对不起,认错人了,pass:
-)
//是否已经被子类化------------------------------------
//我们抓到一个之后,会给它用SetProp挂个牌(后面会介绍)
if(:
GetProp(pStruct->
hwnd,CoolMenu_oldProc)!
=NULL)
//已经在编?
pass.
//抓到一个,给它登记注册(这个函数我会在后面介绍),
而且不能登记失败,:
VERIFY(AddWndHook(pStruct->
hwnd)!
=NULL);
//下面该叫它去洗心革面了-----------------
//取得原来的窗口过程----------------------------------
WNDPROColdWndProc=(WNDPROC)(long):
GetWindowLong(pStruct->
hwnd,GWL_WNDPROC);
if(oldWndProc==NULL)
ASSERT(oldWndProc!
=CoolMenuProc);
//这个过程一样不能出错
//保存到窗口的属性中----------------------------------
//哈哈,给它打个记号吧(SetPropAPI函数是用来给一个窗口加上一个属性的,
//RemoveProp则是删除一个属性,GetProp是取得一个属性的值)
//CoolMenu_oldProc为一字符数组,我在CPP文件的开头声明了它,表示你要
//添加的属性名:
constTCHARCoolMenu_oldProc[]=_T("
CoolMenu_oldProc"
//这里保存的是它的原来的窗口过程,这种该随身带的东西还是让它自己拿着比较好
if(!
SetProp(pStruct->
hwnd,CoolMenu_oldProc,oldWndProc))
//子类化----------------------------------------------
//这个不用我说了吧,这里我们用了偷梁换柱的方法,呵呵,这可是子类化的惯技了:
SetWindowLong(pStruct->
hwnd,GWL_WNDPROC,(DWORD)(ULONG)CoolMenuProc))
//没有成功!
!
唉,就放过他吧,虽然忙了半天了,不过这种情况我想是不可能发生的!
RemoveProp(pStruct->
hwnd,CoolMenu_oldProc);
//这句可是绝对不能少的,叫那些闲杂人等该干什么就干什么去,不要?
//嘿嘿,看你的程序怎么死吧!
returnCallNextHookEx(m_hMenuHook,code,
wParam,lParam);
我们再来看看,怎么"
登记"
它们:
CMenuWndHook*CMenuWndHook:
AddWndHook(HWNDhwnd)
CMenuWndHook*pWnd=NULL;
if(m_WndMenuMap.Lookup(hwnd,pWnd))
//有这个人了,不用再登记了。
returnpWnd;
//给它分配个房间(牢房!
嘿嘿)
pWnd=newCMenuWndHook(hwnd);
if(pWnd!
m_WndMenuMap.SetAt(hwnd,pWnd);
//另外还可有一个对应的查找函数:
CMenuWndHook*
CMenuWndHook:
GetWndHook(HWNDhwnd)
return
NULL;
上面的函数和变量大部分都是静态成员,因为hook系统只要有一套就可以了到这里为止,坚巨的任务已经完成了一半,做下面的事,就得心应手多了。
下面是窗口的新过程,依然为一个静态的函数。
CoolMenuProc(HWNDhWnd,
UINTuMsg,
WPARAMwParam,
LPARAMlParam)
WNDPROColdWndProc=(WNDPROC):
GetProp(hWnd,CoolMenu_oldProc);
switch(uMsg)
//计算非客户区的大小--------------------------
caseWM_NCCALCSIZE:
{
LRESULTlResult=CallWindowProc(oldWndProc,
hWnd,
uMsg,
wParam,
lParam);
if((pWnd=GetWndHook(hWnd))!
pWnd->
OnNcCalcsize((NCCALCSIZE_PARAMS*)lParam);
returnlResult;
}
//当窗口的位置将要发生改变,在这里它一般发生在菜单被弹出之前,
//给你最后一次机会设置它的位置.
caseWM_WINDOWPOSCHANGING:
NULL)
OnWindowPosChanging((LPWINDOWPOS)lParam);
}break;
//为什么要响应这个消息呢?
我也不知道啊,我只知道,当菜单是以动画的方式弹出的时候
//系统是通过发送这个消息来绘制菜单的,wParam是对应的设备上下文句柄,不过我也不知
//道它到底是属于谁的.
caseWM_PRINT:
OnPrint(CDC:
FromHandle((HDC)wParam));
//这个就不同说了吧.
caseWM_NCPAINT:
OnNcPaint();
return0;
//菜单窗口被隐藏的时候,我也不知道这种情况会不会发生,:
(,主要是看到人家这样处理了.
caseWM_SHOWWINDOW:
OnShowWindow(wParam!
//菜单窗口被销毁的时候
caseWM_NCDESTROY:
OnNcDestroy();
returnCallWindowProc(oldWndProc,hWnd,uMsg,wParam,lParam);
下面就看如何慢慢实现这些消息的响应函数吧:
OnWindowPosChanging(WINDOWPOS*pWindowPos)
IsShadowEnabled())
//加一块区域来显示阴影-------
pWindowPos->
cx+=4;
cy+=4;
//为了绘制阴影,我们须要先保存这个区域的图像,以便绘制半透明的阴影.
IsWindowVisible(m_hWnd)&
!
if(m_bmpBack.m_hObject!
m_bmpBack.DeleteObject();
m_bmpBack.Attach(GetScreenBitmap(CRect(pWindowPos->
x,
y,
cx,
pWindowPos->
cy)));
OnNcCalcsize(NCCALCSIZE_PARAMS*lpncsp)
//留出一点区域来显示阴影-------
lpncsp->
rgrc[0].right-=4;
rgrc[0].bottom-=4;
上面我用到了两个全局函数,其中IsShadowEnabled是检测系统是否开启了菜单阴影(主要针对于WindowsXP,Windows2003及他更高的版本)如果系统已经给我们开启了阴影,我们还忙乎什么哦。
BOOLWINAPIIsShadowEnabled()
BOOLbEnabled=FALSE;
if(SystemParametersInfo(SPI_GETDROPSHADOW,0,bEnabled,0))
returnbEnabled;
returnFALSE;
其中SPI_GETDROPSHADOW在VC6里面没有被声明,你需要自已声明它:
#ifndefSPI_GETDROPSHADOW
#defineSPI_GETDROPSHADOW0x1024
#endif
另外还有GetScreenBitmap函数用于截取屏幕上指定区域内的图像:
HBITMAPWINAPIGetScreenBitmap(LPCRECTpRect)
HDC
hDC;
hMemDC;
HBITMAPhNewBitmap=NULL;
if((hDC=:
GetDC(NULL))!
=NULL)
if((hMemDC=:
CreateCompatibleDC(hDC))!
if((hNewBitmap=:
CreateCompatibleBitmap(hDC,
pRect->
right-pRect->
left,
bottom-pRect->
top))!
HBITMAPhOldBitmap=(HBITMAP):
SelectObject(hMemDC,hNewBitmap);
BitBlt(hMemDC,0,0,pRect->
left,pRect->
top,
hDC,pRect->
top,SRCCOPY);
SelectObject(hMemDC,(HGDIOBJ)hOldBitmap);
DeleteDC(hMemDC);
ReleaseDC(NULL,hDC);
returnhNewBitmap;
下面这两个函数要做的事就差不多了:
OnNcPaint()
CWindowDCdc(CWnd:
FromHandle(m_hWnd));
OnPrint(&
dc);
OnPrint(CDC*pDC)
CRectrc;
GetWindowRect(m_hWnd,&
rc);
rc.OffsetRect(-rc.TopLeft());
//绘制阴影
CDCcMemDC;
cMemDC.CreateCompatibleDC(pDC);
HGDIOBJhOldBitmap=:
SelectObject(cMemDC.m_hDC,m_bmpBack);
pDC->
BitBlt(0,rc.bottom-4,rc.Width()-4,4,&
cMemDC,0,rc.bottom-4,SRCCOPY);
BitBlt(rc.right-4,0,4,rc.Height(),&
cMemDC,rc.right-4,0,SRCCOPY);
DrawShadow(pDC,rc);
rc.right-=4;
rc.bottom-=4;
//绘制边框
Draw3dRect(rc,m_crFrame[0],m_crFrame[1]);
rc.DeflateRect(1,1);
Draw3dRect(rc,m_crFrame[2],m_crFrame[3]);
在指定的矩形区域内绘制阴影的全局函数(当然这些函数不一定都要做成全局函数,我把它们写成了全局函数是因为在好几个类中都用到了它们,写成全局函数便于调用)也许你会觉得这不符合面向对象编程的思想,其实面向过程的编程思想,并不一定就比面向对象的思想落后,我把这些比较独立的函数写成全局函数,当作API函数用,还是觉得很方便的,如果硬要将它们塞到一个类里面,反而觉得很郁闷。
-).voidDrawShadow(CDC*pDC,CRectrect);
voidDrawShadow(CD