【问题标题】:Creating a transparent window in C++ Win32在 C++ Win32 中创建透明窗口
【发布时间】:2011-04-27 13:42:24
【问题描述】:

我正在创建一个非常简单的 Win32 C++ 应用程序,其唯一目的是只显示一个半透明的 PNG。窗口不应该有任何镶边,所有的不透明度都应该在 PNG 本身中控制。

我的问题是,当窗口下的内容发生变化时,窗口不会重新绘制,因此 PNG 的透明区域与最初启动应用程序时窗口下的内容“卡住”了。

这是我设置新窗口的那一行:

hWnd = CreateWindowEx(WS_EX_TOPMOST, szWindowClass, szTitle, WS_POPUP, 0, height/2 - 20, 40, 102, NULL, NULL, hInstance, 0);

对于 RegisterClassEx 的调用,我为背景设置了这个:

wcex.hbrBackground = (HBRUSH)0;

这是我的 WM_PAINT 消息处理程序:

 case WM_PAINT:
 {
   hdc = BeginPaint(hWnd, &ps);
   Gdiplus::Graphics graphics(hdc);
   graphics.DrawImage(*m_pBitmap, 0, 0);
   EndPaint(hWnd, &ps);
   break;
 }

需要注意的一点是,应用程序始终停靠在屏幕左侧并且不会移动。但是,当用户打开、关闭或移动它下面的窗口时,应用程序下面的内容可能会发生变化。

当应用程序第一次启动时,它看起来很完美。 PNG 的透明(和半透明)部分完美地显示出来。但是,当应用程序下面的背景发生变化时,背景不会更新,它只是从应用程序首次启动时保持不变。事实上,WM_PAINT(或 WM_ERASEBKGND 在背景变化时不会被调用)。

我玩这个已经有一段时间了,已经接近 100% 正确,但还没有完全做到。例如,我尝试将背景设置为 (HBRUSH) NULL_BRUSH,并尝试处理 WM_ERASEBKGND。

当窗口下面的内容发生变化时,如何让窗口重新绘制?

【问题讨论】:

  • SetBKMode 和 SetBKColor 是我用来制作透明父控件的 API。

标签: c++ winapi gdi+ transparency alphablending


【解决方案1】:

通过使用本系列第 1 部分和第 2 部分中的代码,我能够完全按照我的意愿行事:

Displaying a Splash Screen with C++


那些博客文章谈论的是在 Win32 C++ 中显示启动画面,但这与我需要做的几乎相同。我相信我缺少的部分是,我需要使用 UpdateLayeredWindow 函数和正确的 BLENDFUNCTION 参数,而不是使用 GDI+ 将 PNG 绘制到窗口。我将在下面粘贴 SetSplashImage 方法,可以在上面链接的第 2 部分中找到:

void SetSplashImage(HWND hwndSplash, HBITMAP hbmpSplash)
{
  // get the size of the bitmap
  BITMAP bm;
  GetObject(hbmpSplash, sizeof(bm), &bm);
  SIZE sizeSplash = { bm.bmWidth, bm.bmHeight };

  // get the primary monitor's info
  POINT ptZero = { 0 };
  HMONITOR hmonPrimary = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
  MONITORINFO monitorinfo = { 0 };
  monitorinfo.cbSize = sizeof(monitorinfo);
  GetMonitorInfo(hmonPrimary, &monitorinfo);

  // center the splash screen in the middle of the primary work area
  const RECT & rcWork = monitorinfo.rcWork;
  POINT ptOrigin;
  ptOrigin.x = 0;
  ptOrigin.y = rcWork.top + (rcWork.bottom - rcWork.top - sizeSplash.cy) / 2;

  // create a memory DC holding the splash bitmap
  HDC hdcScreen = GetDC(NULL);
  HDC hdcMem = CreateCompatibleDC(hdcScreen);
  HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpSplash);

  // use the source image's alpha channel for blending
  BLENDFUNCTION blend = { 0 };
  blend.BlendOp = AC_SRC_OVER;
  blend.SourceConstantAlpha = 255;
  blend.AlphaFormat = AC_SRC_ALPHA;

  // paint the window (in the right location) with the alpha-blended bitmap
  UpdateLayeredWindow(hwndSplash, hdcScreen, &ptOrigin, &sizeSplash,
      hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);

  // delete temporary objects
  SelectObject(hdcMem, hbmpOld);
  DeleteDC(hdcMem);
  ReleaseDC(NULL, hdcScreen);
}

【讨论】:

  • 最佳答案,通过 COM 接口与 WIC(Windows 映像组件)一起使用时,还支持自动 PNG alpha 混合。
  • 最终在 AlphaBlend 上浪费了很多时间。此解决方案可能是实现每像素 Alpha 混合的最简单方法之一,尤其是在您更新窗口时。
【解决方案2】:

使用SetLayeredWindowAttributesarchive 函数,这允许您设置将变为透明的遮罩颜色,从而允许背景显示出来。

您还需要使用分层标志配置窗口,例如

SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);

之后就相当简单了:

// Make red pixels transparent:
SetLayeredWindowAttributes(hwnd, RGB(255,0,0), 0, LWA_COLORKEY);

当您的 PNG 包含要与背景混合的半透明像素时,这会变得更加复杂。您可以尝试查看此 CodeProject 文章中的方法:

Cool, Semi-transparent and Shaped Dialogs with Standard Controls for Windows 2000 and Above

【讨论】:

  • 我已经尝试过了,它几乎可以工作。问题是PNG的某些区域是半透明的,并且颜色键方法仅使颜色键颜色值的100%不透明度的像素透明。如果一切都失败了,我的后备将是删除所有半透明区域,但我真的宁愿避免这样做。
  • 您可以尝试更复杂的 hack,例如我链接的文章中的那个,但基本上只要您希望将预先存在的图像与桌面内容混合,事情就会变得复杂。
  • 查看我发布的答案。我喜欢这个代码比我在这篇 CodeProject 文章中看到的要好得多。不仅如此,我相信我发现的是正确的解决方案,而不是破解。感谢您的帮助!
  • 啊,太好了,我以前没看过 BLENDFUNCTION 的东西 - 不客气!
  • 为什么不把SetLayeredWindowAttributes的第四个参数设置为LWA_ALPHA
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-08
  • 2011-05-31
  • 1970-01-01
  • 2021-06-24
  • 2022-11-02
  • 1970-01-01
  • 2021-09-17
相关资源
最近更新 更多