如何获得每个监视器的比例因子,例如1, 1.25, 1.5

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

我知道以前有人问过这个问题,但我已经尝试了我找到的所有答案,其中 none 似乎对我有用。答案似乎在单个监视器上工作,或者需要 window 句柄,或者在 WPF 应用程序中。我有一个没有 UI 的 C# class 库,它是从不同的语言一起调用的。

我被要求确定比例因子,例如1、1.25、1.5 等,用于连接到 C# class 库中当前 PC 的每个显示器。

我还需要提供每台显示器的分辨率和色深。注册表确实保存 DpiValue,无论如何,在 Windows 10 下

Computer\HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings

但是我不知道如何将它们映射到 Screen 以获得

中返回的匹配分辨率
System.Windows.Forms.Screen.AllScreens

那么有没有人有办法获得这些信息?

我认为您可以像这样获得每个显示器的比例因子。

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(这些代码来自 , 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,
}

我相信我终于(经过长时间的搜索)找到了一个可行的答案,它甚至可以在我的高 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);
            }

希望其他人觉得这有用。

不幸的是,user3225503 的答案似乎不起作用(不再?)

我的场景:WIN10 20H2,具有 dpi 感知“PerMonitor”的 WPF 应用程序,Framework 4.7.2,2 个具有不同分辨率和不同屏幕缩放比例的显示器(“恐怖场景”):

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

我们想要的只是恢复我们的程序及其 windows 就像我们在上次会话中留下的那样,对吗?多亏了 MS,这似乎非常困难!

但另一种方法似乎有效:

  1. 在应用程序的清单文件中打开每个显示器的 dpi 感知:

    <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. 始终使用 GetPlacement 和 SetPlacement win32-api 调用 storing/restoring window 个展示位置

  3. SetPlacement 将设置错误的对话框 width/height 如果对话框在辅助显示器上并且每个显示器具有不同的缩放比例。因此,我们需要一个新的因子,取决于每个显示器的缩放因子,以在 window:

    的加载事件中更正此问题

事件代码:

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