如何获取每个显示器的缩放比例,例如1,1.25,1.5。

7

我知道这个问题之前已经被问过,但是我尝试了所有我找到的答案,没有一个对我有效。这些答案似乎只在单个监视器上运行,或需要窗口句柄,或者必须在WPF应用程序中才能使用。我有一个没有UI的C#类库,从完全不同的语言中调用。

我被要求确定连接到当前PC的每个监视器的缩放因子,例如1、1.25、1.5等,在C#类库中提供分辨率和颜色深度信息。

我还需要为每个监视器提供分辨率和颜色深度。在Windows 10下,注册表保存了DpiValue(不知道是什么)。

Computer\HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings

然而,我不知道如何将它们映射到一个屏幕,以便返回匹配的分辨率。
System.Windows.Forms.Screen.AllScreens

那么有没有获取这些信息的方法?
4个回答

16

我相信我终于找到了一个有效的答案(经过长时间的搜索),它甚至可以在我的高DPI Surface Book 2屏幕上工作。我已经尽可能多地进行了测试,到目前为止它总是返回正确的值。

下面是我是如何做到的,感谢过去发布代码片段的人,让我从中收集了这些信息。

首先,您需要一个结构来调用user32.dll中的EnumDisplaySettings。

    [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;
    }

然后您需要声明外部函数调用

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

然后您需要使用它来计算屏幕缩放比例。

            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);
            }

希望这对其他人有用。

1
谢谢,它有效!奇怪的是没有人尝试过,点赞也很少。 - Nikita Zamalyutdinov
你不知道我为这个解决方案搜索了多久! - Christoph S.
程序可以正常运行,但我需要在“属性”->“兼容性”->“高 DPI 缩放覆盖”->“应用程序”中关闭此设置,否则我的应用程序会变得模糊不清。所以我又回到了起点,再次尝试使用清单和 app.config 的 DPI 感知设置 :-( - Harry
1
在Windows11下,对我来说它根本不起作用,始终为scalingFactor提供0或1。 - Daniel
对我来说一样。永远是1。 - Artem Kulikov
显示剩余3条评论

2

很遗憾,用户3225503的答案似乎不起作用了(还是说不再起作用了?)

我的情况:WIN10 20H2,WPF-App具有dpi感知“PerMonitor”,框架4.7.2,2个分辨率和不同屏幕缩放比例的显示器(“恐怖场景”):

DEVMODE结构的dm.dmPelsWidth成员始终具有我的监视器的物理分辨率,因此缩放始终为1.0。

我们想要的就是像上次会话一样恢复我们的程序及其窗口,对吧?这似乎非常困难,多亏了微软!

但是,不同的方法似乎有效:

  1. 在应用程序的清单文件中开启每个监视器的 DPI 意识:

    <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <!-- 下面两个标签的组合具有以下效果:
      1)大于等于 Windows 10 周年更新版本的每个监视器
      2)小于 Windows 10 周年更新版本的系统 -->
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> PerMonitor</dpiAwareness>
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    </windowsSettings>
    
  2. 始终使用 GetPlacement 和 SetPlacement Win32 API 调用来存储/恢复窗口位置。

  3. 如果对话框位于辅助显示器上,并且每个显示器具有不同的缩放比例,则 SetPlacement 将设置错误的对话框宽度/高度。因此,我们需要一个新的因子,该因子取决于每个显示器的缩放因子,在窗口的 Loading 事件中进行更正:

事件代码:

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. 代码示例中还有一些好东西,例如WINDOWPLACEMENT结构的序列化。不要忘记在应用程序设置中创建一个名为“Placement”的成员!如果这对您有用,请告诉我:

示例代码:

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;
      }
    }
  }
}

2

正如评论中提到的,所接受的答案在Windows 11上根本不起作用。经过所有的研究,我找到了一个在Windows 11上有效并显示每个显示器的正确比例的解决方案。关键思路包含以下几个步骤:

  1. 检查操作系统版本是否支持每个显示器的DPI,使用此结构体
  2. 如果不支持:尝试通过内置函数获取DPI:DeviceDpi
  3. 如果支持:
  4. 通过点(可以使用Screen.Bounds.Left+1)获取该显示器:MonitorFromPoint
  5. 使用GetDpiForMonitor获取此显示器的DPI。
  6. 最终的比例值为DPI / 100 * 96。

完整的脚本如下:

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,
    }
}

谢谢,看起来很有趣。很遗憾看到我的解决方案停止工作了。微软没有简化它!我还没有机会尝试这个,但是我有三个显示器连接到我的电脑上,我想找到它们的分辨率和缩放比例,但我不确定应该如何做?我没有使用System.Windows.Forms,而是使用一个类库,所以屏幕上没有任何图形控件。 - user3225503
谢谢,看起来很有趣。很遗憾看到我的解决方案停止工作了。微软没有让它变得简单!我还没有机会尝试这个,但是我有三个显示器连接到我的电脑上,我想找到它们的分辨率和缩放比例,但我不确定应该如何做到这一点?我没有使用System.Windows.Forms,而是使用一个类库,所以屏幕上没有任何图形控件。 - undefined
在这种情况下,你首先需要获取一个屏幕(例如使用 Screens),然后调用 'ScaleFactor(null, Screen.Bounds.X+1)'。 - Artem Kulikov
在这种情况下,你首先需要获取一个屏幕(例如使用 Screens),然后调用 'ScaleFactor(null, Screen.Bounds.X+1)'。 - undefined
DPIUtil.ScaleFactor(this, new Point(0, 0)) 给我了准确的数字。100=100%,250=250%。谢谢! - undefined

0
我认为你可以像这样获取每个显示器的缩放因子。
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);
    }
}

同时,您还需要添加以下代码以获取显示器 DPI(这些代码来自 https://dev59.com/810b5IYBdhLWcg3wO_E_#29463627,感谢 @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,
}

谢谢。看起来很有前途,但我得到了与我过去尝试的某些代码类似的结果。我有3个显示器,显示器1为3240 x 2160 200%比例;显示器2为3840 x 1200 100%比例;显示器3为1900 x 1200 100%比例。我从上面的代码中得到的结果是(缩放)74.66、103.35、102.36。为什么不是200而是75?物理宽度报告为1209.45,边界宽度为1296。这个显示器似乎与其他显示器不同(Microsoft Surface Book 2)。将缩放更改为250%返回93?我猜测96的假定DPI对于某些显示器并不正确? - user3225503
@user3225503,您可以使用此链接中的代码获取每个显示器的DPI:https://dev59.com/810b5IYBdhLWcg3wO_E_#29463627 - user12949439

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接