前几天看到一位兄弟模仿Kugou7界面做的一个效果,下载源码一看之后发现在WM_PAINT没有一句自己的代码,只有DrawUI中有绘制的代码。顿时为之一震,原来UpdateLayeredWindow还有种这样的效果。于是乎在网上找了一下资料:
原文地址:http://alexkr.com/source-code/50/layered-windows-and-updatelayeredwindow/
Layered windows and UpdateLayeredWindow
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); }
经过自己的实验,发现和文章还是有一些出入,最后自己做一点总结 :
总的来说,调用这两个函数中的任意一个之后,除非程序中调用了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,利用截图背景实现,窗口显示时,对窗口区域的屏幕进行截图,截完之后,设置为窗口的背景。这种方案的缺点,也是窗口不能移动,并且背后不能有动画。只能是一种假透明。此种方案适用于复杂程序,在手机上变成使用的比较多,因为手机的窗口很多是全屏,不移动。