Layered windows and UpdateLayeredWindow 分層視窗
前段摘自MSDN的詳盡介紹:
http://msdn.microsoft.com/en-us/library/ms997507.aspx
Recently I was playing with transparent (layered) windows in Windows XP.
The basic information about layered windows is available from MSDN,
however the lack of information and examples for UpdateLayeredWindow() inspired me
to write this article.
To create a layered window flag WS_EX_LAYERED must be used.
//here we create a layered window
HWND hWnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED, WNDCLASSNAME,WNDCAPTION, WS_POPUP ,WINDOW_X, WINDOW_Y, WINDOW_WIDTH, WINDOW_HEIGHT,NULL, NULL, hInstance, NULL);
It is also possible to set layered flag after creation of a regular window.
//setting extended style flag WS_EX_LAYERED);
SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) | WS_EX_LAYERED);
In short, there are two different methods to work with layered windows under Window 2000 and XP.
Using SetLayeredWindowAttributes()
The idea behind this is that you can easy create a semi transparent window and draw on this window
as you used to draw in a regular Win 32 API applications.
Application processes all regular window messages such as WM_PAINT and WM_ERASEBKGND .
SetLayeredWindowAttributes() can be used in order to change window transparency.
The function works in two alternative modes.
First mode allows to set transparency of pixels of a given color but all other pixels will be absolutely opaque.
The following code makes all white pixels on window 100% transparent
SetLayeredWindowAttributes(hWnd, RGB(0xff, 0xff, 0xff), 0, LWA_COLORKEY); // ^ ^ // | | // color key NOT USED with LWA_COLORKEY
Another mode allows to set whole window transparency for all differently colored pixels.
This example makes window 50% transparent.
SetLayeredWindowAttributes(hWnd, RGB(0xff, 0xff, 0xff), 0x7F, LWA_ALPHA); // ^ ^ // | | // not used with LWA_ALPHA opacity level (0 to 255)
What is good and bad about this method?
+ It is easy and intuitive
- It does not allow to set per pixel transparency for complex windows.
- The performance of window drawing with SetLayeredWindowAttributes() is relatively low
Using UpdateLayeredWindow()
This is another method of drawing transparent windows which works relatively faster and it offers more power for programmer.
This function does anything you want for a layered window.
You can change position, size, look and transparency all in one function call.
Also this function can use per pixel Alpha information stored in a bitmap to maintain pixel level transparency.
Note a few moments before using UpdateLayeredWindow().
- If you first call SetLayeredWindowAttributes() before UpdateLayeredWindow() you will get errors (0×06).
- Since MSDN unclearly says that hdcDst parameter is a “Handle to a device context (DC) for the screen” this actually means a window DC. I got error (number 6) trying to use screen DC.
So the basic scheme is the following.
In a timer message handler I draw the window contents on a memory DC.
Then I update window surface using this function.
//
// this function is called when hdcBackBuffer bitmap
// contains a fresh look of the window
//
void __fastcall UpdateMemoWindow(HWND hWnd){
HDC hRealDC = GetDC(hWnd);
BLENDFUNCTION bfunc;
bfunc.AlphaFormat = 0;
bfunc.BlendFlags = 0;
bfunc.BlendOp = AC_SRC_OVER;
bfunc.SourceConstantAlpha = ActiveSettings.Opacity;
if (ActiveSettings.TextOnly){
//
// BLENDFUNCTION &bfunc is not used in this case
// because we need to maintain color level opacity
//
if (!UpdateLayeredWindow(hWnd, hRealDC, &gWindowPosition, &gWindowSize, \
hdcBackBuffer, &PointZero, RGB(255,255,255), &bfunc, ULW_COLORKEY \
)){
HandleError(L"Failed to update layered window");
}
}else{
//
// the color key RGB(255,255,255) is not used in this case
// because transparency will be based on pixels of the bitmap
// selected for hdcBackBuffer
//
if (!UpdateLayeredWindow(hWnd, hRealDC, &gWindowPosition, &gWindowSize, \
hdcBackBuffer, &PointZero, RGB(255,255,255), &bfunc, ULW_ALPHA \
)){
HandleError(L"Failed to update layered window");
}
}
ReleaseDC(hWnd, hRealDC);
}
網上尋找到的關於分層視窗的總結:
1、主要是為了實現透明效果而加的一個視窗樣式(WS_EX_LAYERED)
使用 UpdateLayeredWindow 之後,視窗就忽略了 WM_PAINT訊息,所有DC繪製都要自己操作。
2、win2000以後的版本在使用者介面方面包括了幾個重大的改進,譬如有陰影的滑鼠,漸入的工具條快速提示,透明的視窗,平滑的視窗變化等等.這些變化都可以歸結為使用者對"漸變"的需求遠遠要比傳統的"躍進式"要感興趣的多,很顯然,"漸變"比"躍進式"的使用者介面要溫和的多.
其實是因為WIN2000採用了一種GDI,以前叫GDI2K,現在叫GDI+,是一種新型的圖形裝置介面,它的主要特點在於它那個建立全新的使用者桌面體系,能夠輕易的完成二維或三維的圖形處理,同同時也提供了增強的圖形處理技術,如alpha blending,紋理,貼圖,增強的文字以及圖片顯式技術.實際上GDI+主要的特色就就在於強調通過硬體加速來達到靚號的視覺感受!
透明的視窗,淡入淡出,這些在很大程度上引入了多分層視窗的應用!
分層視窗主要作用以及特點如下:
分層視窗採取"合成"(compose)的方式來繪製,【系統佔用資源低】,【支援視窗平滑變化】
分層視窗可以是【半透明】或者【透明】的
分層視窗可以是【任意形狀】,支援【變形操作】
我們知道,在傳統的windows98或者NT下面,視窗外觀發生變化(該視窗被其它視窗覆蓋,視窗大小發生變化)時,應用程式會自動維護視窗的外觀,而這種維護,是在程式設計中加入了對WM_PAINT之類的訊息響應.如果桌面視窗的外觀頻繁的發生變化,那麼,所有的視窗都回去響應"WM_PAINT"訊息,以保持視窗自身的外觀,這樣就使得每個視窗都在進行重繪操作,這樣就自然加重了作業系統的負擔,當系統忙不過來的時候,你會發現,有些視窗憂鬱在重繪過程中就產生了"抖動"~~~大大的影響了整個桌面的外觀.
分層視窗的出現使得上面的問題得到了解決,它的特點就在於,它將視窗的繪製操作進行了重新定義::【由作業系統(而不是應用程式),完成重繪操作,完成的方式是"合成":將視窗看成一副點陣圖,視窗外形的變化只是"點陣圖"的變化!而不需要非得通過對WM_PAINT訊息來進行.這樣就能夠保證分層視窗在概念上包括兩層含義:與傳統相比,這種視窗從外觀上看起來恩奇怪(它可以是透明或者半透明的,或者是異性的);二是【重定向】:對視窗的重繪操作不需要你手工新增程式碼來維護,系統會自動將重繪操作在後臺完成!
分層視窗實際上一種在WIN2000下能夠自動地與非活動視窗進行合成的一種視窗.
經過自己的實驗,發現和文章還是有一些出入,最後自己做一點總結 :
總的來說,呼叫這兩個函式中的任意一個之後,除非程式中呼叫了InvalidateRect之類的函式,否則系統將接管視窗的繪製,使用者收不到WM_PAINT訊息,
彷彿就是UpdateLayeredWindow永久改變了HDC的點陣圖一樣。而且單獨呼叫UpdateLayeredWindow而沒有呼叫過SetLayeredWindowAttributes的話
每次重新整理必須依靠UpdateLayeredWindow,普通地再WM_PAINT中處理是無效的。而呼叫SetLayeredWindowAttributes
之後就能阻斷UpdateLayeredWindow的這種行為。還有一個重要區別是單獨UpdateLayeredWindow之後視窗第一次顯示
的時候不會受到WM_PAINT.LayeredWindow視窗效率比較低,不適合大而複雜的視窗。
樓上那個兄弟是在初始化函式中(OnInitDialog,OnCreate...)呼叫了修改了視窗的style為WS_EX_LAYERED,然後緊接著
DrawUI(這個函式是他用來繪製的)。程式中沒有呼叫SetLayeredWindowAttributes所以從始至終都沒有收到過
WM_PAINT(如果沒有InvalidateRect的話)。但是這對於習慣於在WM_PAINT中處理繪製程式碼的我不太習慣,所以我把 DrawUI放在WM_PAINT中了,但是收不到這個訊息怎麼辦?可以在初始化中SetLayeredWindowAttributes這樣就
能收到一次WM_PAINT在第一次顯示的時候,但是SetLayeredWindowAttributes的 說明中MSDN:Note that once SetLayeredWindowAttributes has been called for a layered window, subsequent UpdateLayeredWindow calls will fail until the layering style bit is cleared and set again.
所以這樣也是不行的,所以可以選擇在OnPaint中修改WS_EX_LAYERED屬性而不是在初始化函式中。
需要特別注意的是UpdateLayeredWindow和SetLayeredWindowAttributes的互斥性。
另:研究一下Layered Window
前幾天吳同學問我怎麼做這樣的透明效果:
開始想得很簡單, 異型視窗+貼PNG圖就搞定了, 仔細一看沒這麼簡單, 這個鐘錶視窗, 邊緣部分是透明的, 中間部分是不透明的, 如果是全透明視窗, 建立LayeredWindow之後呼叫SetLayeredWindowAttributes即可, 但現在這個部分透明的視窗, 是要用到LayeredWindow針對每個畫素的特性, 傳說中的東西, 一直沒實踐過.
看了一下,也很簡單, LayeredWindow提供兩種模式:
1,使用SetLayeredWindowAttributes去設定透明度, 完成視窗的統一透明,此時視窗仍然收到PAINT訊息, 其他應用跟普通視窗一樣.
2, 使用UpdateLayeredWindow方法, 向系統提交包含bitmap的DC, 交由系統統一管理,此時再也收不到paint訊息, 任何對視窗的改變,只能通過UpdateLayeredWindow來修改.
如果你不需要針對畫素級別的不同透明,只需要使用SetLayeredWindowAttributes模式即可,用法與普通視窗用法一樣,有一點不同,系統會快取視窗的bitmap,所以當視窗上面的其他視窗被移開時,這是系統會去自己繪製,不會發送paint訊息。使用這種模式的好處時,你基本不用改變你使用視窗的方法,你收到paint訊息後,繪製的影象會被系統重定向到另一個函式裡面,進行組合,從而得出透明效果。
如果你需要達到針對畫素級別的不同透明,也就是上圖的想過,或者你想更加直接的去控制視窗的繪製,就必須使用UpdateLayeredWindow方法了,這個方法不重定向你的繪製結果,也不快取視窗的bitmap,而是完全由你自己來繪製,這樣在記憶體上來說,是更高效的。需要注意的是,一旦你呼叫了SetLayeredWindowAttributes,UpdateLayeredWindow的呼叫就會失敗,除非重新設定WS_EX_LAYERED,所以他們是互斥的。
對layeredwindow來說,在不完全透明的地方,是可以接收到滑鼠訊息的,在完全透明的地方,滑鼠的點選將會被穿過,還有一種情況是對視窗設定了WS_EX_TRANSPARENT屬性,滑鼠訊息也會穿過。
特別注意的是,WS_EX_LAYERED屬性是不可以設定給子視窗的。
看了一下QQ的個人資訊展示欄,底部帶有這種透明效果,抓起視窗,發現就是使用layeredwindow的UpdateLayeredWindow模式:
而百度Hi的資訊欄,就沒有這種效果了:
雖然也是採用的layeredwindow,但是隻是為了漸隱效果採用的。採用的,是SetLayeredWindowAttributes模式。
對於這種透明的效果,另外又有幾種旁門左道可以實現,但終歸有缺陷,總結一下,有如下幾種:
1,利用異形視窗實現,由於子視窗不能被設定WS_EX_LAYERED,所以,可以給定A、B、C三個視窗,其中,A為全透明視窗,B為UpdateLayeredWindow型的layered視窗,C為正常子視窗,C的父親為A,B為A的popup視窗。這樣,在C上面有不透明的東西,B上有任意透明的東西,唯一要做的,就是當ABC三視窗有任意一個移動或者改變大小時,其他兩個都要相應變化。
這個方案的缺點,一是不適合大規模使用,第二是有同事試驗過,移動時如果移動過快會留下殘影,這種方案適用於不移動視窗的簡單程式。
2,利用截圖背景實現,視窗顯示時,對視窗區域的螢幕進行截圖,截完之後,設定為視窗的背景。這種方案的缺點,也是視窗不能移動,並且背後不能有動畫。只能是一種假透明。此種方案適用於複雜程式,在手機上變成使用的比較多,因為手機的視窗很多是全屏,不移動。