1
votes

Comment obtenir le facteur de mise à l'échelle pour chaque moniteur, par exemple 1, 1,25, 1,5

Je sais que cela a déjà été demandé, mais j'ai essayé toutes les réponses que j'ai trouvées et aucune d'elles ne semble fonctionner pour moi. Les réponses semblent fonctionner sur un seul moniteur, ou nécessitent un handle de fenêtre ou se trouver dans une application WPF. J'ai une bibliothèque de classes C # sans interface utilisateur qui est appelée à partir d'un langage différent.

On m'a demandé de déterminer le facteur de mise à l'échelle, par exemple 1, 1,25, 1,5, etc. pour chaque moniteur connecté au PC actuel dans une bibliothèque de classes C #.

Je dois également fournir la résolution et la profondeur de couleur de chaque moniteur. Le registre contient le DpiValue , quel qu'il soit, dans Windows 10 sous

System.Windows.Forms.Screen.AllScreens

Cependant, je ne sais pas comment les mapper sur un Screen afin d'obtenir la résolution correspondante renvoyée dans

Computer\HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings

Quelqu'un a-t-il donc un moyen d'obtenir ces informations?


0 commentaires

3 Réponses :


0
votes

Je pense que vous pouvez obtenir un facteur de mise à l'échelle pour chaque moniteur comme celui-ci.

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

Et vous devez également ajouter ces codes à utiliser pour obtenir le DPI du moniteur (ce code provient de https://stackoverflow.com/a/29463627/12949439 , merci @Koopakiller ):

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


2 commentaires

Merci. Cela semble prometteur, mais j'obtiens un résultat similaire à un code que j'ai essayé dans le passé. J'ai 3 moniteurs, moniteur 1 3240 x 2160 échelle de 200%; moniteur 2 3840 x 1200 échelle de 100%; moniteur 3 1900 x 1200 100%. Le résultat que j'obtiens du code ci-dessus est (mise à l'échelle) 74,66, 103,35, 102,36. Pourquoi 75 et pas 200? La largeur physique est signalée comme 1209,45 et la largeur des limites comme 1296. Ce moniteur semble être différent des autres (Microsoft Surface Book 2). Changer l'échelle à 250% renvoie 93? Je suppose que le DPI supposé de 96 n'est pas vrai pour certains moniteurs?


@ user3225503, vous pouvez obtenir des DPI pour chaque moniteur en utilisant le code de ce lien: https://stackoverflow.com/a/29463627/12949439



0
votes

Je crois avoir enfin (après une longue période de recherche) trouvé une réponse qui fonctionne, cela fonctionne même sur mon écran Surface Book 2 à haute résolution. Je l'ai testé autant que possible, et jusqu'à présent, il a toujours renvoyé la valeur correcte.

Voici comment je l'ai fait, grâce à celui qui a posté les fragments de code dans le passé d'où je l'ai recueilli.

Vous avez d'abord besoin d'une structure pour appeler EnumDisplaySettings dans user32.dll

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

Ensuite, vous devez déclarer l'appel de fonction externe

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

Ensuite, vous devez l'utiliser pour calculer la mise à l'échelle de l'écran

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

J'espère que d'autres trouveront cela utile.


0 commentaires

0
votes

Malheureusement, la réponse de user3225503 semble ne pas fonctionner (plus?)

Mon scénario: WIN10 20H2, WPF-App avec prise en compte du dpi "PerMonitor", Framework 4.7.2, 2 moniteurs avec différentes résolutions et différentes échelles d'écran ("Scénario d'horreur"):

le membre dm.dmPelsWidth de la structure DEVMODE a toujours la résolution physique de mes moniteurs, donc la mise à l'échelle est toujours 1.0.

Tout ce que nous voulons, c'est restaurer notre programme et ses fenêtres comme nous l'avons laissé lors de la dernière session, n'est-ce pas? Cela semble incroyablement difficile, grâce à la SP!

Mais une approche différente semble fonctionner:

  1. Activez la détection du dpi par moniteur dans le fichier manifeste de votre application:

    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. Utilisez toujours les appels GetPlacement et SetPlacement win32-api pour stocker / restaurer les emplacements de fenêtre

  3. SetPlacement définira la mauvaise largeur / hauteur de la boîte de dialogue si la boîte de dialogue est sur un affichage secondaire et que chaque affichage a des échelles différentes. Nous avons donc besoin d'un nouveau facteur en fonction des facteurs de mise à l'échelle de chaque affichage pour corriger cela dans l'événement de chargement de la fenêtre:

code d'événement:

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. Il y a d'autres avantages dans l'exemple de code, par exemple la sérialisation de la structure WINDOWPLACEMENT. n'oubliez pas de créer un membre "Placement" dans les paramètres de votre application! Dites-moi si cela fonctionne pour vous:

Exemple de code:

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


0 commentaires