程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> vc教程 >> 利用鉤子實現菜單陰影效果

利用鉤子實現菜單陰影效果

編輯:vc教程

  也許有很多人曾和我一樣, 對Office XP裡面的菜單的陰影效果羨慕不已,它不需要在Windows XP 中就可以在菜單後面顯示陰影, 當然在Windows XP中, 已經完全支持菜單陰影了。雖然我們不一定很有必要自己來實現這個較難實現的效果。但是正如有很多人想實現那種IE風格的菜單欄一樣,盡管它 們並不能為我們帶來更多實用的功能, 卻可以使我們的程序看起來與眾不同。:)

  菜單也是一個窗口, 假如我們能得到它的窗口的句柄, 要實現像添加陰影這樣的效果, 就不會很難了。可惜我們根本找不到這個窗口是在哪裡被創建的,也沒辦法很容易地取得它的窗口句柄,甚至幾乎難以相信它是一個窗口,因為我實在找不到它的窗口句柄啊。經過對許多別人已經做好的類的源代碼的"研究", 我終於找到了一個方法。那就是萬能的鉤子,如果說在Windows裡面抓"人",連鉤子也辦不到的話,那我就不知道該用什麼方法實現了,呵呵。

  下面我就一起來看看如何抓到這些"可惡"的家伙吧。為了便於移植,我們就寫一個專用的類吧,就取名為CMenuWndHook。添加兩個靜態成員先:

static CMap m_WndMenuMap;
static HHOOK m_hMenuHook;
  被我們抓到的這些家伙肯定不止一個,我們需要一個映射模板類來保存它們的句柄和對應的CMenuWndHook 類對象的指針。m_hMenuHook則為我們將要創建的鉤子的鉤子句柄。再在CPP文件中初始化它們:
CMap CMenuWndHook::m_WndMenuMap;
HHOOK CMenuWndHook::m_hMenuHook = NULL;
下面再添加兩個函數來做安裝與卸載hook之用, 它們都是靜態函數:
void CMenuWndHook::InstallHook()
{
  if (m_hMenuHook == NULL)
  {
    m_hMenuHook = ::SetWindowsHookEx(WH_CALLWNDPROC,
                     WindowHook,
          AfxGetApp()->m_hInstance,
                    ::GetCurrentThreadId());
  }
}
Windows之下一般用上面的SetWindowsHookEx API函數來安裝HOOK,它的函數原型如下:

  

HHOOK SetWindowsHookEx(int idHook, //鉤子的類型,即它處理的消息類型      
  HOOKPROC  lpfn,
    //子函數的入口地址,當鉤子鉤到任何消息後先調用這個函數。    
    // (如果dwThreadId參數為0,或是一個由別的進程創建的線程的標識,
    //lpfn必須指向DLL中的鉤子子程。除此以外,lpfn可以指向當前進
    //程的一段鉤子子程代碼)      
  HINSTANCE  hMod, //應用程序實例的句柄。標識包含lpfn所指的子程的DLL。   
    // 如果dwThreadId標識當前進程創建的一個線程,
    //而且子程代碼位於當前進程,hMod必須為NULL。
    //可以很簡單的設定其為本應用程序的實例句柄。      
  DWORD  dwThreadId //與安裝的鉤子子程相關聯的線程的標識符。
    //如果為0,鉤子子程與所有的線程關聯,即為全局鉤子。
    //但這時,你鉤子只能是放在DLL中。           
  );

  函數成功則返回鉤子子程的句柄,失敗返回NULL。 我們用到的是WH_CALLWNDPROC類型的鉤子,它使你可以監視發送到窗口過程的消息, 系統在消息發送到 接收窗口過程之前會調用你指定的WH_CALLWNDPROC Hook 子程,這樣你就可以等它們自投羅網,然後就可以 對它們為所欲為了。 卸載鉤子就簡單多了,只需要調用UnhookWindowsHookEx即可,當然,我們還需要額外做一點清理工作:

void CMenuWndHook::UnInstallHook()
{
  POSITION pos = m_WndMenuMap.GetStartPosition();
  while (pos != NULL)
  {
    HWND hwnd;
    CMenuWndHook *pMenuWndHook;
    m_WndMenuMap.GetNextAssoc(pos, hwnd, pMenuWndHook);
    delete pMenuWndHook;
    pMenuWndHook= NULL;
  }
  
  m_WndMenuMap.RemoveAll();
  if (m_hMenuHook != NULL)
  {
    ::UnhookWindowsHookEx(m_hMenuHook);
  } 
}
  在介紹如何安裝鉤子時,提到要一個鉤子子程,這個子程必須按下面的格式聲明,否則不能使用:

  LRESULT CALLBACK WindowHook(int code, WPARAM wParam, LPARAM lParam); 函數名隨意,同樣把它聲明為靜態函數,下面各位注意了,我們的逮捕行動就是在這個函數中展開的:

LRESULT CALLBACK CMenuWndHook::WindowHook(int code, WPARAM wParam, LPARAM lParam)
{
  //如果你安裝的是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;
    }
    // 是否為菜單類 ----------------------------------------
    TCHAR strClassName[10];
    int Count = ::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);
    //下面該叫它去洗心革面了-----------------
    //取得原來的窗口過程 ----------------------------------
    WNDPROC oldWndProc = (WNDPROC)(long)::GetWindowLong(pStruct->hwnd, GWL_WNDPROC);
    if (oldWndProc == NULL)
    {
      break; 
    }
    ASSERT(oldWndProc != CoolMenuProc); //這個過程一樣不能出錯
    // 保存到窗口的屬性中 ----------------------------------
    // 哈哈,給它打個記號吧 (SetProp API函數是用來給一個窗口加上一個屬性的,
    // RemoveProp 則是刪除一個屬性,GetProp 是取得一個屬性的值)   
    // CoolMenu_oldProc 為一字符數組, 我在CPP文件的開頭聲明了它,表示你要
    // 添加的屬性名: const TCHAR CoolMenu_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;
    }
  }
  // 這句可是絕對不能少的,叫那些閒雜人等該干什麼就干什麼去,不要?
  // 嘿嘿,看你的程序怎麼死吧! 
  return CallNextHookEx(m_hMenuHook, code, wParam, lParam);
}  
我們再來看看,怎麼"登記"它們:
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裡面沒有被聲明,你需要自已聲明它:

本文示例代碼或素材下載

  • 首頁
  • 上一頁
  • 1
  • 2
  • 3
  • 下一頁
  • 尾頁
  • 共3頁
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved