实现菜单阴影效果.docx

上传人:b****1 文档编号:2902412 上传时间:2023-05-05 格式:DOCX 页数:13 大小:44.08KB
下载 相关 举报
实现菜单阴影效果.docx_第1页
第1页 / 共13页
实现菜单阴影效果.docx_第2页
第2页 / 共13页
实现菜单阴影效果.docx_第3页
第3页 / 共13页
实现菜单阴影效果.docx_第4页
第4页 / 共13页
实现菜单阴影效果.docx_第5页
第5页 / 共13页
实现菜单阴影效果.docx_第6页
第6页 / 共13页
实现菜单阴影效果.docx_第7页
第7页 / 共13页
实现菜单阴影效果.docx_第8页
第8页 / 共13页
实现菜单阴影效果.docx_第9页
第9页 / 共13页
实现菜单阴影效果.docx_第10页
第10页 / 共13页
实现菜单阴影效果.docx_第11页
第11页 / 共13页
实现菜单阴影效果.docx_第12页
第12页 / 共13页
实现菜单阴影效果.docx_第13页
第13页 / 共13页
亲,该文档总共13页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

实现菜单阴影效果.docx

《实现菜单阴影效果.docx》由会员分享,可在线阅读,更多相关《实现菜单阴影效果.docx(13页珍藏版)》请在冰点文库上搜索。

实现菜单阴影效果.docx

实现菜单阴影效果

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

程序运行效果截图:

  也许有很多人曾和我一样,对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

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 法律文书 > 调解书

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2