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(); }
当前模糊效果:
期望的清晰效果:
如何在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
相关产品推荐
相关产品推荐

