8

We can get resolution and stuff for our screen is Screen class. So, we should use Screen.WorkingArea.Width and Screen.WorkingArea.Height, if we want to place something in center of screen.

But, in Windows 8.1 (not sure about other OSs) there is possibility to scale items on screen, if they seem too small. You can check it, by calling context menu on desktop, select "Screen Resolution", and then select "Make text and other items larger or smaller".

And the problem is, that, it seems, it does actually increase screen resolution! So, Screen.WorkingArea.Width and Screen.WorkingArea.Height will give you scaled value, and point Screen.WorkingArea.Width/2 Screen.WorkingArea.Height/2 will no longer in center of actual screen (for 200% scaling this point will be on lower right corner of screen). That might screw a lot of placement of UI items.

So, is it possible to get value of scaling? I can't find any class containing this info.

lentinant
  • 792
  • 1
  • 10
  • 36
  • [Does this help you?](https://msdn.microsoft.com/en-US/library/windows/apps/windows.graphics.display.displayproperties.resolutionscale.aspx) – kevintjuh93 Sep 16 '15 at 11:36
  • It seems like right thing, but now i need to figure out, how to use it with my code. I've found [this arcticle](http://www.codeproject.com/Articles/1285/Calling-API-functions-using-C) about using API methods, but I don't know where exactly this property is. – lentinant Sep 16 '15 at 11:59
  • What kind of application is it? – kevintjuh93 Sep 16 '15 at 12:00
  • WPF application. Is there any specific reference to use Windows API? – lentinant Sep 16 '15 at 12:17
  • 1
    http://stackoverflow.com/questions/2236173/screen-resolution-problem-in-wpf What about this? – kevintjuh93 Sep 16 '15 at 12:21
  • It is just what i need (it also specifies the problem), but solution they are using involves using existing control (which is already rendered), and i need to calculate it before splash screen is shown (because it is actually first item shown). I need to get DPI value somehow before any UI actually shows up. – lentinant Sep 16 '15 at 12:54
  • 1
    Well, it seems, I can always get DPI of screen from registry, Path is "HKEY_CURRENT_USER\Control Panel\Desktop", key-value is "LogPixels". – lentinant Sep 16 '15 at 13:10

4 Answers4

8

Most methods to retrieve DPI depends on existing controls, which may be inconvenient sometimes. But DPI can be always retrieved from registry. In C#:

using Microsoft.Win32;
//...
var currentDPI = (int)Registry.GetValue("HKEY_CURRENT_USER\\Control Panel\\Desktop", "LogPixels", 96);

Scale will be

var scale = 96/(float)currentDPI;
lentinant
  • 792
  • 1
  • 10
  • 36
  • 1
    This registry setting doesn't exist for my Windows 10 machine. (It does exist for my Windows 7 machine.) – RenniePet Nov 24 '18 at 14:43
  • @RenniePet: You probably found it by now but here it is just in case: ```int DPI = Int32.Parse((string)Registry.GetValue( @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ThemeManager", "LastLoadedDPI", "96"));``` – miran80 Feb 04 '20 at 21:16
  • This registry not work on my WPF app. – Adison Nov 25 '22 at 08:54
  • @Adison you can find updated registry in a comment just before yours. – lentinant Nov 26 '22 at 17:06
  • If you change the scaling factor from Windows OS and don't restart OS nor log out from session, the registry values specified here LogPixels and LastLoadedDPI remains always the same. At least in Windows 11, so it does not work. – Willy Mar 01 '23 at 18:40
1

As it was mentioned in the comments, the accepted answer is based on the registry and didn't work in Windows 11. 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:

using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows;
using System.Windows.Forms;

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,
    }
}
Denis G. Labrecque
  • 1,023
  • 13
  • 29
Artem Kulikov
  • 2,250
  • 19
  • 32
0
 var currentDPI = (int)Microsoft.Win32.Registry.GetValue("HKEY_CURRENT_USER\\Control Panel\\Desktop\\WindowMetrics", "AppliedDPI", 0);
 SetClientSizeCore((int)((float)Width * (float)currentDPI/96.0f), (int)((float)Height * (float)currentDPI/96.0f));
  • 3
    Answers are more useful when they include an explanation -- especially as they may be found by a user working in a different language. Please edit the answer and provide some context for this code. – claytond Jan 20 '20 at 21:17
  • This registry not work on my WPF app. – Adison Nov 25 '22 at 08:54
0

Here is a working method of getting DPI information on Windows 10:

int DPI = Int32.Parse((string)Registry.GetValue(
   @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ThemeManager",
   "LastLoadedDPI", "96"));
miran80
  • 945
  • 7
  • 22