如何将DisplayMonitor映射到Forms.Screen并获取显示器工作区等信息?
解决DisplayMonitor与Forms.Screen的信息互补问题
问题1:能否将DisplayMonitor映射到Forms.Screen?
可以通过设备硬件标识符实现映射。虽然Forms.Screen.DeviceName仅返回\\.\DISPLAY1这类简化名称,但借助Win32 API可以获取每个屏幕对应的完整设备路径,与DisplayMonitor.DeviceId中的硬件标识片段匹配。
实现步骤与代码示例
先定义P/Invoke相关方法,用于获取屏幕的设备路径:
using System.Runtime.InteropServices; using System.Text; public static class ScreenHelper { [DllImport("user32.dll")] private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumProc lpfnEnum, IntPtr dwData); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi); [DllImport("dxva2.dll", CharSet = CharSet.Unicode)] private static extern bool GetMonitorDevicePath(IntPtr hMonitor, StringBuilder pszDevicePath, ref int pcchDevicePath); private delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct MONITORINFOEX { public int cbSize; public RECT rcMonitor; public RECT rcWork; public uint dwFlags; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string szDevice; } [StructLayout(LayoutKind.Sequential)] private struct RECT { public int Left; public int Top; public int Right; public int Bottom; } public static string GetDevicePathForScreen(System.Windows.Forms.Screen screen) { var monitorInfo = new MONITORINFOEX(); monitorInfo.cbSize = Marshal.SizeOf(monitorInfo); GetMonitorInfo(screen.GetHmonitor(), ref monitorInfo); var sb = new StringBuilder(1024); int length = sb.Capacity; GetMonitorDevicePath(screen.GetHmonitor(), sb, ref length); return sb.ToString(); } }
结合你的CollectMonitors方法实现映射:
private async Task MapMonitors() { // 获取所有DisplayMonitor实例 var displays = await DeviceInformation.FindAllAsync(DisplayMonitor.GetDeviceSelector()); var displayMonitors = new List<DisplayMonitor>(); foreach (var display in displays) { displayMonitors.Add(await DisplayMonitor.FromInterfaceIdAsync(display.Id)); } // 获取所有Forms.Screen及其对应的设备路径 var screens = System.Windows.Forms.Screen.AllScreens; var screenDevicePathMap = screens.ToDictionary(s => s, ScreenHelper.GetDevicePathForScreen); // 通过设备路径片段匹配两者 var mappedPairs = new List<(DisplayMonitor DisplayMonitor, System.Windows.Forms.Screen Screen)>(); foreach (var dm in displayMonitors) { var matchingScreen = screens.FirstOrDefault(s => dm.DeviceId.Contains(screenDevicePathMap[s])); if (matchingScreen != null) { mappedPairs.Add((dm, matchingScreen)); } } // 使用组合信息:DisplayName + 工作区/主显示器状态 foreach (var (dm, screen) in mappedPairs) { Console.WriteLine($"显示器名称: {dm.DisplayName}, 工作区: {screen.WorkingArea}, 是否主显示器: {screen.Primary}"); } }
问题2:无法映射时,如何从DisplayMonitor获取工作区?
若映射出现问题,可直接通过DisplayMonitor.DeviceId获取对应的Win32监视器句柄,再调用GetMonitorInfo获取工作区信息:
代码示例
public static System.Drawing.Rectangle GetWorkAreaFromDisplayMonitor(DisplayMonitor dm) { IntPtr targetMonitorHandle = IntPtr.Zero; // 枚举所有监视器,匹配目标DisplayMonitor的设备路径 ScreenHelper.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (hMon, hdcMon, ref ScreenHelper.RECT lprcMon, IntPtr dwData) => { var sb = new StringBuilder(1024); int length = sb.Capacity; if (ScreenHelper.GetMonitorDevicePath(hMon, sb, ref length)) { if (dm.DeviceId.Contains(sb.ToString())) { targetMonitorHandle = hMon; return false; // 找到后停止枚举 } } return true; }, IntPtr.Zero); if (targetMonitorHandle == IntPtr.Zero) throw new InvalidOperationException("未找到对应监视器"); // 获取包含工作区的监视器信息 var monitorInfo = new ScreenHelper.MONITORINFOEX(); monitorInfo.cbSize = Marshal.SizeOf(monitorInfo); ScreenHelper.GetMonitorInfo(targetMonitorHandle, ref monitorInfo); // 转换为Rectangle格式返回 return new System.Drawing.Rectangle( monitorInfo.rcWork.Left, monitorInfo.rcWork.Top, monitorInfo.rcWork.Right - monitorInfo.rcWork.Left, monitorInfo.rcWork.Bottom - monitorInfo.rcWork.Top ); }
判断主显示器可通过GetMonitorInfo返回的dwFlags(MONITORINFOF_PRIMARY标记),或对比工作区与System.Windows.Forms.Screen.PrimaryScreen.WorkingArea是否一致。
内容的提问来源于stack exchange,提问作者lionkor
相关产品推荐
相关产品推荐

