如何實現視窗陰影效果 (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。也就是說即使視窗上面有一塊地方是完全透明的,你用滑鼠在那點選也會點在視窗上面,而不是透過視窗
效果如下: