用C++ Win32创建一个透明窗口

57

我正在创建一个应该非常简单的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来制作透明的父控件。 - user90150
2个回答

46

我通过使用这个系列的第一部分和第二部分的代码成功地实现了我想要的功能:

使用C++显示启动画面


这些博客文章讲述了如何在Win32 C ++中显示启动画面,但几乎与我所需的完全相同。我认为我遗漏的部分是,我需要使用具有正确BLENDFUNCTION参数的UpdateLayeredWindow函数,而不仅仅是使用GDI +将PNG绘制到窗口中。下面是我将在上面的链接中的第2部分中找到的“SetSplashImage”方法:

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);
}

1
最佳答案是,当与WIC(Windows Imaging Component)通过COM接口一起使用时,还支持自动PNG alpha混合。 - dns
最终花了很多时间才搞定AlphaBlend。这个解决方案可能是实现像素级透明混合的最简单方法之一,特别是当你正在更新窗口时。 - TheBlueNotebook

25

使用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文章中的方法:

在Windows 2000及以上版本中使用标准控件创建酷炫的、半透明的和有形状的对话框


2
我已经尝试过这个方法,它几乎可以工作。问题在于PNG图像中有一些半透明的区域,而颜色键方法只能使完全不透明的像素变成透明的颜色键颜色值。如果其他方法都失败了,我的备选方案将是删除所有半透明区域,但我真的很想避免这样做。 - adoss
你可以尝试更复杂的黑客攻击,比如我链接文章中提到的那种,但是一旦你想要将现有图像与桌面内容混合,事情就开始变得复杂了。 - Simon Steele
请看我发布的答案。我比在这篇 CodeProject 文章中看到的代码更喜欢那个代码。不仅如此,我相信我找到的是正确的解决方案,而不是一个 hack。感谢你的帮助! - adoss
啊,不错,我之前没见过BLENDFUNCTION这个东西 - 不用谢! - Simon Steele
3
为什么不将SetLayeredWindowAttributes的第四个参数设置为LWA_ALPHA? - Valen
我不知道为什么一年后我还在寻找同样的东西。作为跟进,子窗口将自动继承LWA_ALPHA的值。 - Valen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接