缘由
最近有个工装的项目,测试HDMI接口可不可用,采取了诱骗器的方式,软件需要读到当前是否有外接屏幕的插入,最简单的就是来获取屏幕的数量,从而判断是否有外接屏幕的介入。因此有了需要查询Windows系统显示器个数的需求。
尝试过的方法
方法1
Screen.AllScreens.Count();
这个方法是利用WinForm中的Screen类,从而获得当前屏幕的数量。
方法2
using System.Management;
int number = 0;
ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_DesktopMonitor");
foreach (ManagementObject queryObj in searcher.Get())
{
number++;
}
这个方法是利用WMI来查询当前系统中的显示器,得到显示信息的列表,循环得到列表的个数,从而得到显示器的个数。
方法3
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip,
EnumMonitorsDelegate lpfnEnum, IntPtr dwData);
private delegate bool MonitorEnumProc(IntPtr hDesktop, IntPtr hdc, ref ScreenRect pRect, int dwData);
int monCount = 0;
MonitorEnumProc callback = (IntPtr hDesktop, IntPtr hdc, ref ScreenRect prect, int d) => ++monCount > 0;
if (!EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, callback, 0))
Console.WriteLine("An error occured while enumerating monitors");
return monCount;
这个方法是利用Win32中的EnumDisplayMonitors方法来枚举当前系统中存在的显示器,通过回调来进行一个显示器个数的计算。
方法4
[Flags()]
public enum DisplayDeviceStateFlags : int
{
/// <summary>The device is part of the desktop.</summary>
AttachedToDesktop = 0x1,
MultiDriver = 0x2,
/// <summary>The device is part of the desktop.</summary>
PrimaryDevice = 0x4,
/// <summary>Represents a pseudo device used to mirror application drawing for remoting or other purposes.</summary>
MirroringDriver = 0x8,
/// <summary>The device is VGA compatible.</summary>
VGACompatible = 0x10,
/// <summary>The device is removable; it cannot be the primary display.</summary>
Removable = 0x20,
/// <summary>The device has more display modes than its output devices support.</summary>
ModesPruned = 0x8000000,
Remote = 0x4000000,
Disconnect = 0x2000000
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DISPLAY_DEVICE
{
[MarshalAs(UnmanagedType.U4)]
public int cb;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string DeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceString;
[MarshalAs(UnmanagedType.U4)]
public DisplayDeviceStateFlags StateFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string DeviceKey;
}
[DllImport("user32.dll")]
static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);
public void Display()
{
DISPLAY_DEVICE d = new DISPLAY_DEVICE();
d.cb = Marshal.SizeOf(d);
int number=0;
try
{
for (uint id = 0; EnumDisplayDevices(null, id, ref d, 0); id++)
{
if (d.StateFlags.HasFlag(DisplayDeviceStateFlags.AttachedToDesktop))
{
//计算个数
number++;
//输出相关信息
Console.WriteLine(
String.Format("{0}, {1}, {2}, {3}, {4}, {5}",
id,
d.DeviceName,
d.DeviceString,
d.StateFlags,
d.DeviceID,
d.DeviceKey
)
);
d.cb = Marshal.SizeOf(d);
EnumDisplayDevices(d.DeviceName, 0, ref d, 0);
Console.WriteLine(String.Format("{0}, {1}",
d.DeviceName,
d.DeviceString
)
);
}
d.cb = Marshal.SizeOf(d);
}
}
catch (Exception ex)
{
Console.WriteLine(String.Format("{0}", ex.ToString()));
}
}
这个方法是利用Win32中的EnumDisplayDevices方法来枚举当前系统中存在的显示器,通过循环来进行一个显示器个数的计算。
方法5
public enum SystemMetric
{
//其他类型查看:https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
SM_CMONITORS=80,
...
}
[DllImport("user32.dll")]
static extern int GetSystemMetrics(SystemMetric smIndex);
int screenNum=GetSystemMetrics(SystemMetric.SM_CMONITORS);
这个方法是利用Win32中的GetSystemMetrics方法来获取当前系统中存在的显示器个数,这个方法还可以查询系统的其他信息,详情参照官方文档。
重点来了:如果接两个显示器,设置系统处于复制模式(Clone/duplicate),则上述方法都失效,只能找到一个显示设备,但是项目正好是这个模式,哎
接下来咱们来一个不受模式影响的方法X
原理:每个种类的设备在系统中有一个类Guid,我们可以通过设备管理器中设备的属性中查看,如下图。然后我们用Win32的API枚举设备管理器中的设备,然后通过类Guid对设备进行筛选,从而筛选出屏幕设备,然后计算个数。
[Flags]
public enum DIGCF : uint
{
DEFAULT = 0x00000001,
PRESENT = 0x00000002,
ALLCLASSES = 0x00000004,
PROFILE = 0x00000008,
DEVICEINTERFACE = 0x00000010
}
[StructLayout(LayoutKind.Sequential)]
public struct SP_DEVINFO_DATA
{
public UInt32 cbSize;
public Guid ClassGuid;
public UInt32 DevInst;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct SP_DEVINFO_DATA
{
public UInt32 cbSize;
public Guid ClassGuid;
public UInt32 DevInst;
public IntPtr Reserved;
}
[DllImport("setupapi.dll", SetLastError = true)]
public static extern bool SetupDiDestroyDeviceInfoList(IntPtr handle);
[DllImport("setupapi.dll", SetLastError = true)]
public static extern IntPtr SetupDiGetClassDevsW([In] ref Guid ClassGuid, [MarshalAs(UnmanagedType.LPWStr)]string Enumerator, IntPtr parent, DIGCF flags);
[DllImport("setupapi.dll", SetLastError = true)]
public static extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet, UInt32 memberIndex, [Out] out SP_DEVINFO_DATA deviceInfoData);
int screenNum=0;
Guid NullGuid = Guid.Empty;
IntPtr info = SetupDiGetClassDevsW(ref NullGuid, null, IntPtr.Zero, DIGCF.ALLCLASSES|DIGCF.PROFILE);
SP_DEVINFO_DATA devdata = new SP_DEVINFO_DATA();
devdata.cbSize = (UInt32)Marshal.SizeOf(devdata);
//遍历设备
for (uint i = 0; SetupDiEnumDeviceInfo(info, i, out devdata); i++)
{
if (devdata.ClassGuid == new Guid("{4d36e96e-e325-11ce-bfc1-08002be10318}"))
{
screenNum++;
}
}
if (info != IntPtr.Zero)
SetupDiDestroyDeviceInfoList(info);
总结
最后这个方法只要是设备管理器中可以看见的设备均可以查询到,拿到更加原始的数据,不过相对的查询速度会慢一些,不过解决了问题,值得庆祝,分享给大家,希望大家少走弯路。
踩坑的道路永不停歇!