C# 如何获取应用窗口当前所在显示器的精确DPI值
窗口所在显示器DPI动态获取实现方案
前置配置调整
原有代码始终返回主显示器DPI的核心原因,绝大多数是应用的DPI感知模式不匹配,默认的系统感知模式(System Aware)下,系统会给应用返回启动时主显示器的固定DPI值,哪怕窗口拖动到其他屏幕也不会触发值更新。
- 在项目的
app.manifest配置中,将DPI感知模式修改为PerMonitorV2,避免系统强制锁定进程级固定DPI值,配置片段参考:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> </windowsSettings> </application>
- 如果是WinForm项目,在应用入口的启动代码中,必须在实例化任何窗口之前调用PerMonitorV2开启方法:
// 必须放在Main方法最开头,早于Run()调用 Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); Application.EnableVisualStyles(); Application.Run(new MainForm());
核心DPI获取逻辑
不要依赖进程级的全局DPI变量,要以当前窗口的句柄为锚点,定位窗口所在的显示器,直接获取对应屏幕的实际DPI:
- 引入两个必要的Win32 API方法:
// 根据窗口句柄获取窗口所在的显示器句柄 [DllImport("user32.dll")] static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags); // 根据显示器句柄获取该屏的实际DPI值 [DllImport("Shcore.dll")] static extern int GetDpiForMonitor(IntPtr hmonitor, int dpiType, out uint dpiX, out uint dpiY);
- 封装可直接调用的获取方法,窗口跨屏时自动匹配对应屏的DPI:
const uint MONITOR_DEFAULTTONEAREST = 2; // 窗口跨屏时,取窗口中心最靠近的显示器 const int MDT_EFFECTIVE_DPI = 0; // 获取系统实际渲染用的有效DPI,和图像缩放需求完全匹配 public (float dpiX, float dpiY) GetCurrentWindowDpi(IntPtr windowHandle) { // 定位当前窗口所在的显示器 IntPtr currentMonitor = MonitorFromWindow(windowHandle, MONITOR_DEFAULTTONEAREST); // 获取对应显示器的DPI值 GetDpiForMonitor(currentMonitor, MDT_EFFECTIVE_DPI, out uint dpiX, out uint dpiY); return (dpiX, dpiY); }
动态更新触发
不要只在窗口初始化时获取一次DPI,要响应跨屏拖动时的DPI变更:
- 重写主窗口的
WndProc方法,监听系统主动推送的WM_DPICHANGED消息——当窗口从一个DPI的屏拖动到另一个屏时,系统会自动发送该消息,把新的DPI值直接带在消息参数中,不需要手动反复查询屏位置:
const int WM_DPICHANGED = 0x02E0; protected override void WndProc(ref Message m) { base.WndProc(ref m); if (m.Msg == WM_DPICHANGED) { // 从消息参数中直接解析新的DPI值 ushort newDpiX = (ushort)(m.WParam.ToInt64() & 0xFFFF); ushort newDpiY = (ushort)((m.WParam.ToInt64() >> 16) & 0xFFFF); // 触发图像渲染参数更新逻辑,把新的DPI值传给图像绘制模块 UpdateImageRenderScale(newDpiX, newDpiY); } }
常见踩坑提醒
- 不要用主显示器的全局缩放值硬算副屏DPI,这种方法在Windows非整数缩放(如125%、175%)场景下会出现计算偏差,直接通过
GetDpiForMonitor拿到的值是系统实际渲染用的真实值,无计算误差。 - 不要在老版本.NET Framework中使用
Control.DeviceDpi属性直接取值,这个属性在PerMonitorV2开启前会固定返回进程启动时的主屏DPI,不会随窗口跨屏更新。
内容的提问来源于stack exchange,提问作者Prithiv
相关产品推荐
相关产品推荐

