C#如何获取与Windows显示设置一致的屏幕排列及主副屏信息
你遇到的System.Windows.Forms.Screen类返回结果和系统显示设置不一致的问题,是该类的已知缺陷:它基于老旧的GDI接口实现,内部会静态缓存显示设备信息,仅在进程启动时读取一次,后续修改显示配置(切换主屏、调整排列、插拔显示器)后不会主动刷新,同时高DPI场景下也可能返回缩放后的错误坐标。
以下是完全和Windows显示设置面板结果一致的可靠解决方案:
方案1:调用Win32原生API(兼容性最好,支持Win7及以上所有系统)
直接使用Windows显示子系统原生API,和系统设置面板使用完全相同的数据源,结果100%一致。
第一步:定义P/Invoke声明和结构体
using System; using System.Collections.Generic; using System.Runtime.InteropServices; public static class DisplayApi { [DllImport("user32.dll")] private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData); private delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi); [StructLayout(LayoutKind.Sequential)] private struct RECT { public int left; public int top; public int right; public int bottom; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct MONITORINFOEX { public uint cbSize; public RECT rcMonitor; public RECT rcWork; public uint dwFlags; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string szDevice; } private const uint MONITORINFOF_PRIMARY = 0x00000001; // 对外暴露的显示器信息结构 public class DisplayInfo { public string DeviceName { get; set; } public int BoundsLeft { get; set; } public int BoundsTop { get; set; } public int BoundsWidth { get; set; } public int BoundsHeight { get; set; } public int WorkAreaLeft { get; set; } public int WorkAreaTop { get; set; } public int WorkAreaWidth { get; set; } public int WorkAreaHeight { get; set; } public bool IsPrimary { get; set; } } // 获取所有显示器最新信息的方法 public static List<DisplayInfo> GetAllDisplays() { List<DisplayInfo> displays = new List<DisplayInfo>(); EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (IntPtr hMonitor, IntPtr hdc, ref RECT rc, IntPtr data) => { MONITORINFOEX mi = new MONITORINFOEX(); mi.cbSize = (uint)Marshal.SizeOf(mi); if (GetMonitorInfo(hMonitor, ref mi)) { displays.Add(new DisplayInfo { DeviceName = mi.szDevice, BoundsLeft = mi.rcMonitor.left, BoundsTop = mi.rcMonitor.top, BoundsWidth = mi.rcMonitor.right - mi.rcMonitor.left, BoundsHeight = mi.rcMonitor.bottom - mi.rcMonitor.top, WorkAreaLeft = mi.rcWork.left, WorkAreaTop = mi.rcWork.top, WorkAreaWidth = mi.rcWork.right - mi.rcWork.left, WorkAreaHeight = mi.rcWork.bottom - mi.rcWork.top, IsPrimary = (mi.dwFlags & MONITORINFOF_PRIMARY) == MONITORINFOF_PRIMARY }); } return true; }, IntPtr.Zero); return displays; } }
第二步:调用示例
foreach (var display in DisplayApi.GetAllDisplays()) { Console.WriteLine($"Device Name: {display.DeviceName}"); Console.WriteLine($"Bounds: X={display.BoundsLeft}, Y={display.BoundsTop}, Width={display.BoundsWidth}, Height={display.BoundsHeight}"); Console.WriteLine($"Working Area: X={display.WorkAreaLeft}, Y={display.WorkAreaTop}, Width={display.WorkAreaWidth}, Height={display.WorkAreaHeight}"); Console.WriteLine($"Primary Screen: {display.IsPrimary}"); Console.WriteLine("====================================================="); }
必须注意的配置项
必须为程序开启Per-Monitor DPI感知,否则获取到的坐标会被系统DPI缩放,导致和设置面板显示的数值不一致,可在项目的app.manifest中添加如下配置:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware> </windowsSettings> </application>
该方案每次调用都会主动读取当前最新的显示配置,不存在缓存问题,修改显示设置后直接重新调用即可拿到最新结果。
备选方案(Win10 1809及以上版本可用)
可以使用Windows Runtime提供的Windows.Devices.Display.Core.DisplayManager类,无需P/Invoke,直接调用即可获取最新的显示配置,结果同样和系统设置完全一致。
内容的提问来源于stack exchange,提问作者DARKGuy
相关产品推荐
相关产品推荐

