Screen.AllScreens 返回当前激活的物理显示器及其实际分辨率,不提供驱动支持的所有分辨率;EnumDisplaySettings 才能枚举显卡驱动中的全部可选模式,需P/Invoke并用Screen.DeviceName获取正确设备名。

用 Screen.AllScreens 获取当前所有显示器及其分辨率
直接读取系统已连接的物理屏幕信息,不依赖显卡驱动提供的“可选分辨率列表”,只反映当前实际使用的设置。这是最稳定、跨 Windows 版本兼容的方式。
常见错误是误以为 Screen.AllScreens 能列出“所有支持的分辨率”,其实它只返回当前激活的显示模式(比如 1920×1080@60Hz),不是显卡驱动里那个下拉菜单里的全部选项。
-
Screen.AllScreens返回的是Screen[],每个Screen对象含Bounds(含Width/Height)、WorkingArea、Primary等字段 - 多显示器场景下,
Bounds是该屏绝对坐标系下的矩形,可能为负值(如主屏在右,副屏在左) - 如果程序未启用 DPI 感知,高缩放比下
Bounds.Size可能返回逻辑像素而非物理像素——需配合GraphicsDeviceCapabilities或 Win32 API 判断真实 DPI
用 EnumDisplaySettings 枚举某显示器全部支持的显示模式
这才是真正对应“显卡驱动设置界面里下拉列表”的方式,但必须调用 Win32 API,且仅限 Windows 平台。C# 需通过 P/Invoke 调用 user32.dll 中的 EnumDisplaySettings 函数。
容易踩的坑:传入错误的设备名(lpszDeviceName)。不能硬写 "\\.\DISPLAY1",而应从 Screen.AllScreens[i].DeviceName 获取真实设备名(如 "\\.\DISPLAY1" 或 "\\.\DISPLAY2")。
- 每次调用
EnumDisplaySettings需递增iModeNum(从 0 开始),直到返回false - 返回的
DEVMODE结构体中,dmPelsWidth/dmPelsHeight是分辨率,dmDisplayFrequency是刷新率,dmBitsPerPel是色深 - 某些模式可能被标记为
DM_DISPLAYFLAGS中的DM_GRAYSCALE或DM_INTERLACED,实际不可用或已弃用
为什么 GraphicsAdapter.AdapterInformation 不适合这个需求
Unity 或 SharpDX 中的 GraphicsAdapter 类型常被误查,但它属于图形 API 抽象层(如 DXGI),返回的是 GPU 支持的“渲染能力”,不是 Windows 显示子系统暴露给桌面的“显示模式”。它不会告诉你当前显示器能否切到 2560×1440@120Hz,也不会反映 EDID 限制。
典型混淆现象:在 Surface Book 或外接 USB-C 显示器上,GraphicsAdapter.SupportedDisplayModes 可能为空或只返回默认模式——因为底层没走 DXGI EnumAdapters 逻辑,而是由 Windows Display Driver Model (WDDM) 控制。
- 真要查硬件级支持,得用 WMI 查询
Win32_VideoController的CurrentHorizontalResolution等属性,但那是当前状态,不是可选列表 - .NET 6+ 的
System.Drawing.Common已移除对GraphicsAdapter的公开支持,进一步说明这不是标准路径
实际代码片段:枚举主显示器所有分辨率
以下是最简可行示例,仅依赖 System.Runtime.InteropServices 和基础 Win32 结构体定义:
[StructLayout(LayoutKind.Sequential)]
public struct DEVMODE
{
private const int CCHDEVICENAME = 32;
private const int CCHFORMNAME = 32;
<pre class="brush:php;toolbar:false;">[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
public string dmFormName;
public short dmLogPixels;
public int dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;}
[DllImport("user32.dll")] static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode);
// 使用: var primary = Screen.PrimaryScreen; var devMode = new DEVMODE { dmSize = (short)Marshal.SizeOf(typeof(DEVMODE)) }; int modeIndex = 0; while (EnumDisplaySettings(primary.DeviceName, modeIndex++, ref devMode)) { Console.WriteLine($"{devMode.dmPelsWidth}x{devMode.dmPelsHeight}@{devMode.dmDisplayFrequency}Hz"); }
注意:dmSize 必须显式赋值,否则 Win32 调用会失败;modeIndex 从 0 开始,-1 表示当前活动模式(但你通常不需要它)。
真正的难点不在调用本身,而在判断哪些模式对当前显示器实际有效——比如 HDMI 接口可能不支持某分辨率的 120Hz,但 EnumDisplaySettings 仍会返回它。这时候需要结合 MonitorInfoEx 和 EDID 解析,或者直接尝试 ChangeDisplaySettings 并捕获返回码。










