Search code examples
c#dpiclass-librarymultiple-monitors

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


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?


Solution

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