分类: C/C++
2008-03-12 08:42:04
有没有方法创建一个半透明的窗口,并将该窗口上发生的所有鼠标事件都传递到桌面或另一个应用窗口处理? Scott Stringham 当然可以,并且相当容易。你只要创建一个“分层窗口”即可。我写了一个小程序叫 lwtest 来示范如何做。你可以下载源代码。为了创建分层窗口,你需要扩展式样 WS_EX_LAYERED,此外,为了能在透明窗口上进行鼠标点击,你还需要 WS_EX_TRANSPARENT 扩展式样。在窗口创建之后,你可以同时设置两个式样,MFC 代码如下:int CMainFrame::OnCreate(...) { ... ModifyStyleEx(0, WS_EX_LAYERED|WS_EX_TRANSPARENT); } ModifyStyle 和 ModifyStyleEx 是专用的 MFC CWnd 方法,其作用顾名思义。如果你用 C 语言编写,那么得调用 GetWindowLong(GWL_EXSTYLE) 来获取扩展式样,然后必须调用 SetWindowLong(GWL_EXSTYLE)来设置式样。其效果与 ModifyStyle(Ex)一样。当然,你也可以在创建窗口的时候使用此式样。 // in CMainFrame::OnCreate SetLayeredWindowAttributes(0, 255 * 0.50, LWA_ALPHA); 这里我用乘法来表示一般公式;你可以仅用 128,因为那是 255 的一半(四舍五入)。你还可以用专门的颜色作为透明色。此时,你得用 LWA_COLORKEY 作为属性,在第一个参数中指定 COLORREF。Windows 会让所有像素颜色都呈透明。注意前面的代码段假设你是从 CWnd 派生对象中调用。如果用 C 语言,你得使用 ::SetLayeredWindowAttributes,它带有一个额外的参数 HWND。 Bob Kline 借助 COM 确实可以在 IE 中实现转换效果。这些效果包括——渐变、擦除,框入、框出、棒状等等——在DirectX 中都支持。具体细节已经超出了本文的讨论范围,所以我只能让你去看相关文档,其内容参见“Internet Development SDK”中的“Using Transforms in C++”。你需要熟悉 COM 以及一些基本的 DirectX 知识,如:表层(surfaces)和转换(transforms)(DXSurface 和 DXTransform)。如果你仅仅是想实现图像到图像的渐变,我可以给你示范如何用 GDI+ 函数 AlphaBlend 来实现,微软的老大们在 MFC 中已经对之进行了足够友好的包装,CDC::AlphaBlend。AlphaBlend 中的 alpha 是一个图形学术语。它表示位图使用3个字节来说明一个像素:每个字节分别表示 红、绿、蓝的值。由于 32位的 DWORD 有4个字节,多余的这个字节常被用作“alpha channel”,用于指定像素的透明度。这个 alpha 值按照如下的公式来合并像素: [R,G,B]blended = ?[R,G,B]image + (1-?? [R,G,B]background 当 alpha 为 0 时,你得到的是背景(图像完全透明);当 alpha 为 1 时,你得到非透明图像(完全不透明)。实际有透明效果的 alpha 值是一个 8 位的字节表示的值,范围从0-255,0 和 1 只是表示透明和非透明两个极端。它们都是可用的 alpha 值,但大多数应用程序不需要;多数应用程序使用一个常量 alpha 值来处理整个对象,如一幅图像。例如,你可能想让一幅特定的图像以25%的透明度显示。 // in Doc.cpp: BOOL CPictureDoc::OnOpenDocument(LPCTSTR lpszPathName) { UpdateAllViews(NULL, PREOPENDOC, this); return m_pict.Load(lpszPathName); } PREOPENDOC 是我自己的枚举代码,在 doc.h 中定义。当你调用 UpdateAllViews 时,将自己的“提示代码”(一个32位整数)随一个指针传递到“提示对象”,该对象可以是任何 CObject 派生的 MFC 类。这里我传的是文档本身。注意我是在加载新图像之前调用 UpdateAllViews,而旧图像仍然有效。视图处理通知消息保存该图像: void CPictureView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { if (lHint==CPictureDoc::PREOPENDOC) { SaveDocImage((CPictureDoc*)pHint); } } 相同的 OnUpdate 函数处理所有文档的通知消息,所以你得检查发送了哪个通知消息。一般情况下,提示代码和提示对象背后的工作原理是文档以提示方式提供信息,告诉视图它需要更新屏幕的哪一部份。对于 CPictureView 来说,如果提示代码是 PREOPENDOC,那么 CPictureView则调用一个辅助函数 SaveDocImage 来保存当前图像。Figure 3 是 SaveDocImage 的代码,它创建一个位图和内存设备上下文(DC),然后在内存设备上下文中呈现图像,在文档摧毁原来图像后有效地进行渐变拷贝。 void CPictureView::OnDraw(CDC* pDC) { CPicture* ppic = // get current picture if (m_iStartTime) { // do blend } else { // render as normal ppic->Render(pDC,rc); } } 我省略了渐变的细节,主要突出 CPictureView 如何用 m_iStartTime 作为标志来确定是否渐变。以下是实现渐变需要的基本步骤。
在画面以外的内存 DC 中进行渐变然后拷贝到屏幕这一步是很重要的;否则用户将会看到一闪而过的中间图像。因为 AlphaBlend 需要设备上下文,而不是 CPicture 对象,首先绘制新图像(所以我调用 CPicture::Render ),然后在其上渐变旧图像要方便一些。所以我用的 alpha 值与先从旧的图像开始显示所用的 alpha 值相反转(1-alpha) ,换句话说,不是先从旧图像开始,然后在上面以越来越多的效果渐变新图像。我是先从新图像开始,然后在上面以越来越少的效果渐变旧图像。很聪明,不是吗?网格效果处理方法一样。以下是计算 AlpahBlend alpha 值的关键代码行: int alpha = ((clock() - m_iStartTime) * 255) / BLEND_DURATION; alpha = max(255-alpha,0); 渐变之后,如果计算的 alpha 值大于 0,那么就需要处理更多的渐变效果。所以 OnDraw 调用 Invalidate(FALSE) 在不擦除背景的情况下而重画窗口。Windows 发送另一个 WM_PAINT 消息——只是要等到当前消息处理完成。这样一来(使 WM_PAINT 为有效消息),没有阻塞。在渐变期间,用户仍然能使用应用程序。你可以在渐变期间改变窗口大小来证明这一点。CPictureView 在新的窗口尺寸下保持渐变。 |
作者简介 Paul DiLascia 是一名自由作家,软件咨询顾问以及大型 Web/UI 的设计师。他是《Writing Reusable Windows Code in C++》书(Addison-Wesley, 1992)的作者。业余时间他开发 PixeLib,这是一个 MFC 类库,从 Paul 的网站 可以获得这个类库。 |