Vegard Larsen
Vegard Larsen

Reputation: 13047

Linking System.Windows.Form.Screen to the monitor's serial code

I am trying to find a way to store data pertaining to a specific monitor. Using System.Windows.Forms.Screen, the DeviceName property only contains a string like \\.\DISPLAY1, which is based only on the index of the monitor. I need a unique ID for that monitor, not its order in the display setup.

I need to link this monitor ID to its working area.

I am not tied to using System.Windows.Forms.Screen if that is not necessary.

Upvotes: 1

Views: 1632

Answers (2)

Andy Hopper
Andy Hopper

Reputation: 3678

I took a slightly different approach from hofmeister; I'm using P/Invoke to get the display name of the Form's monitor using MonitorFromWindow + GetMonitorInfo (this can be turned into a handy extension method), and then use EnumDisplayDevices to get a list of displays to see which one that is, followed by emitting the PnP device ID:

   public partial class Form1 : Form
{
    [DllImport("user32.dll")]
    private static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfoEx lpmi);

    // size of a device name string
    private const int CCHDEVICENAME = 32;

    /// <summary>
    /// The MONITORINFOEX structure contains information about a display monitor.
    /// The GetMonitorInfo function stores information into a MONITORINFOEX structure or a MONITORINFO structure.
    /// The MONITORINFOEX structure is a superset of the MONITORINFO structure. The MONITORINFOEX structure adds a string member to contain a name 
    /// for the display monitor.
    /// </summary>
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    internal struct MonitorInfoEx
    {
        /// <summary>
        /// The size, in bytes, of the structure. Set this member to sizeof(MONITORINFOEX) (72) before calling the GetMonitorInfo function. 
        /// Doing so lets the function determine the type of structure you are passing to it.
        /// </summary>
        public int Size;

        /// <summary>
        /// A RECT structure that specifies the display monitor rectangle, expressed in virtual-screen coordinates. 
        /// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.
        /// </summary>
        public RectStruct Monitor;

        /// <summary>
        /// A RECT structure that specifies the work area rectangle of the display monitor that can be used by applications, 
        /// expressed in virtual-screen coordinates. Windows uses this rectangle to maximize an application on the monitor. 
        /// The rest of the area in rcMonitor contains system windows such as the task bar and side bars. 
        /// Note that if the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values.
        /// </summary>
        public RectStruct WorkArea;

        /// <summary>
        /// The attributes of the display monitor.
        /// 
        /// This member can be the following value:
        ///   1 : MONITORINFOF_PRIMARY
        /// </summary>
        public uint Flags;

        /// <summary>
        /// A string that specifies the device name of the monitor being used. Most applications have no use for a display monitor name, 
        /// and so can save some bytes by using a MONITORINFO structure.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
        public string DeviceName;

        public void Init()
        {
            this.Size = 40 + 2 * CCHDEVICENAME;
            this.DeviceName = string.Empty;
        }
    }

    /// <summary>
    /// The RECT structure defines the coordinates of the upper-left and lower-right corners of a rectangle.
    /// </summary>
    /// <see cref="http://msdn.microsoft.com/en-us/library/dd162897%28VS.85%29.aspx"/>
    /// <remarks>
    /// By convention, the right and bottom edges of the rectangle are normally considered exclusive. 
    /// In other words, the pixel whose coordinates are ( right, bottom ) lies immediately outside of the the rectangle. 
    /// For example, when RECT is passed to the FillRect function, the rectangle is filled up to, but not including, 
    /// the right column and bottom row of pixels. This structure is identical to the RECTL structure.
    /// </remarks>
    [StructLayout(LayoutKind.Sequential)]
    public struct RectStruct
    {
        /// <summary>
        /// The x-coordinate of the upper-left corner of the rectangle.
        /// </summary>
        public int Left;

        /// <summary>
        /// The y-coordinate of the upper-left corner of the rectangle.
        /// </summary>
        public int Top;

        /// <summary>
        /// The x-coordinate of the lower-right corner of the rectangle.
        /// </summary>
        public int Right;

        /// <summary>
        /// The y-coordinate of the lower-right corner of the rectangle.
        /// </summary>
        public int Bottom;
    }

    [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.Auto)]
    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", CharSet = CharSet.Auto)]
    static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);

    public Form1()
    {
        InitializeComponent();
        String thisFormsMonitor = null;
        IntPtr hMon = MonitorFromWindow(this.Handle, 0);
        MonitorInfoEx monInfo = new MonitorInfoEx();
        monInfo.Size = 104;
        if (GetMonitorInfo(hMon, ref monInfo))
        {
            thisFormsMonitor = monInfo.DeviceName;
        }

        DISPLAY_DEVICE displayDevice = new DISPLAY_DEVICE();
        displayDevice.cb = Marshal.SizeOf(displayDevice);

        uint deviceIndex = 0;
        while (EnumDisplayDevices(null, deviceIndex, ref displayDevice, 0))
        {
            if (displayDevice.DeviceName == thisFormsMonitor) System.Diagnostics.Debug.WriteLine(displayDevice.DeviceID);
            deviceIndex++;
        }

        this.Text = System.Windows.Forms.Screen.PrimaryScreen.DeviceName;
    }
}

Upvotes: 2

Andre Hofmeister
Andre Hofmeister

Reputation: 3416

I had similar requirements. I am parsing the EDIDs in the Windows Registry to get the monitor specific id and couple of other information. The EDID information are stored in following key:

"HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\DISPLAY\{DevideId}\Device Parameters"

I use an additional WMI query to get the {DeviceId}. The EDID contains the monitor model as well as the serial number and the screen size. Have a look at the EDID data format as well. I hope that will help you.

Here is my code snippet to parse the EDID information:

var bytes = (byte[]) key.GetValue("EDID");

if (bytes == null) return;

/* Read model number */
var buffer = new byte[4];
for (var i = 54; i < 109; i += 18)
{
    var sb = new StringBuilder();

    Buffer.BlockCopy(bytes, i, buffer, 0, 4);

    Array.Reverse(buffer);

    if (BitConverter.ToInt32(buffer, 0).Equals(0xFF))
    {
        for (var j = i + 5; (bytes[j] != 10) && (j < i + 18); j++)
        {
            sb.Append((char) bytes[j]);
        }

        this.ModelNo = sb.ToString();
        sb.Clear();
    }

    if (BitConverter.ToInt32(buffer, 0).Equals(0xFC))
    {
        for (var j = i + 5; (bytes[j] != 10) && (j < i + 18); j++)
        {
            sb.Append((char)bytes[j]);
        }

        this.Model = sb.ToString();
        sb.Clear();
    }
}

if (string.IsNullOrEmpty(this.ModelNo)) this.ModelNo = string.Empty;

if (string.IsNullOrEmpty(this.Model)) this.Model = string.Empty;

/* Read serial number */

buffer = new byte[4];
Buffer.BlockCopy(bytes, 12, buffer, 0, 4);

//this.SerialNo = BitConverter.ToString(buffer);

this.SerialNo = string.Concat(buffer.Select(b => b.ToString("X2")));

/* Read screen size */
var x = (int) bytes[21];
var y = (int) bytes[22];

this.Size = Math.Round(Math.Sqrt(x * x + y * y) / 2.54).ToString();

Upvotes: 2

Related Questions