【问题标题】:Valid friendly monitor names with C#使用 C# 的有效友好监视器名称
【发布时间】:2020-02-18 08:50:10
【问题描述】:

我的主要开发机器是一台有 2 个屏幕的笔记本电脑:一个内部屏幕和一个外部三星显示器。

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

我的代码是: 显示.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;
    }
}

然后我尝试调试

if(Display.Displays !=null) { }

我得到了这些结果:

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

根据屏幕分辨率和左上角的值,Display 0 应该是“SF350_S24F350FH / S24F352FH / S24F354FH (HDMI)”,为什么它会与 Display 1 交换?

【问题讨论】:

  • 使用与您相同的代码并设置相同的显示设置进行测试。无法重现,总是按显示设置中的索引顺序返回(正确对应)

标签: c# winapi display monitor names


【解决方案1】:

这取决于哪个监视器被定义为主监视器。每台显示器的位置无关紧要。

如果你愿意,这个库可以很好地完成工作:WindowsDisplayAPI

【讨论】:

  • 是的,我是这么认为的。这意味着GetMonitorInfo()EnumDisplayDevices() 之间存在不一致。 GetMonitorInfo() 将“\\\\.\\DISPLAY1”视为 1920x1080 设备(外部显示器),但 EnumDisplayDevices() 将“\\\\.\\DISPLAY1”视为“通用 PnP 监视器”(内部显示器)。会试试那个图书馆?
【解决方案2】:

根据EnumDisplayDevices的文档

要获取显示监视器上的信息,请先调用 EnumDisplayDevices 将 lpDevice 设置为 NULL。然后打电话 EnumDisplayDeviceslpDevice 设置为 DISPLAY_DEVICE.DeviceName 从第一次调用到 EnumDisplayDevicesiDevNum 设置为 zero。然后 DISPLAY_DEVICE.DeviceString 是监视器名称。

样本:

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();
    }
}

【讨论】:

  • 我在您的示例中添加了EnumDisplaySettings(),它仍然将“通用即插即用监视器”作为 1920x1080 设备(当它应该是 1366x768 设备时)
【解决方案3】:

有了@Dmo的线索,我用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);
            }

它会生成正确的监视器名称和设置对! ?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-18
    • 2017-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多