基于Win32 API与Direct2D实现带透明阴影的圆形窗口方案咨询
最优实现方案:Direct2D圆形窗口+透明穿透阴影
核心思路
结合WS_EX_LAYERED窗口样式,用UpdateLayeredWindow替代SetLayeredWindowAttributes,配合Direct2D离屏渲染生成带Alpha通道的位图实现正确透明混合;通过WM_NCHITTEST消息区分圆形交互区域与阴影穿透区域,同时解决闪烁问题。
实现步骤
1. 窗口创建与样式配置
- 注册窗口类时设置
hbrBackground = NULL,禁用GDI背景绘制 - 创建窗口时使用以下样式:
- 主样式:
WS_POPUP | WS_VISIBLE(无标题栏的自定义窗口) - 扩展样式:
WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP(分层窗口+禁用DWM重定向位图,减少闪烁)
- 主样式:
HWND CreateCustomWindow(HINSTANCE hInstance, int x, int y, int width, int height) { WNDCLASSEX wc = {0}; wc.cbSize = sizeof(WNDCLASSEX); wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.lpszClassName = L"Direct2DCircleWindow"; wc.hbrBackground = NULL; // 禁用GDI背景 RegisterClassEx(&wc); return CreateWindowEx( WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP, L"Direct2DCircleWindow", L"", WS_POPUP | WS_VISIBLE, x, y, width, height, NULL, NULL, hInstance, NULL ); }
2. Direct2D离屏渲染配置
创建ID2D1BitmapRenderTarget作为离屏画布,确保支持Alpha通道,依次绘制高斯模糊阴影与圆形主体:
- 清空画布为全透明
- 绘制比圆形大的半透明椭圆作为阴影源,再应用高斯模糊效果
- 绘制不透明的圆形主体,最终生成带Alpha通道的位图
ID2D1Bitmap* RenderCircleWithShadow(ID2D1Factory* pD2DFactory, ID2D1HwndRenderTarget* pHwndRT, float radius, int width, int height) { ID2D1BitmapRenderTarget* pBitmapRT = nullptr; pHwndRT->CreateCompatibleRenderTarget( D2D1::SizeF(width, height), &pBitmapRT ); // 清空为全透明 pBitmapRT->Clear(D2D1::ColorF(D2D1::ColorF::Transparent)); // 创建阴影画刷(半透明黑色) ID2D1SolidColorBrush* pShadowBrush = nullptr; pBitmapRT->CreateSolidColorBrush( D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.3f), &pShadowBrush ); // 绘制阴影源(比圆形大的椭圆) pBitmapRT->DrawEllipse( D2D1::Ellipse(D2D1::Point2F(width/2.0f, height/2.0f), radius+10.0f, radius+10.0f), pShadowBrush, 0.0f ); // 应用高斯模糊效果 ID2D1Effect* pBlurEffect = nullptr; pD2DFactory->CreateEffect(CLSID_D2D1GaussianBlur, &pBlurEffect); pBlurEffect->SetInput(0, pBitmapRT->GetBitmap()); pBlurEffect->SetValue(D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION, 8.0f); pBitmapRT->DrawImage(pBlurEffect); // 创建主体画刷(示例蓝色) ID2D1SolidColorBrush* pMainBrush = nullptr; pBitmapRT->CreateSolidColorBrush( D2D1::ColorF(0.2f, 0.5f, 1.0f, 1.0f), &pMainBrush ); // 绘制圆形主体 pBitmapRT->FillEllipse( D2D1::Ellipse(D2D1::Point2F(width/2.0f, height/2.0f), radius, radius), pMainBrush ); // 获取最终位图 ID2D1Bitmap* pFinalBitmap = nullptr; pBitmapRT->GetBitmap(&pFinalBitmap); // 释放临时资源 pShadowBrush->Release(); pMainBrush->Release(); pBlurEffect->Release(); pBitmapRT->Release(); return pFinalBitmap; }
3. 更新分层窗口与透明混合
使用UpdateLayeredWindow将离屏渲染的位图更新到窗口,利用位图自身的Alpha通道实现正确的透明混合:
void UpdateLayeredWindowFromBitmap(HWND hwnd, ID2D1Bitmap* pBitmap, int x, int y) { D2D1_SIZE_U bitmapSize = pBitmap->GetPixelSize(); HDC hdcScreen = GetDC(NULL); HBITMAP hBitmap = nullptr; // 将Direct2D位图转换为HBITMAP pBitmap->GetHBITMAP(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f), &hBitmap); BLENDFUNCTION blend = {0}; blend.BlendOp = AC_SRC_OVER; blend.SourceConstantAlpha = 255; // 完全使用位图自身的Alpha通道 blend.AlphaFormat = AC_SRC_ALPHA; POINT ptSrc = {0, 0}; POINT ptDst = {x, y}; SIZE sizeWnd = {bitmapSize.width, bitmapSize.height}; // 更新分层窗口 UpdateLayeredWindow( hwnd, hdcScreen, &ptDst, &sizeWnd, CreateCompatibleDC(hdcScreen), &ptSrc, 0, &blend, ULW_ALPHA ); // 释放资源 DeleteDC(CreateCompatibleDC(hdcScreen)); DeleteObject(hBitmap); ReleaseDC(NULL, hdcScreen); }
4. 鼠标穿透逻辑实现
在窗口过程中处理WM_NCHITTEST消息,判断鼠标位置:
- 圆形区域内返回
HTCLIENT,接收鼠标事件 - 阴影区域返回
HTTRANSPARENT,让系统将鼠标事件传递给下方窗口 - 处理
WM_ERASEBKGND返回TRUE,禁止系统擦除背景避免闪烁
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static float radius = 100.0f; static int width = 240; static int height = 240; switch(msg) { case WM_NCHITTEST: { POINT pt = {LOWORD(lParam), HIWORD(lParam)}; ScreenToClient(hwnd, &pt); // 计算鼠标是否在圆形范围内 float dx = pt.x - width/2.0f; float dy = pt.y - height/2.0f; if (dx*dx + dy*dy <= radius*radius) { return HTCLIENT; } // 阴影区域,允许鼠标穿透 return HTTRANSPARENT; } case WM_ERASEBKGND: return TRUE; // 禁用系统背景擦除 case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd, msg, wParam, lParam); } }
方案优势
- 解决混合异常:通过
UpdateLayeredWindow+带Alpha通道的Direct2D位图,实现阴影与桌面/下方窗口的正确透明混合,避免SetLayeredWindowAttributes的黑色背景混合问题。 - 精准鼠标穿透:通过
WM_NCHITTEST区分交互区域和穿透区域,仅阴影部分允许鼠标穿透,圆形主体正常接收交互。 - 消除闪烁:使用
WS_EX_NOREDIRECTIONBITMAP禁用DWM重定向,配合离屏渲染和WM_ERASEBKGND处理,彻底解决窗口移动时的闪烁问题。 - 纯Direct2D实现:全程使用Direct2D绘制,不依赖GDI/GDI+,符合需求。
内容的提问来源于stack exchange,提问作者Gabriel Santi
相关产品推荐
相关产品推荐

