winapiuwpwin32comwinui-3dpi

This is a way to get the ScreenHeightInRawPixels, RawPixelsPerViewPixel, DiagonalSizeInInches and DpiChanged events of a window's screen in WinUI3


I'm trying to convert my UWP app to WinUI3 and I'm unable to get the monitor information (where my app's windows are located [or belongs to]).

I need to know the following values that I have in my UWP app.

var currentViewDisplay = DisplayInformation.GetForCurrentView();
//currentViewDisplay.ScreenHeightInRawPixels
//currentViewDisplay.RawPixelsPerViewPixel
//currentViewDisplay.ScreenWidthInRawPixels
//currentViewDisplay.RawPixelsPerViewPixel;
//currentViewDisplay.DiagonalSizeInInches
//and
//currentViewDisplay.DpiChanged event

I have added nuget CsWin32 to my project and am trying to explor the following native methods:

GetMonitorInfo
MonitorFromWindow
EnumDisplaySettings
EnumDisplayDevices
DisplayConfigGetDeviceInfo
IDisplayPathInterop
DISPLAYCONFIG_SOURCE_DEVICE_NAME
DISPLAYCONFIG_TARGET_DEVICE_NAME
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)
            {

            }
        }
    }

// or

var displayList = await DeviceInformation.FindAllAsync(DisplayMonitor.GetDeviceSelector());
var monitorInfo = await DisplayMonitor.FromInterfaceIdAsync(displayList[0].Id);

All these methods return me useful information, but I don't know how to find the monitor that my window belongs to.

So I've tried the next and it's frustrating as it doesn't return anything and requirece only header as input. Why only header? Is something wrong with CsWin32?

var ip = WinRT.CastExtensions.As<winmdroot.System.WinRT.Display.IDisplayPathInterop>(path);
var sourceId = ip.GetSourceId();
var targetName = new winmdroot.Devices.Display.DISPLAYCONFIG_TARGET_DEVICE_NAME();
targetName.header.adapterId.LowPart = monitor.DisplayAdapterId.LowPart;
targetName.header.adapterId.HighPart = monitor.DisplayAdapterId.HighPart;
targetName.header.id = sourceId;
targetName.header.type = winmdroot.Devices.Display.DISPLAYCONFIG_DEVICE_INFO_TYPE.DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
targetName.header.size = (uint)Marshal.SizeOf(typeof(winmdroot.Devices.Display.DISPLAYCONFIG_TARGET_DEVICE_NAME));
var dcgdiR = PInvoke.DisplayConfigGetDeviceInfo(ref targetName.header);
// dcgdiR == 31, what does 31 mean?

I also tryied, and there is nothing about monitor physical characteristics. monitor is nint, so just number...

var displayArea = DisplayArea.GetFromWindowId(window.AppWindow.Id, DisplayAreaFallback.Nearest);
var dispalyInfo = Microsoft.Graphics.Display.DisplayInformation.CreateForDisplayId(displayArea.DisplayId);
var monitor = Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId);

Then I tried the next and I expect (I hope, not sure) it is about my app window's monitor and there is some data about resolution and dpi but nothing about DiagonalSizeInInches.

var devmodew = new winmdroot.Graphics.Gdi.DEVMODEW();
PInvoke.EnumDisplaySettings(null, winmdroot.Graphics.Gdi.ENUM_DISPLAY_SETTINGS_MODE.ENUM_CURRENT_SETTINGS, ref devmodew);

Then I tried the next and dv props are kinda 0 or so, nothing useful there.

var dv = new winmdroot.Graphics.Gdi.DISPLAY_DEVICEW();
uint devN = 0;
while (PInvoke.EnumDisplayDevices(null, devN, ref dv, 0) == 1)
{
  devN++;
}

Why do I miss to correlate all this? Is there another way to do it?


Solution

  • A physical monitor can be identified by, for example,

    Note: A monitor handle is different from a physical monitor handle and a monitor handle can be associated with multiple physical monitor handles (See GetPhysicalMonitorsFromHMONITOR). The same applies to display name. A device ID or device path is usually convertible to a device instance ID.

    How to correlate the information on a monitor from various APIs is something like a puzzle using the above information.

    You already have DisplayMonitor. Its DeviceId is namely device ID. Then, you can get monitor handle by MonitorFromWindow or MonitorFromPoint or MonitorFromRect functions. The next thing is to connect device ID and monitor handle. One of the ways to do that is to combine traditional EnumDisplayDevices and EnumDisplayMonitors and GetMonitorInfo functions.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text.RegularExpressions;
    
    internal static class DeviceHelper
    {
        public static IEnumerable<(IntPtr monitorHandle, string displayName, string deviceId, string deviceInstanceId)> EnumerateDisplayDeviceMonitorPairs()
        {
            var devices = GetDisplayDevices();
    
            foreach (var monitor in GetDisplayMonitors())
            {
                foreach (var device in devices.Where(x => x.displayDeviceName == monitor.displayDeviceName))
                {
                    var deviceInstanceId = ConvertToDeviceInstanceId(device.monitorDeviceId);
                    yield return (monitor.monitorHandle, device.displayDeviceName, device.monitorDeviceId, deviceInstanceId);
                }
            }
        }
    
        private static (string displayDeviceName, string monitorDeviceId)[] GetDisplayDevices()
        {
            var list = new List<(string, string)>();
            var size = (uint)Marshal.SizeOf<DISPLAY_DEVICE>();
            var display = new DISPLAY_DEVICE { cb = size };
            var monitor = new DISPLAY_DEVICE { cb = size };
    
            for (uint i = 0; EnumDisplayDevices(null, i, ref display, EDD_GET_DEVICE_INTERFACE_NAME); i++)
            {
                for (uint j = 0; EnumDisplayDevices(display.DeviceName, j, ref monitor, EDD_GET_DEVICE_INTERFACE_NAME); j++)
                {
                    list.Add((display.DeviceName, monitor.DeviceID));
                }
            }
            return list.ToArray();
        }
    
        private static (string displayDeviceName, IntPtr monitorHandle)[] GetDisplayMonitors()
        {
            var list = new List<(string, IntPtr)>();
            var size = (uint)Marshal.SizeOf<MONITORINFOEX>();
    
            EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
                (monitorHandle, hdcMonitor, lprcMonitor, dwData) =>
                {
                    var monitorInfo = new MONITORINFOEX { cbSize = size };
                    if (GetMonitorInfo(monitorHandle, ref monitorInfo))
                    {
                        list.Add((monitorInfo.szDevice, monitorHandle));
                    }
                    return true;
                }, IntPtr.Zero);
            return list.ToArray();
        }
    
        private static string ConvertToDeviceInstanceId(string? deviceId)
        {
            if (!string.IsNullOrEmpty(deviceId))
            {
                var pattern = new Regex(@"\\\?\\DISPLAY#(?<hardware>\w+)#(?<instance>[\w|&]+)#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7}");
                var match = pattern.Match(deviceId);
                if (match.Success)
                {
                    return $@"DISPLAY\{match.Groups["hardware"]}\{match.Groups["instance"]}";
                }
            }
            return string.Empty;
        }
    
        [DllImport("User32.dll", CharSet = CharSet.Ansi)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool EnumDisplayDevices(
            string? lpDevice,
            uint iDevNum,
            ref DISPLAY_DEVICE lpDisplayDevice,
            uint dwFlags);
    
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        private struct DISPLAY_DEVICE
        {
            public uint cb;
    
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string DeviceName;
    
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string DeviceString;
    
            public uint StateFlags;
    
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string DeviceID;
    
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string DeviceKey;
        }
    
        private const uint EDD_GET_DEVICE_INTERFACE_NAME = 0x00000001;
    
        [DllImport("User32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool EnumDisplayMonitors(
            IntPtr hdc,
            IntPtr lprcClip,
            MonitorEnumProc lpfnEnum,
            IntPtr dwData);
    
        [return: MarshalAs(UnmanagedType.Bool)]
        private delegate bool MonitorEnumProc(
            IntPtr hMonitor,
            IntPtr hdcMonitor,
            IntPtr lprcMonitor,
            IntPtr dwData);
    
        [DllImport("User32.dll", CharSet = CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetMonitorInfo(
            IntPtr hMonitor,
            ref MONITORINFOEX lpmi);
    
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public struct MONITORINFOEX
        {
            public uint cbSize;
            public RECT rcMonitor;
            public RECT rcWork;
            public uint dwFlags;
    
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string szDevice;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }
    }
    

    EDIT:

    Regarding the case where a monitor handle is associated with multiple physical monitor handles, it happens when the user mirrors the display by selecting "Duplicate these displays" in "Multiple displays" settings. In such case, naturally you cannot distinguish these monitors by monitor handle nor display name.