在开发涉及多显示器的应用程序时,获取显示器的友好名称(Friendly Name)是一个常见需求。本文将深入探讨GetMonitorFriendlyName 方法,了解其实现细节和工作原理。
方法签名
public static string GetMonitorFriendlyName(bool isPrimary)
方法概述
GetMonitorFriendlyName 方法用于获取指定显示器的友好名称。参数 isPrimary 指定是否获取主显示器的友好名称。
实现原理
- 先获取显示设备的目标设备信息和源设备信息,再将目标设备信息(友好名称)和源设备信息(逻辑设备名称)关联起来
- 获取要求获取屏幕(主/副屏)的信息(包含逻辑设备名称)
- 通过逻辑设备名称关联,再找到相应屏幕的友好名称
实现细节
1. 获取屏幕信息:
var screen = ScreenHelper.GetScreen(isPrimary);
2. 获取目标设备名称:
var targetDeviceNames = GeTargetDeviceNames();
调用 GeTargetDeviceNames
方法获取所有活动路径的目标设备名称和源设备名称。
3. 匹配屏幕设备名称:
return targetDeviceNames
.FirstOrDefault(m => m.Item2.viewGdiDeviceName.EqualsIgnoreCase(screen.DeviceName))
.Item1.monitorDevicePath;
核心方法
GeTargetDeviceNames 方法: 该方法获取所有活动路径的目标设备名称和源设备名称,并将目标设备名称和源设备名称相匹配。其实现涉及调用 Windows API 函数 GetDisplayConfigBufferSizes 和 QueryDisplayConfig,并解析返回的显示配置信息。
/// <summary>
/// 获取显示器FriendlyName名称
/// </summary>
/// <returns></returns>
public static IEnumerable<(DISPLAYCONFIG_TARGET_DEVICE_NAME, DISPLAYCONFIG_SOURCE_DEVICE_NAME)>
GeTargetDeviceNames()
{
List<(DISPLAYCONFIG_TARGET_DEVICE_NAME, DISPLAYCONFIG_SOURCE_DEVICE_NAME)> listRet = new();
try
{
int error = GetDisplayConfigBufferSizes(QUERY_DEVICE_CONFIG_FLAGS.QDC_ONLY_ACTIVE_PATHS,
out var pathCount, out var modeCount);
if (error != ERROR_SUCCESS)
throw new Win32Exception(error);
DISPLAYCONFIG_PATH_INFO[] DisplayPaths = new DISPLAYCONFIG_PATH_INFO[pathCount];
DISPLAYCONFIG_MODE_INFO[] DisplayModes = new DISPLAYCONFIG_MODE_INFO[modeCount];
error = QueryDisplayConfig(QUERY_DEVICE_CONFIG_FLAGS.QDC_ONLY_ACTIVE_PATHS,
ref pathCount, DisplayPaths, ref modeCount, DisplayModes, IntPtr.Zero);
if (error != ERROR_SUCCESS)
throw new Win32Exception(error);
for (int i = 0; i < modeCount; i++)
{
var modeInfo = DisplayModes[i];
if (modeInfo.infoType == DISPLAYCONFIG_MODE_INFO_TYPE.DISPLAYCONFIG_MODE_INFO_TYPE_TARGET)
{
var targetName =
GetDisplayconfigTargetDeviceName(DisplayModes[i].adapterId, DisplayModes[i].id);
var displayPathInfo = DisplayPaths.FirstOrDefault(m =>
m.targetInfo.adapterId.LowPart == modeInfo.adapterId.LowPart &&
m.targetInfo.adapterId.HighPart == modeInfo.adapterId.HighPart &&
m.targetInfo.id == modeInfo.id);
var sourceName = GetDisplayconfigSourceDeviceName(displayPathInfo.sourceInfo.adapterId,
displayPathInfo.sourceInfo.id);
listRet.Add((targetName, sourceName));
}
}
}
catch (Exception ex)
{
}
return listRet;
}
相关辅助方法
- GetDisplayconfigTargetDeviceName 方法:获取目标设备的详细信息,包括友好名称
- GetDisplayconfigSourceDeviceName 方法: 获取源设备的详细信息,包括显示设备逻辑名称。
public const int ERROR_SUCCESS = 0;
public enum QUERY_DEVICE_CONFIG_FLAGS : uint
{
QDC_ALL_PATHS = 0x00000001,
QDC_ONLY_ACTIVE_PATHS = 0x00000002,
QDC_DATABASE_CURRENT = 0x00000004
}
public enum DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY : uint
{
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER = 0xFFFFFFFF,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HD15 = 0,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SVIDEO = 1,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPOSITE_VIDEO = 2,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPONENT_VIDEO = 3,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DVI = 4,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI = 5,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_LVDS = 6,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_D_JPN = 8,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDI = 9,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL = 10,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED = 11,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EXTERNAL = 12,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED = 13,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDTVDONGLE = 14,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_MIRACAST = 15,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL = 0x80000000,
DISPLAYCONFIG_OUTPUT_TECHNOLOGY_FORCE_UINT32 = 0xFFFFFFFF
}
public enum DISPLAYCONFIG_SCANLINE_ORDERING : uint
{
DISPLAYCONFIG_SCANLINE_ORDERING_UNSPECIFIED = 0,
DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE = 1,
DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED = 2,
DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_UPPERFIELDFIRST = DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED,
DISPLAYCONFIG_SCANLINE_ORDERING_INTERLACED_LOWERFIELDFIRST = 3,
DISPLAYCONFIG_SCANLINE_ORDERING_FORCE_UINT32 = 0xFFFFFFFF
}
public enum DISPLAYCONFIG_ROTATION : uint
{
DISPLAYCONFIG_ROTATION_IDENTITY = 1,
DISPLAYCONFIG_ROTATION_ROTATE90 = 2,
DISPLAYCONFIG_ROTATION_ROTATE180 = 3,
DISPLAYCONFIG_ROTATION_ROTATE270 = 4,
DISPLAYCONFIG_ROTATION_FORCE_UINT32 = 0xFFFFFFFF
}
public enum DISPLAYCONFIG_SCALING : uint
{
DISPLAYCONFIG_SCALING_IDENTITY = 1,
DISPLAYCONFIG_SCALING_CENTERED = 2,
DISPLAYCONFIG_SCALING_STRETCHED = 3,
DISPLAYCONFIG_SCALING_ASPECTRATIOCENTEREDMAX = 4,
DISPLAYCONFIG_SCALING_CUSTOM = 5,
DISPLAYCONFIG_SCALING_PREFERRED = 128,
DISPLAYCONFIG_SCALING_FORCE_UINT32 = 0xFFFFFFFF
}
public enum DISPLAYCONFIG_PIXELFORMAT : uint
{
DISPLAYCONFIG_PIXELFORMAT_8BPP = 1,
DISPLAYCONFIG_PIXELFORMAT_16BPP = 2,
DISPLAYCONFIG_PIXELFORMAT_24BPP = 3,
DISPLAYCONFIG_PIXELFORMAT_32BPP = 4,
DISPLAYCONFIG_PIXELFORMAT_NONGDI = 5,
DISPLAYCONFIG_PIXELFORMAT_FORCE_UINT32 = 0xffffffff
}
public enum DISPLAYCONFIG_MODE_INFO_TYPE : uint
{
DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE = 1,
DISPLAYCONFIG_MODE_INFO_TYPE_TARGET = 2,
DISPLAYCONFIG_MODE_INFO_TYPE_FORCE_UINT32 = 0xFFFFFFFF
}
public enum DISPLAYCONFIG_DEVICE_INFO_TYPE : uint
{
DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME = 1,
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME = 2,
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE = 3,
DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME = 4,
DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE = 5,
DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE = 6,
DISPLAYCONFIG_DEVICE_INFO_FORCE_UINT32 = 0xFFFFFFFF
}
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public uint LowPart;
public int HighPart;
}
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_PATH_SOURCE_INFO
{
public LUID adapterId;
public uint id;
public uint modeInfoIdx;
public uint statusFlags;
}
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_PATH_TARGET_INFO
{
public LUID adapterId;
public uint id;
public uint modeInfoIdx;
DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY outputTechnology;
DISPLAYCONFIG_ROTATION rotation;
DISPLAYCONFIG_SCALING scaling;
DISPLAYCONFIG_RATIONAL refreshRate;
DISPLAYCONFIG_SCANLINE_ORDERING scanLineOrdering;
public bool targetAvailable;
public uint statusFlags;
}
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_RATIONAL
{
public uint Numerator;
public uint Denominator;
}
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_PATH_INFO
{
public DISPLAYCONFIG_PATH_SOURCE_INFO sourceInfo;
public DISPLAYCONFIG_PATH_TARGET_INFO targetInfo;
public uint flags;
}
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_2DREGION
{
public uint cx;
public uint cy;
}
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_VIDEO_SIGNAL_INFO
{
public ulong pixelRate;
public DISPLAYCONFIG_RATIONAL hSyncFreq;
public DISPLAYCONFIG_RATIONAL vSyncFreq;
public DISPLAYCONFIG_2DREGION activeSize;
public DISPLAYCONFIG_2DREGION totalSize;
public uint videoStandard;
public DISPLAYCONFIG_SCANLINE_ORDERING scanLineOrdering;
}
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_TARGET_MODE
{
public DISPLAYCONFIG_VIDEO_SIGNAL_INFO targetVideoSignalInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINTL
{
int x;
int y;
}
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_SOURCE_MODE
{
public uint width;
public uint height;
public DISPLAYCONFIG_PIXELFORMAT pixelFormat;
public POINTL position;
}
[StructLayout(LayoutKind.Explicit)]
public struct DISPLAYCONFIG_MODE_INFO_UNION
{
[FieldOffset(0)]
public DISPLAYCONFIG_TARGET_MODE targetMode;
[FieldOffset(0)]
public DISPLAYCONFIG_SOURCE_MODE sourceMode;
}
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_MODE_INFO
{
public DISPLAYCONFIG_MODE_INFO_TYPE infoType;
public uint id;
public LUID adapterId;
public DISPLAYCONFIG_MODE_INFO_UNION modeInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS
{
public uint value;
}
[StructLayout(LayoutKind.Sequential)]
public struct DISPLAYCONFIG_DEVICE_INFO_HEADER
{
public DISPLAYCONFIG_DEVICE_INFO_TYPE type;
public uint size;
public LUID adapterId;
public uint id;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DISPLAYCONFIG_TARGET_DEVICE_NAME
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
public DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS flags;
public DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY outputTechnology;
public ushort edidManufactureId;
public ushort edidProductCodeId;
public uint connectorInstance;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string monitorFriendlyDeviceName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string monitorDevicePath;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public partial struct DISPLAYCONFIG_SOURCE_DEVICE_NAME
{
public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string viewGdiDeviceName;
}
[DllImport("user32.dll")]
public static extern int GetDisplayConfigBufferSizes(
QUERY_DEVICE_CONFIG_FLAGS Flags,
out uint NumPathArrayElements,
out uint NumModeInfoArrayElements
);
[DllImport("user32.dll")]
public static extern int QueryDisplayConfig(
QUERY_DEVICE_CONFIG_FLAGS Flags,
ref uint NumPathArrayElements,
[Out] DISPLAYCONFIG_PATH_INFO[] PathInfoArray,
ref uint NumModeInfoArrayElements,
[Out] DISPLAYCONFIG_MODE_INFO[] ModeInfoArray,
IntPtr CurrentTopologyId
);
[DllImport("user32.dll")]
public static extern int DisplayConfigGetDeviceInfo(
ref DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName
);
[DllImport("user32.dll")]
public static extern int DisplayConfigGetDeviceInfo(
ref DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName
);
[DllImport("user32", CharSet = CharSet.Auto)]
public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
/// <summary>
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto,Pack=4)]
public struct MONITORINFO
{
/// <summary>
/// </summary>
public int cbSize;
/// <summary>
/// </summary>
public ScreenHelper.RECT rcMonitor;
/// <summary>
/// </summary>
public ScreenHelper.RECT rcWork;
/// <summary>
/// </summary>
public int dwFlags ;
/*[MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]
public char[] szDevice;*/
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string DeviceName;
}
public static DISPLAYCONFIG_TARGET_DEVICE_NAME GetDisplayconfigTargetDeviceName(LUID adapterId, uint targetId)
{
DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = new DISPLAYCONFIG_TARGET_DEVICE_NAME();
deviceName.header.size = (uint)Marshal.SizeOf(typeof(DISPLAYCONFIG_TARGET_DEVICE_NAME));
deviceName.header.adapterId = adapterId;
deviceName.header.id = targetId;
deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
int error = DisplayConfigGetDeviceInfo(ref deviceName);
if (error != ERROR_SUCCESS)
throw new Win32Exception(error);
return deviceName;
}
public static DISPLAYCONFIG_SOURCE_DEVICE_NAME GetDisplayconfigSourceDeviceName(LUID adapterId, uint targetId)
{
DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName = new DISPLAYCONFIG_SOURCE_DEVICE_NAME();
deviceName.header.size = (uint)Marshal.SizeOf(typeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME));
deviceName.header.adapterId = adapterId;
deviceName.header.id = targetId;
deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
int error = DisplayConfigGetDeviceInfo(ref deviceName);
if (error != ERROR_SUCCESS)
throw new Win32Exception(error);
return deviceName;
}
示例代码
以下是 GetMonitorFriendlyName 方法的完整实现:
public static string GetMonitorFriendlyName(bool isPrimary)
{
try
{
var screen = ScreenHelper.GetScreen(isPrimary);
var targetDeviceNames = GeTargetDeviceNames();
return targetDeviceNames
.FirstOrDefault(m => m.Item2.viewGdiDeviceName.EqualsIgnoreCase(screen.DeviceName))
.Item1.monitorDevicePath;
}
catch (Exception ex)
{
return string.Empty;
}
}