1. 程式人生 > >如何實現視窗陰影效果 (SysShadow、分層視窗、DWM)

如何實現視窗陰影效果 (SysShadow、分層視窗、DWM)

方法一、系統屬性:

 

為了實現陰影效果,這兩天google了不少,終於從 SysShadow 中找到了一點線索。

給視窗新增陰影:

    SetClassLong(this->m_hWnd, GCL_STYLE, GetClassLong(this->m_hWnd, GCL_STYLE) | CS_DROPSHADOW);

在OnCreate(CmainFrame)或者OnInitDialog(Cdialog)裡面新增即可,該屬性與是否選中系統的“顯示選單下面的陰影”有關係。不規則視窗(SetWindowRgn)一樣可以將顯示不規則陰影


 

方法二、分層視窗

 

        採用 WS_EX_LAYERED 擴充套件屬性 + UpdateLayeredWindow + PNG圖片 + GdiPlus 可以實現很強大的不規則窗體,而不用像利用 BMP 圖去摳每一個畫素點。不過利用分層視窗實現有一個很大的缺陷,就是分層視窗不能是子視窗,分層之後的視窗的子控制元件(包括子視窗)不會再顯示了,雖然控制元件仍然可以響應到訊息。

解決辦法有兩個:

 1. 附加一個POPUP的視窗專門用來放置視窗,並且該視窗可以用 SetLayeredWindowAttributes 將該POPUP視窗也透明一些,然後這兩個視窗同時移動即可(這可能要用到DeferWindowPos來同步移動視窗)。

 2. 採用DirectUIHWND的方法,由自己來繪製所有的控制元件,這個工作量很大,但效果是最好的。但是對於一些特殊控制元件,如IE Webbrowser,則只能採用第1種方法了;Windowless RichEidt如果在分層視窗中繪製的話,TxDraw函式的HDC來源也是一個比較麻煩的事:用GDI的HDC,則沒有Alpha channel,還需要在繪製完之後去填充一下Alpha通道(Win7下可以考慮下BufferedPaintSetAlpha,還沒試過),用Gdiplus的GetHDC函式,則效率太低,不能滿足。

 

UpdateLayeredWindow函式使用介紹:

 

SetLayeredWindowAttributes與UpdateLayeredWindow的區別:

       前者只能將整個對話方塊透明(包括上面的子視窗與控制元件),而後者的強大之處在於可以自定義每一個畫素點的alpha值,實現任意效果.

 

       UpdateLayeredWindow 函式的使用其實有點類似於 SetWindowPos,沒用過的時候看起來很複雜。UpdateLayeredWindow 可以設定分層視窗的座標和大小(想想SetWindowPos的作用,利用這一點我們可以實現完全無閃爍的視窗拉伸。不知道為什麼QQ沒有實現這一點)。

 

       該函式的引數分析如下:

BOOL UpdateLayeredWindow(         
 HWND hwnd,         // 視窗控制代碼
    HDC hdcDst,        // 取 NULL 即可
    POINT *pptDst,     // 分層視窗的新位置。不改變位置時可設定為NULL.建議儲存為一個成員變數
    SIZE *psize,       // 分層視窗的新大小。不改變位置時可設定為NULL.建議儲存為一個成員變數
    HDC hdcSrc,        // 記憶體HDC控制代碼,該HDC的HBITMAP中儲存了分層視窗的內容。
    POINT *pptSrc,     // 設定為POINT pt = {0,0}; 即可
    COLORREF crKey,    // 一種使用關鍵色來實現透明的方法。一般都使用alpha來實現,這裡設定0即可
    BLENDFUNCTION *pblend,  // 混合引數
    DWORD dwFlags);    // 取ULW_ALPHA。

這裡面最關鍵的就是HDC hdcSrc了。 

HDC可以用CreateCompatibalDC(NULL)來建立,但選進HDC中的HBITMAP則必須建立為32位色,使其能夠帶alpha channel。例如:

  CImage image;
  image.Create( rc.Width(), -rc.Height(), 32, CImage::createAlphaChannel );

 

BLENDFUNCTION結構的設定可以參考如下:

 BLENDFUNCTION bf;
 bf.BlendOp     = AC_SRC_OVER ;
 bf.AlphaFormat = AC_SRC_ALPHA;

 bf.BlendFlags  = 0;                             // Must be zero. 
 bf.SourceConstantAlpha = 255;         // 0~255 透明度,這裡的效果類似於SetLayeredWindowAttributes函式。取255即可,因為我們使用hdcSrc中的alpha值。

 

 

 

 

WS_EX_TRANSPARENT,加上該屬性就能實現“滑鼠穿透”效果。視窗周圍的陰影肯定也是需要加上該屬性的。

// 滑鼠拖動視窗效果

UINT OnNcHitTest(CPoint point)

{

    return HTCAPTION;

}

 

方法三、DWM

 

 MARGINS m = {-1};
 DwmExtendFrameIntoClientArea(m_hWnd, &m);

 

在win7下面採用DWM就能夠讓視窗識別alpha channel,例如使用 BLACK_BRUSH 去重新整理視窗背景,如果視窗有標題欄,則這整個視窗都能看到毛玻璃效果,如果視窗沒有標題欄,則整個視窗都是透明的(與分層視窗不同,視窗沒有被摟空)。

因此如果使用PNG去繪製視窗背景的話,就能使實現視窗陰影,但同樣需要注意:gdi沒有alpha通道

 

那QQ 2012在win7下,能夠顯示陰影+毛玻璃效果的特性是怎麼實現的呢?

要實現毛玻璃效果,得呼叫另外一個DWM函式:DwmEnableBlurBehindWindow。需要注意的是毛玻璃的範圍區域不能包含四周的陰影區域,否則陰影區域也會顯示成毛玻璃效果了。例如:

 

HRGN hRgn = CreateRectRgn(10,10,500,500);
 DWM_BLURBEHIND blurbehind = {0};
 blurbehind.dwFlags = DWM_BB_ENABLE|DWM_BB_BLURREGION|DWM_BB_TRANSITIONONMAXIMIZED;
 blurbehind.fEnable = true;
 blurbehind.hRgnBlur = hRgn;
 blurbehind.fTransitionOnMaximized = TRUE;
 DwmEnableBlurBehindWindow(m_hWnd, &blurbehind);
 DeleteObject(hRgn);

 

PS: DWM雖然能使得視窗按照alpha channel 進行透明,但與分層視窗不同的是,它不處理HITTEST。也就是說即使視窗上面有一塊地方是完全透明的,你用滑鼠在那點選也會點在視窗上面,而不是透過視窗

 

效果如下: