使用 C# 的有效友好监视器名称
Valid friendly monitor names with C#
我的主要开发机器是一台有 2 个屏幕的笔记本电脑:一个内部屏幕和一个外部三星显示器。
Generic PnP Monitor= 1366x768, Top: 0, Left: 1920 -> secondary display
SF350_S24F350FH / S24F352FH / S24F354FH (HDMI)= 1920x1080, Top: 0, Left: 0 -> main display
我的代码是:
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('[=12=]');
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
根据屏幕分辨率和左上角的值,显示器 0 应该是“SF350_S24F350FH / S24F352FH / S24F354FH (HDMI)”,为什么它与显示器 1 交换了?
这取决于哪个监视器被定义为主监视器。每个显示器的位置无关紧要。
如果您愿意,这个库可以很好地完成这项工作:WindowsDisplayAPI
的文档
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.
样本:
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();
}
}
根据@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);
}
它生成正确的显示器名称和设置对!
我的主要开发机器是一台有 2 个屏幕的笔记本电脑:一个内部屏幕和一个外部三星显示器。
Generic PnP Monitor= 1366x768, Top: 0, Left: 1920 -> secondary display
SF350_S24F350FH / S24F352FH / S24F354FH (HDMI)= 1920x1080, Top: 0, Left: 0 -> main display
我的代码是: 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('[=12=]');
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
根据屏幕分辨率和左上角的值,显示器 0 应该是“SF350_S24F350FH / S24F352FH / S24F354FH (HDMI)”,为什么它与显示器 1 交换了?
这取决于哪个监视器被定义为主监视器。每个显示器的位置无关紧要。
如果您愿意,这个库可以很好地完成这项工作:WindowsDisplayAPI
To obtain information on a display monitor, first call
EnumDisplayDevices
with lpDevice set toNULL
. Then callEnumDisplayDevices
withlpDevice
set toDISPLAY_DEVICE.DeviceName
from the first call toEnumDisplayDevices
and withiDevNum
set tozero
. ThenDISPLAY_DEVICE.DeviceString
is the monitor name.
样本:
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();
}
}
根据@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);
}
它生成正确的显示器名称和设置对!