最近两周使用透明、半透明窗口比较多,在此之前我写代码都借助封装好了的皮肤库,而现在都是“手写”的——石器时代大冒险,遇到了一些困难,在此做总结。
1、GDI函数对Alpha值的忽视。
GDI函数只有AlphaBlend api可以提供alpha通道的绘制,使用AlphaBlend可以实现32位位图的绘制。因为只有这个api能识别alpha通道,如果在一个MemDC上用DrawText绘制文本,这些文字区域的alpha值都为0,之后再使用AlphaBlend api把MemDC的位图拷贝到实际DC,就会导致文字区域异常,要么是透明了,要么是变成纯白色了。解决办法是:1、在能达到目的的前提下,从MemDC拷贝到实际DC时,不用AlphaBlend混合拷贝,而是使用Bitblt简单拷贝;2、1可能无法达到想要的半透明效果,那么就只能在混合拷贝之前遍历文字区域的每一个像素,把它的alpha值修改为255[可参考http://bbs.csdn.net/topics/360021944];3、最方便的做法是引入GDI+,使用CImage,GDI+的文本绘制带有Alpha属性值。
2、AlphaBlend API和透明度
Alpha通道值。0表示完全透明,255表示完全不透明。
AlphaBlend函数里的BLENDFUNCTION结构体里的SourceConstantAlpha成员,指示的是整个图片的Alpha值。0,表示源图片完全透明,255,表示使用源图片的每个像素自己的Alpha值。
AlphaBlend函数产生的图片让源、目标、SourceConstantAlpha混合。有一套复杂的计算:http://msdn.microsoft.com/en-us/library/windows/desktop/dd183393(v=vs.85).aspx
计算涉及到这么几种情况:
1、没有设置AC_SRC_ALPHA 属性,只有SourceConstantAlpha做混合;
2、设置了AC_SRC_ALPHA 属性,同时把SourceConstantAlpha设置为0xFF,这时候,只使用源图片每个像素的Alpha值做混合;
3、设置了AC_SRC_ALPHA属性,同时把SourceConstantAlpha设置为非0xFF,这时候既使用SourceConstantAlpha的值也使用源图片每个像素的Alpha值做混合。
按照MSDN公式的介绍,当SourceConstantAlpha的值为0的时候,则源DC的图片在目标DC上完全不显示了,源DC的RGB值都丢失了。当SourceConstantAlpha为255时,则使用源DC的图片的Alpha值。
当使用AlphaBlend的时候,只是把数据从一个DC拷贝到另一个DC。所以,如果是要把一个图片贴到目标DC,必须是先把图片选入到MemDC,然后再使用AlphaBlend函数把图片从MemDC拷贝到实际DC。
使用这个API,可以实现图片切换时的平缓过渡效果,上一个图片慢慢的消失,下一个图片慢慢的呈现。
3、AlphaBlend失败的可能原因
参数错误失败原因1: 如果位图的bmBitsPixel为24,那么是无法使用该函数的。
所以对于来源未知的图片,安全的做法是
BITMAP bitmapInfo;
:: GetObject(hBitMap, sizeof( BITMAP), &bitmapInfo);
if (bitmapInfo. bmBitsPixel == 24) //24位的位图就直接使用Bitblt/StretchBlt
{...........}
参数错误失败原因2:可能参数使用错了,源dc的位图跟参数里面指示源位图高度、宽度的值不相符。
绘制效果很奇怪:之前以为使用了AlphaBlend之后就没法使用Bitblt了,实际上是可以的,并不会导致alpha通道丢失。倒是这样子会有问题:通过alphablend往memdc上绘制图片,然后使用DrawText往memdc上绘制文字,最后使用alphablend把memdc的内容拷贝到实际DC,这就会导致文字变成白色的。所以,最后一步不能使用alphablend,必须使用bitblt这类函数。
4、GDI+绘制文本
GDI+的DrawString可以绘制文本,如果用GDI+绘制文本再加上使用WS_EX_LAYER属性,那么GDI+绘制的文本必须使用UpdateLayeredWindow拷贝到目标区域。不能直接在有WS_EX_LAYER属性的窗口上使用GDI+绘制,必须先绘制然后使用UpdateLayeredWindow函数拷贝。
5、使用GDI+
VS2008需要加上头文件:#include <objbase.h>
GDI+初始化:
ULONG_PTR m_GdiToken;
GdiplusStartupInput m_GdiInput;
GdiplusStartup(&m_GdiToken ,&m_GdiInput ,NULL );
GDI+销毁
GdiplusShutdown(m_GdiToken );
6、WS_EX_LAYERED窗口无法显示
只有WS_EX_LAYERED属性,而没有调用SetLayeredWindowAttribute或者UpdateLayeredWindow函数,那将导致窗口无法显示。
7、如何绘制部分透明的窗口
1)切出部分透明的图;
2)创建有WS_EX_LAYERED属性的窗口;
3)把图片选进MemDC,使用AlphaBlend函数把图片混合拷贝到另一个MemDC;
4)由于GDI的DrawText函数会把该区域像素的alpha值清0,导致透明,所以建议使用GDI+的DrawString函数;
5)最后使用UpdateLayeredWindow把MemDC拷贝到实际DC。
8、SetLayeredWindowAttributes和UpdateLayeredWindow的区别
两者在win7以下的版本都要求必须是带有WS_EX_LAYERED属性的顶层窗口(非WS_CHILD属性的窗口),windows 8之后的版本开始支持非顶层窗口。
SetLayeredWindowAttributes函数可以实现:1、对某一个RGB值实现透明;2、可以实现对整个窗口都使用同一个透明度。
UpdateLayeredWindow函数可以实现局部半透明。而且这个函数是GDI+的函数,使用时需要初始化GDI+。
9、 UpdateLayeredWindow不支持局部更新
UpdateLayeredWindow函数包含了SetWindowPos设置窗口位置、大小的功能。这个函数不支持局部更新绘制区域。
10、不能把MAKEINTRESOURCE之后的值赋值给wstring
因为这不是一个string,如果发生赋值,会导致触发WM_PAINT消息(很莫名其妙),导致错误。