首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

鉤子實現選單陰影效果:SetWindowsHookEx(WH_CALLWNDPROC) 攔截菜單視窗建立,子類化繪製半透明陰影

     編輯:VC 教學

Office XP 風格選單陰影 (非 XP 系統也能顯示) 令人羨慕。本文揭秘核心技術:**全域鉤子 (WH_CALLWNDPROC) 攔截選單視窗 WM_CREATE 訊息 → 識別類別名 #32768 → 子類化 (SetWindowLong/GWL_WNDPROC) → 處理 WM_NCPAINT/WM_WINDOWPOSCHANGING/WM_PRINT 繪製半透明陰影**。完整類別 CMenuWndHook 實現代碼解析。

核心架構:靜態成員管理鉤子與視窗映射

class CMenuWndHook
{
    static CMap m_WndMenuMap; // HWND -> 實例
    static HHOOK m_hMenuHook; // 全域鉤子句柄
    HWND m_hWnd; // 當前子類化的選單視窗句柄
    CBitmap m_bmpBack; // 陰影區域背景備份
    // ... 其他成員 ...

public:
    static void InstallHook();  // 安裝鉤子 (應用啟動時呼叫)
    static void UnInstallHook(); // 卸載鉤子 (應用退出時呼叫)
    // ... 訊息處理虛函數 ...
};

鉤子安裝與核心攔截邏輯 (WindowHook)

void CMenuWndHook::InstallHook()
{
    if (!m_hMenuHook)
        m_hMenuHook = ::SetWindowsHookEx(WH_CALLWNDPROC, WindowHook, 
            AfxGetApp()->m_hInstance, ::GetCurrentThreadId()); // 執行緒鉤子
}

LRESULT CALLBACK CMenuWndHook::WindowHook(int code, WPARAM wp, LPARAM lp)
{
    if (code != HC_ACTION) return CallNextHookEx(m_hMenuHook, code, wp, lp);
    CWPSTRUCT* pMsg = (CWPSTRUCT*)lp;

    // 只關心 WM_CREATE (視窗建立) 和 0x01E2 (內部訊息,可能為 WM_NCCREATE 等)
    if (pMsg->message != WM_CREATE && pMsg->message != 0x01E2) 
        return CallNextHookEx(m_hMenuHook, code, wp, lp);

    HWND hWnd = pMsg->hwnd;
    // 識別選單視窗類別名 "#32768" (長度 6)
    TCHAR szClass[10]; 
    if (::GetClassName(hWnd, szClass, 10) != 6 || _tcscmp(szClass, _T("#32768")) != 0)
        return CallNextHookEx(m_hMenuHook, code, wp, lp);

    // 檢查是否已子類化 (用屬性標記)
    if (::GetProp(hWnd, _T("CoolMenu_oldProc"))) 
        return CallNextHookEx(m_hMenuHook, code, wp, lp);

    // 登記實例
    CMenuWndHook* pHook = AddWndHook(hWnd); // 建立實例存入 Map
    if (!pHook) return CallNextHookEx(m_hMenuHook, code, wp, lp);

    // 偷梁換柱:保存原 WndProc,設定新 WndProc
    WNDPROC oldProc = (WNDPROC)::GetWindowLong(hWnd, GWL_WNDPROC);
    ::SetProp(hWnd, _T("CoolMenu_oldProc"), (HANDLE)oldProc);
    ::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)CoolMenuProc); // 靜態新視窗程序

    return CallNextHookEx(m_hMenuHook, code, wp, lp);
}

新視窗程序 CoolMenuProc 分發關鍵訊息

LRESULT CALLBACK CMenuWndHook::CoolMenuProc(HWND hWnd, UINT uMsg, WPARAM wp, LPARAM lp)
{
    WNDPROC oldProc = (WNDPROC)::GetProp(hWnd, _T("CoolMenu_oldProc"));
    CMenuWndHook* pHook = GetWndHook(hWnd); // 從 Map 取得實例
    if (!pHook) return CallWindowProc(oldProc, hWnd, uMsg, wp, lp);

    switch (uMsg)
    {
    case WM_NCCALCSIZE: 
        // 計算非客戶區尺寸:預留陰影寬度 (4px)
        if (pHook) pHook->OnNcCalcsize((NCCALCSIZE_PARAMS*)lp);
        return CallWindowProc(oldProc, hWnd, uMsg, wp, lp);
    case WM_WINDOWPOSCHANGING:
        // 視窗位置即將改變 (彈出前):擴大視窗矩形容納陰影、備份背景位圖
        if (pHook) pHook->OnWindowPosChanging((WINDOWPOS*)lp);
        break;
    case WM_PRINT:
        // 動畫彈出時系統繪製:繪製陰影
        if (pHook) pHook->OnPrint(CDC::FromHandle((HDC)wp));
        return CallWindowProc(oldProc, hWnd, uMsg, wp, lp);
    case WM_NCPAINT:
        // 非客戶區繪製:繪製陰影邊框
        if (pHook) { pHook->OnNcPaint(); return 0; }
        break;
    case WM_SHOWWINDOW:
        if (pHook) pHook->OnShowWindow(wp != 0);
        break;
    case WM_NCDESTROY:
        // 視窗銷毀:清理實例、還原 WndProc、移除屬性
        if (pHook) pHook->OnNcDestroy();
        break;
    }
    return CallWindowProc(oldProc, hWnd, uMsg, wp, lp);
}

關鍵繪製邏輯

  • OnWindowPosChanging: 若系統未啟用陰影 (IsShadowEnabled() 檢查 SPI_GETDROPSHADOW),視窗寬高各增 4px,彈出前備份陰影區域螢幕位圖 (GetScreenBitmap) 供半透明混合。
  • OnNcCalcsize: 客戶區右/下各縮 4px,騰出陰影空間。
  • OnNcPaint / OnPrint: 使用 AlphaBlend 或手動像素混合,將備份背景與陰影色 (灰/黑) 按透明度合成繪製。
  • OnNcDestroy: 從 Map 移除實例、delete this、還原 GWL_WNDPROC、RemoveProp。

系統陰影檢測

BOOL WINAPI IsShadowEnabled()
{
    BOOL bEnabled = FALSE;
    // SPI_GETDROPSHADOW 在 VC6 未定義,需自行定義 0x1024
    return ::SystemParametersInfo(0x1024, 0, &bEnabled, 0) && bEnabled;
}

使用方式

CWinApp::InitInstance 中呼叫 CMenuWndHook::InstallHook();ExitInstance 呼叫 UnInstallHook()。即可全局生效,無需修改任何選單代碼。

技術難點總結

難點解決方案
找不到選單視窗句柄全域鉤子 WH_CALLWNDPROC 攔截 WM_CREATE,由類別名 #32768 識別
鉤子回調需在 DLL 中 (全域鉤子)使用執行緒鉤子 (GetCurrentThreadId()),回調可在 EXE 中
子類化後必須完美轉發訊息保存原 WndProc 至視窗屬性 (SetProp),CallWindowProc 轉發
動畫彈出繪製 (WM_PRINT)攔截 WM_PRINT,DC 為系統傳入,繪製陰影後轉發
多選單實例管理靜態 CMap 映射 HWND 到實例,WM_NCDESTROY 及時清理

這是一個經典的「鉤子 + 子類化」系統級 UI 擴展範例,展示了 Windows 訊息機制的深層操作技巧。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved