一种给窗口添加阴影的方法 华南理工大学微软技术俱乐部 |
因为自己很喜欢那些界面做得很漂亮的软件或者使用各种美化界面的软件,如avedesk,samurize等等。其中美化界面的一个重要的方面就是给窗口添加上阴影。虽然OS X已经原生的支持窗口阴影,但是windows要到Longhorn才开始支持原生的窗口阴影。现在如果想实现窗口阴影,一般都会借助第三方的软件,例如windowFX或者YzShadow。其中YzShadow是一个免费软件,我自己也在使用。但是这个软件有个弱点,就是无法为Layered Window添加阴影。而我自己编写的一个简易便条程序Stickies(功能类似OneNote,但是功能简单,小巧。)就是运用了Layered Window来作为软件的界面,于是便自己尝试添加窗口阴影。以下便是添加阴影的方法,写下来与大家讨论一下。 我的程序是在Visual Studio.NET 2003下编写的MFC应用程序。我为了实现窗口阴影创建了一个Shadow的类。首先我们看看各类之间的关系:
ShadowCastingWindow成员函数: 该函数用于创建应用程序的窗口并创建阴影。请留意CreateEx中窗口属性WS_EX_...和WS_...的取值,这使得该应用程序的窗口是一个没有标题栏的Layered Windows。是否有标题栏对于下文Shadow类中求遮挡窗口的大小会有所不同,这必须通过一个判断逻辑或者根据程序的应用不同编写好代码。对于Layered Windows会有两种刷新模式,一种就是传统的消息机制,就是操作系统自动地在适当的时候发送WM_PAINT的消息给应用程序窗口,应用程序窗口则相应该消息,对窗口进行刷新;另一种方式则是在Windows2000以后才支持的UpdateLayeredWindow的机制,在这种机制下,应用程序不再处理WM_PAINT消息,所有的刷新均由用户在内存中的一个绘图上下文中绘制好图像之后再通过UpdateLayeredWindow绘制到屏幕上,只要经过一次绘制,窗口的图像便会保存在一块预订好的内存区域内,如果窗口的图像没有改变那么操作系统便会自动地处理刷新。 void ShadowCastingWindow::OnSizing(UINT fwSide, LPRECT pRect) void ShadowCastingWindow::OnSize(UINT nType, int cx, int cy) void ShadowCastingWindow::OnMoving(UINT fwSide, LPRECT pRect) void ShadowCastingWindow::OnMove(int x, int y) 这四个事件函数都是处理应用程序窗口大小或者位置变化的。只需要在其中调用Shadow类中相应的处理函数即可,Shadow便会自动地更改大小或者移动位置。可能有人会问为什么需要显式地调整Shadow的位置和大小?因为从下文可以看到Shadow其实也是一个Layered Window,没有父窗口,所以操作系统不可以自动地保持两者的相对位置。 void ShadowCastingWindow::SetOpacity( int alpha ) 处理应用程序窗口透明度变化的函数,其中调用了阴影Shadow对于透明度变化的处理函数。并刷新应用程序窗口。 2. Class Shadow Shadow成员函数: m_IsCreated = true; 创建阴影,由于阴影必须是没有标题栏的,而且因为要绘制半透明的像素所以必须使用Layered Window。 void Shadow::CustomizedPaint(void) BLENDFUNCTION blendPixelFunction= {AC_SRC_OVER, 0, m_Alpha, AC_SRC_ALPHA}; CDC * dcScreen = GetDesktopWindow()->GetDC(); //----------------------------------- UpdateLayeredWindow( dcScreen, &ptWindowScreenPosition, &szWindow, &dcMemory, &ptSrc, 0, &blendPixelFunction, ULW_ALPHA); GetDesktopWindow()->ReleaseDC(dcScreen); BOOL Shadow::PreCreateWindow(CREATESTRUCT& cs) void Shadow::OnShadowCastingWndNewSize( int x, int y ) 提供该投射阴影的窗口的接口函数,当投射阴影的窗口大小改变的时候便调用这个函数把新的窗口位置传给Shadow,Shadow便会改变自己的大小,并重绘窗口。 void Shadow::OnShadowCastingWndNewPos( int x, int y ) 提供该投射阴影的窗口的接口函数,当投射阴影的窗口位置改变的时候便调用这个函数把新的窗口位置传给Shadow,Shadow便会改变自己的位置。注意了,这里并不需要重绘窗口,因为窗口的内容并没有改变。 void Shadow::SetAlpha( int alpha ) 提供该投射阴影的窗口的接口函数,当投射阴影的窗口的透明度改变的时候便调用这个函数把新透明度传给Shadow,Shadow便会改变自己的透明度,并重绘窗口。 下面给出一个我自己写的程序的效果图: 3. 结论: 上面就简单地介绍了一个绘制窗口阴影的方法。这种方法基本上可以适用于各种类型的窗口,其中需要注意一下几点: 1. 在于Shadow::CreateShadow中如何正确取得投射阴影窗口m_pShadowCastingWindow的大小然后计算出阴影窗口的大小。 2. Shadow::CustomizedPaint中如何更高效的绘制阴影,例如剪裁掉投射阴影窗口遮挡住的窗口内容,避免绘制时出现闪烁。同时如何正确使用好UpdateLayeredWindow这个系统调用会是实现绘制阴影的关键。当然在当前的设计下,我们可以在CustomizePaint中绘制任何的东西,而不一定是阴影。? 大家可以在这里发挥想象力,让窗口更加绚丽多彩。 其实这个程序只要让他通过钩子函数与特定的Win32API挂钩,完全可以写出一个可以给系统中所有窗口加上阴影效果的小软件。大家不妨试试。如果做出来了,记得给我一份。 注:文章中的代码都是示意性的,都是通过我自己写的程序删减后得到,未必能通过测试。旨在说明一些关键步骤需要注意的地方。如果问题欢迎email讨论。 |