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

C#直接在屏幕绘图时Windows DPI缩放导致坐标错误问题

问题根源

坐标偏移是Windows DPI虚拟化机制导致的。程序未声明DPI感知时,调用GetDC(IntPtr.Zero)获取的桌面设备上下文是经过系统缩放处理的逻辑坐标上下文,而非物理像素坐标上下文:

  • 150%缩放场景下,4K(38402160)屏幕对应的逻辑分辨率恰好是25601440,和你观测到的VisibleClipBounds返回值完全匹配
  • 直接传入物理像素坐标在逻辑坐标DC上绘制,自然会出现位置偏移,右下角坐标超出逻辑分辨率裁剪区后就会完全不显示,此时调用g.ScaleTransform(1.5f)也无法解决问题,因为DC本身的裁剪边界仍然是按逻辑分辨率划定的。
解决方案

核心是先让程序跳过系统DPI虚拟化,再修正绘图逻辑的资源泄漏问题:

1. 开启进程DPI感知

这一步是必须的,开启后系统会直接给程序返回物理像素级别的DC,不再做自动缩放映射。
两种实现方式二选一即可:

  • 方式一:修改程序清单文件(app.manifest),找到DPI相关配置段,取消注释放开以下配置:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
  <windowsSettings>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
    <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
  </windowsSettings>
</application>
  • 方式二:在程序入口(Main函数最开头,所有窗口、GDI相关API调用之前)调用Windows API直接设置:
[DllImport("user32.dll")]
private static extern bool SetProcessDpiAwarenessContext(IntPtr dpiFlag);
private static readonly IntPtr DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = new IntPtr(-4);

// Main函数第一行加这个
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

注意:DPI感知设置必须在程序创建任何UI组件、调用任何GDI/GDI+接口前执行,晚于这个时机调用会失效。

2. 修正绘图代码

开启DPI感知后,原来的坐标逻辑已经可以正常工作,但原代码存在GDI对象泄漏问题,需要用try-finally和using语句保证所有非托管资源被正确释放,修正后的完整代码如下:

// 引入需要的Windows API
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hWnd);

[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

public static void RectOnScreen2(Rectangle rect, int width = 3, Color? kolor = null)
{   
    IntPtr desktopPtr = GetDC(IntPtr.Zero);
    try
    {
        using (Pen pen = new Pen(kolor ?? Color.Black, width))
        using (Graphics g = Graphics.FromHdc(desktopPtr))
        {
            // 开启DPI感知后,传入的物理像素坐标会精准对应屏幕实际位置
            g.DrawRectangle(pen, rect);
        }
    }
    finally
    {
        // 确保DC一定会被释放
        ReleaseDC(IntPtr.Zero, desktopPtr);
    }
}
特殊场景兼容

如果因为历史兼容原因无法给整个进程开启DPI感知,可以在获取桌面DC后,通过GetDeviceCapsAPI查询当前DC的逻辑DPI,手动把传入的物理坐标换算为对应逻辑坐标再绘制。但这种方案在多显示器不同缩放比例的场景下适配成本很高,非必要不推荐。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 16:09:21