DisplayMonitor 到 Forms.Screen 映射

问题描述 投票:0回答:1

我想向用户显示一个下拉列表,其中除了每个条目之外还显示设备名称(如 Windows 分辨率设置)。我可以通过

Windows.Devices.Display.DisplayMonitor
和相关 API 来完成此操作,如下所示:

private async Task CollectMonitors() {
    var displays = await  DeviceInformation.FindAllAsync(DisplayMonitor.GetDeviceSelector());
    List<DisplayMonitor> displayMonitors = new List<DisplayMonitor>();

    foreach (var display in displays)
    {
        displayMonitors.Add(await DisplayMonitor.FromInterfaceIdAsync(display.Id));
    }
    // etc...
}

现在,

displayMonitors
包含所有显示器,并且每个显示器都有一个
DisplayName
属性,这正是Windows屏幕分辨率设置显示的内容(例如
"DELL P2422H"
),以及一个
DeviceId
(例如
\\?\DISPLAY#DELA1C5#4&b8ecaec&0&UID24627#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}
) ) 每个设备都是唯一的。

不会告诉我以下非常重要的信息,我需要这些信息才能实际使用所选屏幕在其上放置 WPF 窗口:

  • 工作区
  • 是否是主显示器

此信息由

System.Windows.Forms.Screen.AllScreens
提供。我对这个 API 的问题是,它包括我需要向用户显示的
DisplayName
(如
"DELL P2422H"
)。

我现在的问题是:

  1. 我可以以某种方式将
    DisplayMonitor
    映射到
    Forms.Screen
    吗?这将是最简单的解决方案。我找不到每个监视器唯一且由这两个 API/结果共享的任何标识符。没有文档说明
    Forms.Screen.AllScreens
    中的屏幕顺序是否与通过
    DisplayMonitors
    枚举
    DeviceInformation.FindAllAsync
    返回的顺序有关,所以可能不是(?)。
  2. 如果这不起作用,是否有办法从
    DisplayMonitor
    、或从 EDID、或从
    DisplayMonitor
    API 提供的任何其他信息或从任何相关 API 获取工作区域的范围我可以在这里使用吗?

相关链接:

这是不是相关问题的回答,这些问题要么a)不提及显示名称,要么b)不提及工作区域/在显示器上生成WPF窗口。

c# wpf windows winapi
1个回答
0
投票

将 WinRT 类与 GDI/GDI+ 类链接起来并不容易,因为 WinRT 类不会公开 GDI 的设备名称,例如 Winforms 使用的

\\?\DISPLAY1
(也可以从 WPF 使用)。

因此您可以使用连接和配置显示器 (CCD) Win32 API(顺便说一句,这是 WinRT 类用来获取显示器的显示名称等的内容。您还可以使用它们来确定 HDR 是否打开/关闭Windows用于获取 HDR(高动态范围)是否处于活动状态的 API),以根据适配器 ID 确定 GDI 设备名称:

public static string GetGdiDeviceName(int adapterIdHigh, uint adapterIdLow, uint sourceId)
{
    var info = new DISPLAYCONFIG_SOURCE_DEVICE_NAME();
    const int DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME = 1;
    info.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
    info.header.size = Marshal.SizeOf<DISPLAYCONFIG_SOURCE_DEVICE_NAME>();
    info.header.adapterIdHigh = adapterIdHigh;
    info.header.adapterIdLow = adapterIdLow;
    info.header.id = sourceId;
    var err = DisplayConfigGetDeviceInfo(ref info);
    if (err != 0)
        throw new Win32Exception(err);

    return info.viewGdiDeviceName;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DISPLAYCONFIG_SOURCE_DEVICE_NAME
{
    public DISPLAYCONFIG_DEVICE_INFO_HEADER header;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string viewGdiDeviceName;

    public override string ToString() => viewGdiDeviceName;
}

[StructLayout(LayoutKind.Sequential)]
private struct DISPLAYCONFIG_DEVICE_INFO_HEADER
{
    public int type;
    public int size;
    public uint adapterIdLow;
    public int adapterIdHigh;
    public uint id;
}

[DllImport("user32")]
private static extern int DisplayConfigGetDeviceInfo(ref DISPLAYCONFIG_SOURCE_DEVICE_NAME requestPacket);

但这需要一个“SourceId”,您可以使用 IDisplayPathInterop 接口从 WinRT 获取

[ComImport, Guid("A6BA4205-E59E-4E71-B25B-4E436D21EE3D"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDisplayPathInterop
{
    [PreserveSig]
    int CreateSourcePresentationHandle(out IntPtr value);

    [PreserveSig]
    int GetSourceId(out uint sourceId);
}

现在您可以使用所有这些来转储最大数量的信息,例如这个 C# 控制台应用程序:

static void Main()
{
    using (var mgr = DisplayManager.Create(DisplayManagerOptions.None))
    {
        var state = mgr.TryReadCurrentStateForAllTargets().State;
        foreach (var view in state.Views)
        {
            foreach (var path in view.Paths)
            {
                var monitor = path.Target.TryGetMonitor();
                if (monitor != null)
                {
                    Console.WriteLine(monitor.DisplayName);
                    var ip = (object)path as IDisplayPathInterop;
                    ip.GetSourceId(out var sourceId);
                    var gdiDeviceName = GetGdiDeviceName(monitor.DisplayAdapterId.HighPart, monitor.DisplayAdapterId.LowPart, sourceId);
                    Console.WriteLine(" GDI Name      : " + gdiDeviceName);

                    // use gdiDeviceName to correlate with other APIs such as Screen from Winforms

                    var screen = Screen.AllScreens.FirstOrDefault(s => s.DeviceName == gdiDeviceName);
                    Console.WriteLine(" Is Primary    : " + screen.Primary);
                    Console.WriteLine(" Working Area  : " + screen.WorkingArea);
                    Console.WriteLine();
                }
            }
        }
    }
}

将输出类似这样的内容(2个显示器):

DELL U2715H
 GDI Name      : \\.\DISPLAY1
 Is Primary    : False
 Working Area  : {X=2560,Y=0,Width=2560,Height=1392}

C27HG7x
 GDI Name      : \\.\DISPLAY2
 Is Primary    : True
 Working Area  : {X=0,Y=0,Width=2560,Height=1392}
© www.soinside.com 2019 - 2024. All rights reserved.