How to get scaling factor for each monitor, e.g. 1, 1.25, 1.5
Asked Answered
B

4

9

I know this has been asked before, but I've tried all the answers I've found and none of them seem to work for me. Answers seem to work on a single monitor, or require a window handle, or to be in a WPF application. I've a C# class library with no UI that is called from a different language all together.

I've been asked to determine the scaling factor, e.g. 1, 1.25, 1.5, etc. for each monitor attached to the current PC in a C# class library.

I also need to provide the resolution and colour depth for each monitor. The registry does hold the DpiValue, whatever that is, in Windows 10 under

Computer\HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings

However I have no idea how to map those to a Screen in order to get the matching resolution returned in

System.Windows.Forms.Screen.AllScreens

So does anyone have a way of getting this information?

Bree answered 26/3, 2020 at 16:42 Comment(0)
B
22

I believe I have finally (after a long time of searching) found an answer that works, it even works on my high DPI Surface Book 2 screen. I have tested it as much as I can, and so far it's always returned the correct value.

Here's how I did it, thanks to whoever posted the code fragments in the past where I gathered this from.

First you need a structure to call EnumDisplaySettings in user32.dll

    [StructLayout(LayoutKind.Sequential)]
    public struct DEVMODE
    {
        private const int CCHDEVICENAME = 0x20;
        private const int CCHFORMNAME = 0x20;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
        public string dmDeviceName;
        public short dmSpecVersion;
        public short dmDriverVersion;
        public short dmSize;
        public short dmDriverExtra;
        public int dmFields;
        public int dmPositionX;
        public int dmPositionY;
        public ScreenOrientation dmDisplayOrientation;
        public int dmDisplayFixedOutput;
        public short dmColor;
        public short dmDuplex;
        public short dmYResolution;
        public short dmTTOption;
        public short dmCollate;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
        public string dmFormName;
        public short dmLogPixels;
        public int dmBitsPerPel;
        public int dmPelsWidth;
        public int dmPelsHeight;
        public int dmDisplayFlags;
        public int dmDisplayFrequency;
        public int dmICMMethod;
        public int dmICMIntent;
        public int dmMediaType;
        public int dmDitherType;
        public int dmReserved1;
        public int dmReserved2;
        public int dmPanningWidth;
        public int dmPanningHeight;
    }

Then you need to declare the external function call

[DllImport("user32.dll")]
public static extern bool EnumDisplaySettings(string lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode);

Then you need to use it to calculate the screen scaling

            Screen[] screenList = Screen.AllScreens;

            foreach (Screen screen in screenList)
            {
                DEVMODE dm = new DEVMODE();
                dm.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE));
                EnumDisplaySettings(screen.DeviceName, -1, ref dm);

                var scalingFactor = Math.Round(Decimal.Divide(dm.dmPelsWidth, screen.Bounds.Width), 2);
            }

Hope others find this useful.

Bree answered 2/4, 2020 at 8:17 Comment(7)
Thanks, it works! Strange that nobody tried it and so few likes.Subversive
It works but I've been seeing some pretty weird runtime errors about methods not being found and types not being loaded in code after this call. It conforms to the description on pinvoke.net/default.aspx/user32.enumdisplaysettings so might be unrelated, but my hunch they are related. Will keep you posted.Hazaki
You don't know how long I was searching for this solution!Giralda
Works but i needed to turn off exe->properties->compatibility->High DPI Scaling override->Application which in return makes my application look blurry, so i am back at start and again playing with manifest and app.config dpi awareness settings :-(Ilonailonka
For me under Windows11, it's not working at all, always give either 0 or 1 for scalingFactor.Constitution
Same to me. Always 1.Bonkers
It works under Windows 11 if the application is not DPI Aware, this setting can be turned on in various ways such as manifest files, config files, Compatibility Settings. So a new solution is required to work with both.Bree
B
3

As it was mentioned in the comments, the accepted answer doesn't work for Windows 11 at all. After all the research, I found a solution that works well on Windows 11 and shows the correct scales for each monitor. The key idea contains a few steps:

  1. Check if the OS version supports DPI per monitor using this struct
  2. If it doesn't support: try to get DPI via the Control built-in function: DeviceDpi
  3. If it supports:
  4. Get the monitor by point (you can use Screen.Bounds.Left+1) with: MonitorFromPoint;
  5. Get the DPI for this monitor with: GetDpiForMonitor The final scale value is DPI / 100 * 96.

The full script is below:

public static class DPIUtil
{
    /// <summary>
    /// Min OS version build that supports DPI per monitor
    /// </summary>
    private const int MinOSVersionBuild = 14393;

    /// <summary>
    /// Min OS version major build that support DPI per monitor
    /// </summary>
    private const int MinOSVersionMajor = 10;

    /// <summary>
    /// Flag, if OS supports DPI per monitor
    /// </summary>
    private static bool _isSupportingDpiPerMonitor;

    /// <summary>
    /// Flag, if OS version checked
    /// </summary>
    private static bool _isOSVersionChecked;

    /// <summary>
    /// Flag, if OS supports DPI per monitor
    /// </summary>
    internal static bool IsSupportingDpiPerMonitor
    {
        get
        {
            if (_isOSVersionChecked)
            {
                return _isSupportingDpiPerMonitor;
            }

            _isOSVersionChecked = true;
            var osVersionInfo = new OSVERSIONINFOEXW
            {
                dwOSVersionInfoSize = Marshal.SizeOf(typeof(OSVERSIONINFOEXW))
            };

            if (RtlGetVersion(ref osVersionInfo) != 0)
            {
                _isSupportingDpiPerMonitor = Environment.OSVersion.Version.Major >= MinOSVersionMajor && Environment.OSVersion.Version.Build >= MinOSVersionBuild;

                return _isSupportingDpiPerMonitor;
            }

            _isSupportingDpiPerMonitor = osVersionInfo.dwMajorVersion >= MinOSVersionMajor && osVersionInfo.dwBuildNumber >= MinOSVersionBuild;

            return _isSupportingDpiPerMonitor;
        }
    }

    /// <summary>
    /// Get scale factor for an each monitor
    /// </summary>
    /// <param name="control"> Any control for OS who doesn't support DPI per monitor </param>
    /// <param name="monitorPoint"> Monitor point (Screen.Bounds) </param>
    /// <returns> Scale factor </returns>
    public static double ScaleFactor(Control control, Point monitorPoint)
    {
        var dpi = GetDpi(control, monitorPoint);

        return dpi * 100 / 96.0;
    }

    /// <summary>
    /// Get DPI for a monitor
    /// </summary>
    /// <param name="control"> Any control for OS who doesn't support DPI per monitor </param>
    /// <param name="monitorPoint"> Monitor point (Screen.Bounds) </param>
    /// <returns> DPI </returns>
    public static uint GetDpi(Control control, Point monitorPoint)
    {
        uint dpiX;

        if (IsSupportingDpiPerMonitor)
        {
            var monitorFromPoint = MonitorFromPoint(monitorPoint, 2);

            GetDpiForMonitor(monitorFromPoint, DpiType.Effective, out dpiX, out _);
        }
        else
        {
            // If using with System.Windows.Forms - can be used Control.DeviceDpi
            dpiX = control == null ? 96 : (uint)control.DeviceDpi;
        }

        return dpiX;
    }

    /// <summary>
    /// Retrieves a handle to the display monitor that contains a specified point.
    /// </summary>
    /// <param name="pt"> Specifies the point of interest in virtual-screen coordinates. </param>
    /// <param name="dwFlags"> Determines the function's return value if the point is not contained within any display monitor. </param>
    /// <returns> If the point is contained by a display monitor, the return value is an HMONITOR handle to that display monitor. </returns>
    /// <remarks>
    /// <see cref="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfrompoint"/>
    /// </remarks>
    [DllImport("User32.dll")]
    internal static extern IntPtr MonitorFromPoint([In] Point pt, [In] uint dwFlags);

    /// <summary>
    /// Queries the dots per inch (dpi) of a display.
    /// </summary>
    /// <param name="hmonitor"> Handle of the monitor being queried. </param>
    /// <param name="dpiType"> The type of DPI being queried. </param>
    /// <param name="dpiX"> The value of the DPI along the X axis. </param>
    /// <param name="dpiY"> The value of the DPI along the Y axis. </param>
    /// <returns> Status success </returns>
    /// <remarks>
    /// <see cref="https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getdpiformonitor"/>
    /// </remarks>
    [DllImport("Shcore.dll")]
    private static extern IntPtr GetDpiForMonitor([In] IntPtr hmonitor, [In] DpiType dpiType, [Out] out uint dpiX, [Out] out uint dpiY);

    /// <summary>
    /// The RtlGetVersion routine returns version information about the currently running operating system.
    /// </summary>
    /// <param name="versionInfo"> Operating system version information </param>
    /// <returns> Status success</returns>
    /// <remarks>
    /// <see cref="https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlgetversion"/>
    /// </remarks>
    [SecurityCritical]
    [DllImport("ntdll.dll", SetLastError = true)]
    private static extern int RtlGetVersion(ref OSVERSIONINFOEXW versionInfo);

    /// <summary>
    /// Contains operating system version information.
    /// </summary>
    /// <remarks>
    /// <see cref="https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexw"/>
    /// </remarks>
    [StructLayout(LayoutKind.Sequential)]
    private struct OSVERSIONINFOEXW
    {
        /// <summary>
        /// The size of this data structure, in bytes
        /// </summary>
        internal int dwOSVersionInfoSize;

        /// <summary>
        /// The major version number of the operating system.
        /// </summary>
        internal int dwMajorVersion;

        /// <summary>
        /// The minor version number of the operating system.
        /// </summary>
        internal int dwMinorVersion;

        /// <summary>
        /// The build number of the operating system.
        /// </summary>
        internal int dwBuildNumber;

        /// <summary>
        /// The operating system platform.
        /// </summary>
        internal int dwPlatformId;

        /// <summary>
        /// A null-terminated string, such as "Service Pack 3", that indicates the latest Service Pack installed on the system.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        internal string szCSDVersion;

        /// <summary>
        /// The major version number of the latest Service Pack installed on the system. 
        /// </summary>
        internal ushort wServicePackMajor;

        /// <summary>
        /// The minor version number of the latest Service Pack installed on the system.
        /// </summary>
        internal ushort wServicePackMinor;

        /// <summary>
        /// A bit mask that identifies the product suites available on the system. 
        /// </summary>
        internal short wSuiteMask;

        /// <summary>
        /// Any additional information about the system.
        /// </summary>
        internal byte wProductType;

        /// <summary>
        /// Reserved for future use.
        /// </summary>
        internal byte wReserved;
    }

    /// <summary>
    /// DPI type
    /// </summary>
    /// <remarks>
    /// <see cref="https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/ne-shellscalingapi-monitor_dpi_type"/>
    /// </remarks>
    private enum DpiType
    {
        /// <summary>
        /// The effective DPI. This value should be used when determining the correct scale factor for scaling UI elements.
        /// </summary>
        Effective = 0,

        /// <summary>
        /// The angular DPI. This DPI ensures rendering at a compliant angular resolution on the screen.
        /// </summary>
        Angular = 1,

        /// <summary>
        /// The raw DPI. This value is the linear DPI of the screen as measured on the screen itself. Use this value when you want to read the pixel density and not the recommended scaling setting.
        /// </summary>
        Raw = 2,
    }
}
Bonkers answered 4/6, 2023 at 20:18 Comment(6)
Thanks, looks interesting. Sorry to see my solution has stopped working. Microsoft haven't made it simple! I haven't had a chance to try this one yet, but I have three monitors attached to my PC and I want to find the resolution and scaling for all of them, and I'm not sure how you would do this? I am not using System.Windows.Forms, but a class library, so there are no graphical controls anywhere on the screen.Bree
In this case you need to get a screen first (using Screens for example) and call to 'ScaleFactor(null, Screen.Bounds.X+1)'Bonkers
DPIUtil.ScaleFactor(this, new Point(0, 0)) gives me the exact number. 100=100%, 250=250%. Thanks!Cinquefoil
In my WinForms application (.NET Framework) this line gives a compilation error: dpiX = control == null ? 96 : (uint)control.DeviceDpi;. There is no DeviceDpi property in control.Mahdi
Also, it returns 100 on my Windows 11 (build 22631.3085) with a display scaling of 150%.Mahdi
The following answer worked for a brand new application, but it didn't work when I integrated into an existing application (using identical framework): stackoverflow.com/a/54424798Mahdi
B
2

Unfortunately, the answer of user3225503 seems not to work (anymore?)

My scenario: WIN10 20H2, WPF-App with dpi-awareness "PerMonitor", Framework 4.7.2, 2 Monitors with different resolutions and different screen scalings ("Horror scenario"):

the dm.dmPelsWidth member of the DEVMODE structure has always the physical resolution of my monitors, so the scaling is always 1.0.

All what we want is to restore our program and its windows like we left it in the last session right? This seems to be incredibly hard, thanks to MS!

But a different approach seems to work:

  1. Switch on per-monitor dpi-awareness in the manifest file of your application:

    <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <!-- The combination of below two tags have the following effect : 
      1) Per-Monitor for >= Windows 10 Anniversary Update
      2) System < Windows 10 Anniversary Update -->
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> PerMonitor</dpiAwareness>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
    
  2. Always use GetPlacement and SetPlacement win32-api calls for storing/restoring window placements

  3. SetPlacement will set the wrong dialog width/height if the dialog is on a secondary display and each display has different scalings. So we need a new factor depending on scaling factors of each display to correct this in the Loading-event of the window:

event code:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
  if (string.IsNullOrWhiteSpace(Properties.Settings.Default.Placement))
    return;
  ScreenExtensions.WINDOWPLACEMENT placement = new ScreenExtensions.WINDOWPLACEMENT();
  placement.ReadFromBase64String(Properties.Settings.Default.Placement);
  System.Windows.Interop.HwndSource shwnd = System.Windows.Interop.HwndSource.FromVisual(this) as System.Windows.Interop.HwndSource;

  double PrimaryMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(1, 1));
  double CurrentMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(placement.rcNormalPosition.left, placement.rcNormalPosition.top));
  double RescaleFactor = CurrentMonitorScaling / PrimaryMonitorScaling;
  double width = placement.rcNormalPosition.right - placement.rcNormalPosition.left;
  double height = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top;
  placement.rcNormalPosition.right = placement.rcNormalPosition.left + (int)(width / RescaleFactor + 0.5);
  placement.rcNormalPosition.bottom = placement.rcNormalPosition.top + (int)(height / RescaleFactor + 0.5);
  ScreenExtensions.SetPlacement(shwnd.Handle, placement);
}
  1. There are some more goodies in the code example, e.g. serialization of the WINDOWPLACEMENT structure. don't forget to create a member "Placement" in your application settings! Tell me if this works for you:

Example code:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Windows;

namespace DpiApp
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
    }

    private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
      System.Windows.Interop.HwndSource shwnd = System.Windows.Interop.HwndSource.FromVisual(this) as System.Windows.Interop.HwndSource;
      var plc = ScreenExtensions.GetPlacement(shwnd.Handle);
      Properties.Settings.Default.Placement = plc.ToString();
      Properties.Settings.Default.Save();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
      if (string.IsNullOrWhiteSpace(Properties.Settings.Default.Placement))
        return;
      ScreenExtensions.WINDOWPLACEMENT placement = new ScreenExtensions.WINDOWPLACEMENT();
      placement.ReadFromBase64String(Properties.Settings.Default.Placement);
      System.Windows.Interop.HwndSource shwnd = System.Windows.Interop.HwndSource.FromVisual(this) as System.Windows.Interop.HwndSource;

      double PrimaryMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(1, 1));
      double CurrentMonitorScaling = ScreenExtensions.GetScalingForPoint(new System.Drawing.Point(placement.rcNormalPosition.left, placement.rcNormalPosition.top));
      double RescaleFactor = CurrentMonitorScaling / PrimaryMonitorScaling;
      double width = placement.rcNormalPosition.right - placement.rcNormalPosition.left;
      double height = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top;
      placement.rcNormalPosition.right = placement.rcNormalPosition.left + (int)(width / RescaleFactor + 0.5);
      placement.rcNormalPosition.bottom = placement.rcNormalPosition.top + (int)(height / RescaleFactor + 0.5);
      ScreenExtensions.SetPlacement(shwnd.Handle, placement);
    }
  }

  public static class ScreenExtensions
  {
    public const string User32 = "user32.dll";
    public const string shcore = "Shcore.dll";
    public static void GetDpi(this System.Windows.Forms.Screen screen, DpiType dpiType, out uint dpiX, out uint dpiY)
    {
      var pnt = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
      var mon = MonitorFromPoint(pnt, 2/*MONITOR_DEFAULTTONEAREST*/);
      GetDpiForMonitor(mon, dpiType, out dpiX, out dpiY);
    }

    public static double GetScalingForPoint(System.Drawing.Point aPoint)
    {
      var mon = MonitorFromPoint(aPoint, 2/*MONITOR_DEFAULTTONEAREST*/);
      uint dpiX, dpiY;
      GetDpiForMonitor(mon, DpiType.Effective, out dpiX, out dpiY);
      return (double)dpiX / 96.0;
    }

   
    [DllImport(User32)]
    private static extern IntPtr MonitorFromPoint([In] System.Drawing.Point pt, [In] uint dwFlags);

    
    [DllImport(shcore)]
    private static extern IntPtr GetDpiForMonitor([In] IntPtr hmonitor, [In] DpiType dpiType, [Out] out uint dpiX, [Out] out uint dpiY);

    [DllImport(User32, CharSet = CharSet.Auto)]
    [ResourceExposure(ResourceScope.None)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);

    [DllImport(User32, CharSet = CharSet.Auto, SetLastError = true)]
    [ResourceExposure(ResourceScope.None)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);

    public enum DpiType
    {
      Effective = 0,
      Angular = 1,
      Raw = 2,
    }

    public static WINDOWPLACEMENT GetPlacement(IntPtr hWnd)
    {
      WINDOWPLACEMENT placement = new WINDOWPLACEMENT();
      placement.length = Marshal.SizeOf(placement);
      GetWindowPlacement(hWnd, ref placement);
      return placement;
    }

    public static bool SetPlacement(IntPtr hWnd, WINDOWPLACEMENT aPlacement)
    {
      bool erg = SetWindowPlacement(hWnd, ref aPlacement);
      return erg;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct POINTSTRUCT
    {
      public int x;
      public int y;
      public POINTSTRUCT(int x, int y)
      {
        this.x = x;
        this.y = y;
      }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
      public int left;
      public int top;
      public int right;
      public int bottom;

      public RECT(int left, int top, int right, int bottom)
      {
        this.left = left;
        this.top = top;
        this.right = right;
        this.bottom = bottom;
      }

      public RECT(Rect r)
      {
        this.left = (int)r.Left;
        this.top = (int)r.Top;
        this.right = (int)r.Right;
        this.bottom = (int)r.Bottom;
      }

      public static RECT FromXYWH(int x, int y, int width, int height)
      {
        return new RECT(x, y, x + width, y + height);
      }

      public Size Size
      {
        get { return new Size(this.right - this.left, this.bottom - this.top); }
      }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct WINDOWPLACEMENT
    {
      public int length;
      public uint flags;
      public uint showCmd;
      public POINTSTRUCT ptMinPosition;
      public POINTSTRUCT ptMaxPosition;
      public RECT rcNormalPosition;

      public override string ToString()
      {
        byte[] StructBytes = RawSerialize(this);
        return System.Convert.ToBase64String(StructBytes);
      }

      public void ReadFromBase64String(string aB64)
      {
        byte[] b64 = System.Convert.FromBase64String(aB64);
        var NewWP = ReadStruct<WINDOWPLACEMENT>(b64, 0);
        length = NewWP.length;
        flags = NewWP.flags;
        showCmd = NewWP.showCmd;
        ptMinPosition.x = NewWP.ptMinPosition.x;
        ptMinPosition.y = NewWP.ptMinPosition.y;
        ptMaxPosition.x = NewWP.ptMaxPosition.x;
        ptMaxPosition.y = NewWP.ptMaxPosition.y;
        rcNormalPosition.left = NewWP.rcNormalPosition.left;
        rcNormalPosition.top = NewWP.rcNormalPosition.top;
        rcNormalPosition.right = NewWP.rcNormalPosition.right;
        rcNormalPosition.bottom = NewWP.rcNormalPosition.bottom;
      }

      static public T ReadStruct<T>(byte[] aSrcBuffer, int aOffset)
      {
        byte[] buffer = new byte[Marshal.SizeOf(typeof(T))];
        Buffer.BlockCopy(aSrcBuffer, aOffset, buffer, 0, Marshal.SizeOf(typeof(T)));
        GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        T temp = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
        handle.Free();
        return temp;
      }

      static public T ReadStruct<T>(Stream fs)
      {
        byte[] buffer = new byte[Marshal.SizeOf(typeof(T))];
        fs.Read(buffer, 0, Marshal.SizeOf(typeof(T)));
        GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        T temp = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
        handle.Free();
        return temp;
      }

      public static byte[] RawSerialize(object anything)
      {
        int rawsize = Marshal.SizeOf(anything);
        byte[] rawdata = new byte[rawsize];
        GCHandle handle = GCHandle.Alloc(rawdata, GCHandleType.Pinned);
        Marshal.StructureToPtr(anything, handle.AddrOfPinnedObject(), false);
        handle.Free();
        return rawdata;
      }
    }
  }
}
Bergstein answered 26/11, 2020 at 10:53 Comment(0)
S
0

I think you can get scaling factor for each monitor like this.

public void GetScalingFactor()
{
    List<double> physicalWidths = new List<double>();

    //Get physical width for each monitor
    ManagementObjectSearcher searcher = new ManagementObjectSearcher("\\root\\wmi", "SELECT * FROM WmiMonitorBasicDisplayParams");

    foreach (ManagementObject monitor in searcher.Get())
    {
        //Get the physical width (inch)
        double width = (byte)monitor["MaxHorizontalImageSize"] / 2.54;
        physicalWidths.Add(width);
    }

    //Get screen info for each monitor
    Screen[] screenList = Screen.AllScreens;
    int i = 0;

    foreach (Screen screen in screenList)
    {
        //Get the physical width (pixel)
        double physicalWidth;
        if (i < physicalWidths.Count)
        {
            //Get the DPI
            uint x, y;
            GetDpi(screen, DpiType.Effective, out x, out y);

            //Convert inch to pixel
            physicalWidth = physicalWidths[i] * x;
        }
        else
        {
            physicalWidth = SystemParameters.PrimaryScreenWidth;
        }
        i++;

        //Calculate the scaling
        double scaling = 100 * (physicalWidth / screen.Bounds.Width);
        double scalingFactor = physicalWidth / screen.Bounds.Width;

        //Output the result
        Console.WriteLine(scalingFactor);
    }
}

And you need also add these codes to use to get the monitor DPI (these code is from https://mcmap.net/q/658124/-how-to-get-dpi-scale-for-all-screens, thanks @Koopakiller):

public void GetDpi(Screen screen, DpiType dpiType, out uint dpiX, out uint dpiY)
{
    var pnt = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
    var mon = MonitorFromPoint(pnt, 2/*MONITOR_DEFAULTTONEAREST*/);
    GetDpiForMonitor(mon, dpiType, out dpiX, out dpiY);
}

//https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062(v=vs.85).aspx
[DllImport("User32.dll")]
private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);

//https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
[DllImport("Shcore.dll")]
private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);

//https://msdn.microsoft.com/en-us/library/windows/desktop/dn280511(v=vs.85).aspx
public enum DpiType
{
    Effective = 0,
    Angular = 1,
    Raw = 2,
}
Siliculose answered 26/3, 2020 at 19:16 Comment(2)
Thank you. Looks promising but I am getting a similar result to some code I tried in the past. I have 3 monitors, monitor 1 3240 x 2160 200% scale; monitor 2 3840 x 1200 100% scale; monitor 3 1900 x 1200 100%. The result I get from the code above are (scaling) 74.66, 103.35, 102.36. Why 75 and not 200? The physical width is reported as 1209.45 and the Bounds Width as 1296. This monitor appears to be different from others (Microsoft Surface Book 2). Changing the scaling to 250% returns 93? My guess would be the assumed DPI of 96 is not true for some monitors?Bree
@user3225503, You can get DPI for each monitor by using the code in this link: https://mcmap.net/q/658124/-how-to-get-dpi-scale-for-all-screensSiliculose

© 2022 - 2024 — McMap. All rights reserved.