You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows 11 Per Monitor DPI模式下如何正确捕获多分辨率屏幕鼠标光标?

多DPI屏幕下正确捕获鼠标光标问题(Windows 11)

问题描述

主屏幕为FullHD(1080p)、副屏幕为4K的环境中,跨屏幕捕获鼠标光标时遇到以下问题:

  • 光标位于FullHD屏幕时,调用DrawIconEx(hdcDest, 0, 0, cursorHandle, 32, 32, NULL, DI_NORMAL);显示正常
  • 光标位于4K屏幕时,调用DrawIconEx(hdcDest, 0, 0, cursorHandle, 48, 48, NULL, DI_NORMAL);得到的光标模糊,无法达到屏幕原生显示的清晰度

实际使用的C#代码:

var hdcSrc = NativeMethods.GetWindowDC(IntPtr.Zero);
var bitmap = new Drawing.Bitmap(48, 48, PixelFormat.Format32bppRgb);                           
using (var g = Drawing.Graphics.FromImage(bitmap))
{
  var hdcDest = g.GetHdc();
  NativeMethods.BitBlt(hdcDest, 0, 0, bitmap.Width, bitmap.Height, hdcSrc, 0, 0, BitBltRop.SRCCOPY | BitBltRop.CAPTUREBLT);
  NativeMethods.DrawIconEx(hdcDest, 0, 0, this.cursorHandle, 48, 48, 0, IntPtr.Zero, NativeMethods.DI_NORMAL);
  g.ReleaseHdc();
}

当前模糊效果:
模糊的48x48光标

期望的清晰效果:
清晰的48x48光标

如何在Windows 11中基于Per Monitor DPI特性正确捕获鼠标光标?


问题根源

直接指定固定尺寸调用DrawIconEx时,系统会对低DPI的光标资源进行拉伸放大,导致模糊。Windows光标资源本身按多DPI维度存储原生版本,需要根据当前屏幕的DPI值,让系统自动匹配对应尺寸的原生资源,而非手动指定尺寸强制拉伸。

解决方案步骤

1. 获取光标所在屏幕的DPI值

先通过鼠标位置获取对应屏幕句柄,再调用GetDpiForMonitor获取该屏幕的有效DPI:

// 获取当前鼠标位置
NativeMethods.GetCursorPos(out POINT cursorPos);
// 获取鼠标所在的屏幕句柄
IntPtr monitor = NativeMethods.MonitorFromPoint(cursorPos, NativeMethods.MONITOR_DEFAULTTONEAREST);
// 获取该屏幕的有效DPI
NativeMethods.GetDpiForMonitor(monitor, NativeMethods.MDT_EFFECTIVE_DPI, out uint dpiX, out uint dpiY);

2. 计算光标原生尺寸

Windows基准DPI为96(对应100%缩放),光标原生尺寸计算公式:

光标尺寸 = 基准尺寸(通常32) * (当前屏幕DPI / 96)

比如4K屏幕通常为150%缩放(DPI=144),计算得32*(144/96)=48,和手动指定的尺寸一致,但关键是让系统调用原生资源而非拉伸。

3. 修正DrawIconEx调用参数

不要直接指定尺寸,传递0作为cx/cy参数,同时添加DI_DEFAULTSIZE标志,让系统自动匹配当前DPI的原生光标资源:

NativeMethods.DrawIconEx(hdcDest, 0, 0, this.cursorHandle, 0, 0, 0, IntPtr.Zero, NativeMethods.DI_NORMAL | NativeMethods.DI_DEFAULTSIZE);

4. 对齐Graphics对象的DPI设置

创建Graphics对象后,设置其DPI与目标屏幕一致,避免额外缩放:

using (var g = Drawing.Graphics.FromImage(bitmap))
{
    g.DpiX = dpiX;
    g.DpiY = dpiY;
    // 后续绘制操作
}

5. 优化Bitmap创建

根据屏幕DPI创建对应尺寸的Bitmap,同时使用ARGB格式保留光标透明通道:

int cursorSize = (int)(32 * ((double)dpiX / 96));
var bitmap = new Drawing.Bitmap(cursorSize, cursorSize, PixelFormat.Format32bppArgb);

完整优化代码片段

// 省略NativeMethods中POINT结构、GetCursorPos、MonitorFromPoint等方法的定义
NativeMethods.GetCursorPos(out POINT cursorPos);
IntPtr monitor = NativeMethods.MonitorFromPoint(cursorPos, NativeMethods.MONITOR_DEFAULTTONEAREST);
NativeMethods.GetDpiForMonitor(monitor, NativeMethods.MDT_EFFECTIVE_DPI, out uint dpiX, out uint dpiY);

int cursorSize = (int)(32 * ((double)dpiX / 96));
var bitmap = new Drawing.Bitmap(cursorSize, cursorSize, PixelFormat.Format32bppArgb);                           
using (var g = Drawing.Graphics.FromImage(bitmap))
{
    g.DpiX = dpiX;
    g.DpiY = dpiY;
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
    
    var hdcDest = g.GetHdc();
    var hdcSrc = NativeMethods.GetWindowDC(IntPtr.Zero);
    // 捕获鼠标所在区域的屏幕内容
    NativeMethods.BitBlt(hdcDest, 0, 0, bitmap.Width, bitmap.Height, hdcSrc, cursorPos.X - cursorSize/2, cursorPos.Y - cursorSize/2, BitBltRop.SRCCOPY | BitBltRop.CAPTUREBLT);
    // 绘制原生尺寸的光标
    NativeMethods.DrawIconEx(hdcDest, 0, 0, this.cursorHandle, 0, 0, 0, IntPtr.Zero, NativeMethods.DI_NORMAL | NativeMethods.DI_DEFAULTSIZE);
    
    NativeMethods.ReleaseDC(IntPtr.Zero, hdcSrc);
    g.ReleaseHdc();
}

关键注意事项

  • 应用必须启用Per Monitor V2 DPI感知,在应用清单中添加对应声明,否则系统会对整个应用进行缩放,导致光标捕获仍然模糊。
  • 使用PixelFormat.Format32bppArgb格式,保留光标透明通道,避免黑边或失真。
  • 调用ReleaseDC释放获取的hdcSrc,避免资源泄漏。

内容的提问来源于stack exchange,提问作者Benoist

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 05:36:12