2019-07-24 by cYo
Detecting connected Monitors, their names, their resolution, their sizes and their relative position is a non trivial process in Windows. You basically make a trip through the history of Windows APIs, all their different incarnations and limitations. And don’t forget the crazy DPI awareness for Windows apps that is by my counting now in the 4th iteration.
Best way is to call EnumDisplayDevices with the AttachedToDesktop enum.
var ddl = new List<DisplayDevice>();
DisplayDevice d = new DisplayDevice();
d.cb = Marshal.SizeOf(d);
for (uint id = 0; EnumDisplayDevices(null, id, ref d, 0); id++)
{
if (!d.StateFlags.HasFlag(DisplayDeviceStateFlags.AttachedToDesktop))
continue;
var d2 = new DisplayDevice();
d2.cb = Marshal.SizeOf(d2);
EnumDisplayDevices(d.DeviceName, 0, ref d2, 0);
d2.DeviceName = d.DeviceName;
ddl.Add(d2);
}
To get the physical sizes of monitors (if available) is by querying WMI. The id check in the sample code is just some internal lookup logic in Walls.
public (float width, float height) GetMonitorPhysicalSize(string id)
{
try
{
var scope = new ManagementScope("\\\\.\\ROOT\\WMI");
var q = new SelectQuery("WmiMonitorBasicDisplayParams");
var searcher = new ManagementObjectSearcher(scope, q);
var qc = searcher.Get();
foreach (var mo in qc)
{
string mi = mo["InstanceName"].ToString();
if (IsMonitorWmiId(id, mi))
return (float.Parse(mo["MaxHorizontalImageSize"].ToString()), float.Parse(mo["MaxVerticalImageSize"].ToString()));
}
}
catch (Exception e)
{
Debug.WriteLine("WMI Monitor error:" + e);
}
return (0, 0);
}
If WMI is not available (e.g. VMWare Virtual Machines) we build a name out of the device ID.
public string GetMonitorModel(string id)
{
try
{
var scope = new ManagementScope("\\\\.\\ROOT\\WMI");
var q = new SelectQuery("WmiMonitorID");
var searcher = new ManagementObjectSearcher(scope, q);
var qc = searcher.Get();
foreach (var mo in qc)
{
string mi = mo["InstanceName"].ToString();
if (!IsMonitorWmiId(id, mi))
continue;
var nameArray = ((UInt16[])(mo["UserFriendlyName"]))
.Select(i => (byte)i).Where(n => n != 0).ToArray();
return System.Text.Encoding.UTF8.GetString(nameArray);
}
}
catch (Exception e)
{
Debug.WriteLine("WMI Monitor error:" + e);
}
try
{
return id.Split('#')[1];
}
catch
{
}
return null;
}
The positions are defined in pixels on the Virtual Screen of all monitors combined. Note that the pixels maybe scaled if you have different DPI settings for the monitors. So it is best to switch the app to non DPI mode when getting the information. The information itself can be queried with the EnumDisplayMonitors function.
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_UNAWARE);
var miexl = new List<MonitorInfoEx>();
bool callback(IntPtr hMonitor, IntPtr hdc, ref Rect prect, int dn)
{
var info = new MonitorInfoEx();
GetMonitorInfo(hMonitor, info);
miexl.Add(info);
return true;
}
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, callback, 0);