虽然我无法准确解释为什么会发生这种情况,但我想我可以展示如何解决它。
ICONINFO 结构包含两个成员,hbmMask 和 hbmColor,分别包含光标的掩码和颜色位图(有关官方文档,请参阅ICONINFO的 MSDN 页面)。
当您为默认光标调用 GetIconInfo() 时,ICONINFO 结构包含有效的掩码和颜色位图,如下所示(注意:已添加红色边框以清楚显示图像边界):
默认光标遮罩位图 
默认光标颜色位图 
当 Windows 绘制默认光标时,首先通过 AND 光栅操作应用遮罩位图,然后通过 XOR 光栅操作应用颜色位图。这会产生不透明的光标和透明的背景。
但是,当您为 I-Beam 光标调用 GetIconInfo() 时,ICONINFO 结构仅包含有效的掩码位图,没有颜色位图,如下所示(注意:再次添加红色边框以清楚显示图像边界):
I-Beam 光标遮罩位图
根据 ICONINFO 文档,I-Beam 光标是单色光标。掩码位图的上半部分是 AND 掩码,掩码位图的下半部分是 XOR 位图。当 Windows 绘制 I-Beam 光标时,该位图的上半部分首先通过 AND 光栅操作绘制在桌面上。然后使用 XOR 光栅操作在顶部绘制位图的下半部分。在屏幕上,光标将显示为它后面的内容的反面。
您链接的原始文章的评论之一提到了这一点。在桌面上,由于在桌面内容上应用了光栅操作,因此光标将正确显示。但是,当图像在没有背景的情况下绘制时,如在您发布的代码中,Windows 执行的光栅操作会导致图像褪色。
话虽如此,这个更新后的 CaptureCursor() 方法将同时处理彩色和单色光标,当光标为单色时提供纯黑色光标图像。
static Bitmap CaptureCursor(ref int x, ref int y)
{
Win32Stuff.CURSORINFO cursorInfo = new Win32Stuff.CURSORINFO();
cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
if (!Win32Stuff.GetCursorInfo(out cursorInfo))
return null;
if (cursorInfo.flags != Win32Stuff.CURSOR_SHOWING)
return null;
IntPtr hicon = Win32Stuff.CopyIcon(cursorInfo.hCursor);
if (hicon == IntPtr.Zero)
return null;
Win32Stuff.ICONINFO iconInfo;
if (!Win32Stuff.GetIconInfo(hicon, out iconInfo))
return null;
x = cursorInfo.ptScreenPos.x - ((int)iconInfo.xHotspot);
y = cursorInfo.ptScreenPos.y - ((int)iconInfo.yHotspot);
using (Bitmap maskBitmap = Bitmap.FromHbitmap(iconInfo.hbmMask))
{
// Is this a monochrome cursor?
if (maskBitmap.Height == maskBitmap.Width * 2)
{
Bitmap resultBitmap = new Bitmap(maskBitmap.Width, maskBitmap.Width);
Graphics desktopGraphics = Graphics.FromHwnd(Win32Stuff.GetDesktopWindow());
IntPtr desktopHdc = desktopGraphics.GetHdc();
IntPtr maskHdc = Win32Stuff.CreateCompatibleDC(desktopHdc);
IntPtr oldPtr = Win32Stuff.SelectObject(maskHdc, maskBitmap.GetHbitmap());
using (Graphics resultGraphics = Graphics.FromImage(resultBitmap))
{
IntPtr resultHdc = resultGraphics.GetHdc();
// These two operation will result in a black cursor over a white background.
// Later in the code, a call to MakeTransparent() will get rid of the white background.
Win32Stuff.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 32, Win32Stuff.TernaryRasterOperations.SRCCOPY);
Win32Stuff.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 0, Win32Stuff.TernaryRasterOperations.SRCINVERT);
resultGraphics.ReleaseHdc(resultHdc);
}
IntPtr newPtr = Win32Stuff.SelectObject(maskHdc, oldPtr);
Win32Stuff.DeleteObject(newPtr);
Win32Stuff.DeleteDC(maskHdc);
desktopGraphics.ReleaseHdc(desktopHdc);
// Remove the white background from the BitBlt calls,
// resulting in a black cursor over a transparent background.
resultBitmap.MakeTransparent(Color.White);
return resultBitmap;
}
}
Icon icon = Icon.FromHandle(hicon);
return icon.ToBitmap();
}
代码存在一些问题,可能是也可能不是问题。
- 单色光标的检查只是测试高度是否是宽度的两倍。虽然这看起来合乎逻辑,但 ICONINFO 文档并没有强制要求只定义一个单色光标。
- 可能有更好的方法来呈现我使用的方法调用的 BitBlt() - BitBlt() - MakeTransparent() 组合的光标。