ir1keren
ir1keren

Reputation: 77

Valid friendly monitor names with C#

My main development machine is a laptop with 2 screens: an internal screen and an external Samsung monitor.

Generic PnP Monitor= 1366x768, Top: 0, Left: 1920 -> secondary display
SF350_S24F350FH / S24F352FH / S24F354FH (HDMI)= 1920x1080, Top: 0, Left: 0 -> main display

My display configuration

And my codes are: Dispay.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

internal class Display
{
  private Rectangle _bounds;
  private DisplayOrientation _orientation;
  private Rectangle _workingArea;
  private string _name,_deviceId;
  private static Display[] _displays;

  public Rectangle Bounds
  {
     get
     {
       return _bounds;
     }
  }

  public DisplayOrientation Orientation
  {
    get
    {
        return _orientation;
    }
  }

  public Rectangle WorkingArea
  {
    get
    {
        return _workingArea;
    }
  }

  public string DeviceId
  {
    get
    {
        return _deviceId;
    }
  }

  public string Name
  {
    get
    {
        return _name;
    }
  }

  public static DisplayImpl[] Displays
  {
    get
    {

        if (_displays == null) QueryDisplayDevices();

        return _displays;
    }

    private static void QueryDisplayDevices()
    {
            List<Display> list = new List<Display>();
            WinApi.MonitorEnumDelegate MonitorEnumProc = new WinApi.MonitorEnumDelegate((IntPtr hMonitor, IntPtr hdcMonitor, ref WinApi.RECT lprcMonitor, IntPtr dwData) => {
                WinApi.MONITORINFOEX mi = new WinApi.MONITORINFOEX() { Size = Marshal.SizeOf(typeof(WinApi.MONITORINFOEX)) };

                if (WinApi.GetMonitorInfo(hMonitor, ref mi))
                {
                    WinApi.DISPLAY_DEVICE device = new WinApi.DISPLAY_DEVICE();
                    device.Initialize();
  
                    if (WinApi.EnumDisplayDevices(mi.DeviceName.ToLPTStr(), 0, ref device, 0))
                    {
                        Display display = new Display()
                        {
                            _name = device.DeviceString,
                            _deviceId = mi.DeviceName,
                            _bounds=new Rectangle(mi.Monitor.Left,mi.Monitor.Top,mi.Monitor.Right-mi.Monitor.Left,mi.Monitor.Bottom-mi.Monitor.Top),
                            _workingArea = new Rectangle(mi.WorkArea.Left, mi.WorkArea.Top, mi.WorkArea.Right - mi.WorkArea.Left, mi.WorkArea.Bottom - mi.WorkArea.Top),
                        };
                        list.Add(display);
                    }
                }
                
                return true;
            });

            WinApi.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, MonitorEnumProc, IntPtr.Zero);
            _displays=list.ToArray();
    }
  }
}

WinApi.cs:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;

internal class WinApi
{
#region DISPLAY_DEVICE struct
    [StructLayout(LayoutKind.Sequential)]
    internal struct DISPLAY_DEVICE
    {
        public int cb;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string DeviceName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceString;
        public DisplayDeviceStateFlags StateFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceKey;

        public void Initialize()
        {
            cb = 0;
            DeviceName = new string((char)32, 32);
            DeviceString = new string((char)32, 128);
            DeviceID = new string((char)32, 128);
            DeviceKey = new string((char)32, 128);
            cb = Marshal.SizeOf(this);
        }
    }
#endregion

#region RECT struct
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }
#endregion

#region MONITORINFOEX struct
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct MONITORINFOEX
    {
        public int Size;
        public RECT Monitor;
        public RECT WorkArea;
        public uint Flags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string DeviceName;
    }
#endregion

#region DisplayDeviceStateFlags enum
    [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,
    }
#endregion

    public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);

    [DllImport("user32.dll")]
    public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData);

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

    [DllImport("User32.dll")]
    internal static extern bool EnumDisplayDevices(byte[] lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, int dwFlags);

    public static byte[] ToLPTStr(this string str)
    {
        var lptArray = new byte[str.Length + 1];

        var index = 0;
        foreach (char c in str.ToCharArray())
        lptArray[index++] = Convert.ToByte(c);

        lptArray[index] = Convert.ToByte('\0');

        return lptArray;
    }
}

Then I try to debug

if(Display.Displays !=null) { }

And I got these results:

Display 0:
    Name: Generic PnP Monitor
    DeviceId: \\\\.\\DISPLAY1
    Bounds:
    Top: 0
    Left: 0
    Width: 1920
    Height: 1080

Display 1:
    Name: SF350_S24F350FH / S24F352FH / S24F354FH (HDMI)
    DeviceId: \\\\.\\DISPLAY2
    Bounds:
        Top: 0
        Left: 1920
        Width: 1366
        Height: 768

Based on screen resolution and top-left values, Display 0 should be "SF350_S24F350FH / S24F352FH / S24F354FH (HDMI)", and why it got swapped with Display 1?

Upvotes: 1

Views: 2513

Answers (3)

ir1keren
ir1keren

Reputation: 77

With @Dmo 's clues, I use this library

            Rectangle rect;
            Display display;

            foreach (PathInfo pi in PathInfo.GetActivePaths())
            {
                if (!pi.TargetsInfo[0].DisplayTarget.IsAvailable) continue;

                rect=System.Windows.Forms.Screen.GetWorkingArea(new Rectangle(pi.Position, pi.Resolution));
                display = new DisplayImpl()
                {
                    _name = string.IsNullOrEmpty(pi.TargetsInfo[0].DisplayTarget.FriendlyName)? "Generic PnP Monitor" : pi.TargetsInfo[0].DisplayTarget.FriendlyName,
                    _deviceId = pi.DisplaySource.DisplayName,
                    _devicePath=pi.TargetsInfo[0].DisplayTarget.DevicePath,
                    _bounds = new Rectangle(pi.Position,pi.Resolution),
                    _workingArea = rect,
                };

                list.Add(display);
            }

It produces the correct monitor name and settings pair! 👍

Upvotes: 0

Drake Wu
Drake Wu

Reputation: 7170

According to the document of the EnumDisplayDevices

To obtain information on a display monitor, first call EnumDisplayDevices with lpDevice set to NULL. Then call EnumDisplayDevices with lpDevice set to DISPLAY_DEVICE.DeviceName from the first call to EnumDisplayDevices and with iDevNum set to zero. Then DISPLAY_DEVICE.DeviceString is the monitor name.

The samples:

private static void QueryDisplayDevices()
{
    DISPLAY_DEVICE device = new DISPLAY_DEVICE();
    device.Initialize();
    uint DispNum = 0;
    while (EnumDisplayDevices(null, DispNum, ref device, 0))
    {
        DISPLAY_DEVICE dev = new DISPLAY_DEVICE();
        dev.Initialize();
        if (EnumDisplayDevices(device.DeviceName, 0, ref dev, 0))
       {
            Console.WriteLine("Device Name:" + dev.DeviceName);
            Console.WriteLine("Monitor name:" + dev.DeviceString);
       }
       DispNum++;
       device.Initialize();
    }
}

Upvotes: 0

Dmo
Dmo

Reputation: 161

It depends on which monitor is defined as the main one. It does not matter the position of each monitor.

This library does the job very well if you want : WindowsDisplayAPI

Upvotes: 1

Related Questions