实现菜单阴影效果.docx
- 文档编号:2902412
- 上传时间:2023-05-05
- 格式:DOCX
- 页数:13
- 大小:44.08KB
实现菜单阴影效果.docx
《实现菜单阴影效果.docx》由会员分享,可在线阅读,更多相关《实现菜单阴影效果.docx(13页珍藏版)》请在冰点文库上搜索。
实现菜单阴影效果
利用钩子实现菜单阴影效果
程序运行效果截图:
也许有很多人曾和我一样,对OfficeXP里面的菜单的阴影效果羡慕不已,它不需要在WindowsXP中就可以在菜单后面显示阴影,当然在WindowsXP中,已经完全支持菜单阴影了。
虽然我们不一定很有必要自己来实现这个较难实现的效果。
但是正如有很多人想实现那种IE风格的菜单栏一样,尽管它们并不能为我们带来更多实用的功能,却可以使我们的程序看起来与众不同。
:
)
菜单也是一个窗口,假如我们能得到它的窗口的句柄,要实现像添加阴影这样的效果,就不会很难了。
可惜我们根本找不到这个窗口是在哪里被创建的,也没办法很容易地取得它的窗口句柄,甚至几乎难以相信它是一个窗口,因为我实在找不到它的窗口句柄啊。
经过对许多别人已经做好的类的源代码的"研究",我终于找到了一个方法。
那就是万能的钩子,如果说在Windows里面抓"人",连钩子也办不到的话,那我就不知道该用什么方法实现了,呵呵。
下面我就一起来看看如何抓到这些"可恶"的家伙吧。
为了便于移植,我们就写一个专用的类吧,就取名为CMenuWndHook。
添加两个静态成员先:
staticCMapm_WndMenuMap;
staticHHOOKm_hMenuHook;
被我们抓到的这些家伙肯定不止一个,我们需要一个映射模板类来保存它们的句柄和对应的CMenuWndHook类对象的指针。
m_hMenuHook则为我们将要创建的钩子的钩子句柄。
再在CPP文件中初始化它们:
CMapCMenuWndHook:
:
m_WndMenuMap;
HHOOKCMenuWndHook:
:
m_hMenuHook=NULL;
下面再添加两个函数来做安装与卸载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!
=NULL)
{
:
:
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->message!
=0x01E2)
{
break;
}
//是否为菜单类----------------------------------------
TCHARstrClassName[10];
intCount=:
:
GetClassName(hWnd,
strClassName,
sizeof(strClassName)/sizeof(strClassName[0]));
//再次确认它的身份(菜单窗口类的类名为"#32768",且为6个字符长):
if(Count!
=6||_tcscmp(strClassName,_T("#32768"))!
= 0)
{
//对不起,认错人了,pass:
-)
break;
}
//是否已经被子类化------------------------------------
//我们抓到一个之后,会给它用SetProp挂个牌(后面会介绍)
if(:
:
GetProp(pStruct->hwnd,CoolMenu_oldProc)!
=NULL)
{
//已经在编?
pass.
break;
}
//抓到一个,给它登记注册(这个函数我会在后面介绍), 而且不能登记失败,:
)
VERIFY(AddWndHook(pStruct->hwnd)!
=NULL);
//下面该叫它去洗心革面了-----------------
//取得原来的窗口过程----------------------------------
WNDPROColdWndProc=(WNDPROC)(long):
:
GetWindowLong(pStruct->hwnd,GWL_WNDPROC);
if(oldWndProc==NULL)
{
break;
}
ASSERT(oldWndProc!
=CoolMenuProc);//这个过程一样不能出错
//保存到窗口的属性中----------------------------------
//哈哈,给它打个记号吧(SetPropAPI函数是用来给一个窗口加上一个属性的,
//RemoveProp则是删除一个属性,GetProp是取得一个属性的值)
//CoolMenu_oldProc为一字符数组,我在CPP文件的开头声明了它,表示你要
//添加的属性名:
constTCHARCoolMenu_oldProc[]=_T("CoolMenu_oldProc");
//这里保存的是它的原来的窗口过程,这种该随身带的东西还是让它自己拿着比较好
if(!
SetProp(pStruct->hwnd,CoolMenu_oldProc,oldWndProc))
{
break;
}
//子类化----------------------------------------------
//这个不用我说了吧,这里我们用了偷梁换柱的方法,呵呵,这可是子类化的惯技了:
if(!
SetWindowLong(pStruct->hwnd,GWL_WNDPROC,(DWORD)(ULONG)CoolMenuProc))
{
//没有成功!
!
唉,就放过他吧,虽然忙了半天了,不过这种情况我想是不可能发生的!
:
:
RemoveProp(pStruct->hwnd,CoolMenu_oldProc);
break;
}
}
//这句可是绝对不能少的,叫那些闲杂人等该干什么就干什么去,不要?
//嘿嘿,看你的程序怎么死吧!
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!
=NULL)
{
m_WndMenuMap.SetAt(hwnd,pWnd);
}
returnpWnd;
}
//另外还可有一个对应的查找函数:
CMenuWndHook* CMenuWndHook:
:
GetWndHook(HWNDhwnd)
{
CMenuWndHook*pWnd=NULL;
if(m_WndMenuMap.Lookup(hwnd,pWnd))
{
returnpWnd;
}
return NULL;
}
上面的函数和变量大部分都是静态成员,因为hook系统只要有一套就可以了到这里为止,坚巨的任务已经完成了一半,做下面的事,就得心应手多了。
下面是窗口的新过程,依然为一个静态的函数。
LRESULTCALLBACKCMenuWndHook:
:
CoolMenuProc(HWNDhWnd,
UINTuMsg,
WPARAMwParam,
LPARAMlParam)
{
WNDPROColdWndProc=(WNDPROC):
:
GetProp(hWnd,CoolMenu_oldProc);
CMenuWndHook*pWnd=NULL;
switch(uMsg)
{
//计算非客户区的大小--------------------------
caseWM_NCCALCSIZE:
{
LRESULTlResult=CallWindowProc(oldWndProc,
hWnd,
uMsg,
wParam,
lParam);
if((pWnd=GetWndHook(hWnd))!
=NULL)
{
pWnd->OnNcCalcsize((NCCALCSIZE_PARAMS*)lParam);
}
returnlResult;
}
break;
//当窗口的位置将要发生改变,在这里它一般发生在菜单被弹出之前,
//给你最后一次机会设置它的位置.
caseWM_WINDOWPOSCHANGING:
{
if((pWnd=GetWndHook(hWnd))!
= NULL)
{
pWnd->OnWindowPosChanging((LPWINDOWPOS)lParam);
}
}break;
//为什么要响应这个消息呢?
我也不知道啊,我只知道,当菜单是以动画的方式弹出的时候
//系统是通过发送这个消息来绘制菜单的,wParam是对应的设备上下文句柄,不过我也不知
//道它到底是属于谁的.
caseWM_PRINT:
{
LRESULTlResult=CallWindowProc(oldWndProc,
hWnd,
uMsg,
wParam,
lParam);
if((pWnd=GetWndHook(hWnd))!
=NULL)
{
pWnd->OnPrint(CDC:
:
FromHandle((HDC)wParam));
}
returnlResult;
}
break;
//这个就不同说了吧.
caseWM_NCPAINT:
{
if((pWnd=GetWndHook(hWnd))!
=NULL)
{
pWnd->OnNcPaint();
return0;
}
}
break;
//菜单窗口被隐藏的时候,我也不知道这种情况会不会发生,:
(,主要是看到人家这样处理了.
caseWM_SHOWWINDOW:
{
if((pWnd=GetWndHook(hWnd))!
=NULL)
{
pWnd->OnShowWindow(wParam!
=NULL);
}
}
break;
//菜单窗口被销毁的时候
caseWM_NCDESTROY:
{
if((pWnd=GetWndHook(hWnd))!
=NULL)
{
pWnd->OnNcDestroy();
}
}
break;
}
returnCallWindowProc(oldWndProc,hWnd,uMsg,wParam,lParam);
}
下面就看如何慢慢实现这些消息的响应函数吧:
voidCMenuWndHook:
:
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)));
}
}
voidCMenuWndHook:
:
OnNcCalcsize(NCCALCSIZE_PARAMS*lpncsp)
{
if(!
IsShadowEnabled())
{
//留出一点区域来显示阴影-------
lpncsp->rgrc[0].right-=4;
lpncsp->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;
HDC hMemDC;
HBITMAPhNewBitmap=NULL;
if((hDC=:
:
GetDC(NULL))!
=NULL)
{
if((hMemDC=:
:
CreateCompatibleDC(hDC))!
=NULL)
{
if((hNewBitmap=:
:
CreateCompatibleBitmap(hDC,
pRect->right-pRect->left,
pRect->bottom-pRect->top))!
=NULL)
{
HBITMAPhOldBitmap=(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);
}
returnhNewBitmap;
}
下面这两个函数要做的事就差不多了:
voidCMenuWndHook:
:
OnNcPaint()
{
CWindowDCdc(CWnd:
:
FromHandle(m_hWnd));
OnPrint(&dc);
}
voidCMenuWndHook:
:
OnPrint(CDC*pDC)
{
CRectrc;
GetWindowRect(m_hWnd,&rc);
rc.OffsetRect(-rc.TopLeft());
//绘制阴影
if(!
IsShadowEnabled())
{
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);
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函数用,还是觉得很方便的,如果硬要将它们塞到一个类里面,反而觉得很郁闷。
:
-).voidDrawShadow(CDC*pDC,CRectrect);
voidDrawShadow(CD
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 实现 菜单 阴影 效果
![提示](https://static.bingdoc.com/images/bang_tan.gif)