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

利用 Hook 實現經典選單陰影效果(支援 Windows XP 以前版本)

     編輯:關於VC++

前言

Office XP 的選單陰影效果令人羨慕:無需 Windows XP 即可顯示陰影,而在 XP 系統則由原生支援。雖非核心功能,但能讓程式外觀脫穎而出。本文示範如何透過全域 Hook 攔截選單視窗建立,實現自繪陰影。

核心思路

選單本質是一個視窗(類別名 #32768),若能取得其 HWND,子類化後即可自繪非客戶區(陰影)。但選單視窗由系統內部建立,應用層無從獲取句柄。解決之道:安裝 WH_CALLWNDPROC Hook,在 WM_CREATE 或特定訊息(0x01E2)時刻攔截、驗證類別名,隨即子類化並註冊管理。

關鍵類別:CMenuWndHook

靜態成員

static CMap m_WndMenuMap;   // HWND -> CMenuWndHook*
static HHOOK m_hMenuHook;   // Hook 句柄

安裝與卸載 Hook

void CMenuWndHook::InstallHook()
{
    if (m_hMenuHook == NULL)
    {
        m_hMenuHook = ::SetWindowsHookEx(WH_CALLWNDPROC,
            WindowHook, AfxGetApp()->m_hInstance, ::GetCurrentThreadId());
    }
}

void CMenuWndHook::UnInstallHook()
{
    POSITION pos = m_WndMenuMap.GetStartPosition();
    while (pos)
    {
        HWND hwnd; CMenuWndHook* p;
        m_WndMenuMap.GetNextAssoc(pos, hwnd, p);
        delete p;
    }
    m_WndMenuMap.RemoveAll();
    if (m_hMenuHook) ::UnhookWindowsHookEx(m_hMenuHook);
}

Hook 回調:攔截選單建立

LRESULT CALLBACK CMenuWndHook::WindowHook(int code, WPARAM wParam, LPARAM lParam)
{
    CWPSTRUCT* pStruct = (CWPSTRUCT*)lParam;
    while (code == HC_ACTION)
    {
        HWND hWnd = pStruct->hwnd;
        // 僅關心 WM_CREATE 或 0x01E2
        if (pStruct->message != WM_CREATE && pStruct->message != 0x01E2) break;

        // 驗證類別名 "#32768"(6 字元)
        TCHAR szClass[10];
        int len = ::GetClassName(hWnd, szClass, 10);
        if (len != 6 || _tcscmp(szClass, _T("#32768")) != 0) break;

        // 已子類化則跳過
        if (::GetProp(hWnd, CoolMenu_oldProc)) break;

        // 註冊管理
        VERIFY(AddWndHook(hWnd) != NULL);

        // 子類化:偷梁換柱
        WNDPROC oldProc = (WNDPROC)::GetWindowLong(hWnd, GWL_WNDPROC);
        if (!oldProc || oldProc == CoolMenuProc) break;
        if (!::SetProp(hWnd, CoolMenu_oldProc, oldProc)) break;
        if (!::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)CoolMenuProc))
        {
            ::RemoveProp(hWnd, CoolMenu_oldProc);
            break;
        }
        break;
    }
    return CallNextHookEx(m_hMenuHook, code, wParam, lParam);
}

新視窗程序:處理關鍵訊息

CoolMenuProc 處理 WM_NCCALCSIZE(預留陰影區域)、WM_WINDOWPOSCHANGING(擴大視窗含陰影)、WM_PRINT/WM_NCPAINT(繪製陰影與邊框)、WM_NCDESTROY(清理)等。核心繪製邏輯在 OnPrint 與全域 DrawShadow:

void DrawShadow(CDC* pDC, CRect rect)
{
    // 底部、右側、四角漸變陰影,使用 AlphaArray 控制透明度
    // 逐像素 GetPixel/SetPixel 實現半透明混合
    // 速度可接受(選單重繪不頻繁)
}

系統原生陰影檢測

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

若系統已啟用陰影,直接跳過自繪。

應用程式整合

#include "MenuWndHook.h"
BOOL CNewmenuApp::InitInstance()
{
    // ...
    CMenuWndHook::InstallHook();
    return TRUE;
}
int CNewmenuApp::ExitInstance()
{
    CMenuWndHook::UnInstallHook();
    return CWinApp::ExitInstance();
}

搭配自繪選單類,即可打造極具質感的選單介面。

最終成品截圖

結語

VC 預設控件外觀確實較「老土」,但精美介面終究靠開發者自行打造。Hook 技術雖屬偏門,善用之可突破框架限制。歡迎高手指點優化。

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