78

How can I get the DPI in WPF?

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
tom greene
  • 5,361
  • 9
  • 39
  • 52
  • 1
    Why would you need to do it in WPF, of all things, and what are you going to do with the value once you get it? WPF has no way to specify any coordinates in device-dependent pixels. It may seem that its sizes are in pixels, but those are "virtual pixels" - the size of a pixel as it is at 96 DPI. It will grow or shrink if you change the system DPI, so a "one-pixel thick" line drawn using WPF may not physically be one-pixel thick. – Pavel Minaev Dec 17 '09 at 01:20
  • 1
    Because I want to round pixels – tom greene Dec 17 '09 at 01:43
  • 1
    If you want pixel-precise placement at physical pixel boundaries, you're much better off not using WPF in the first place. It's not the scenario for which it is designed, and there are absolutely no guarantees with respect to how WPF coordinates may be rounded etc. If you just want the vertices to snap to nearest physical pixels, use `UseLayoutRounding` property. If you want to draw a line that is exactly 10 physical pixels long, then forget about WPF. – Pavel Minaev Dec 17 '09 at 01:51
  • 2
    Does "SnapToDevicePixels" not work for you? – Ana Betts Dec 17 '09 at 01:52
  • 2
    SnapToDevicePixels does not work very well. In fact, Microsoft introduced UseLayoutRounding for this reason. But UseLayoutRounding is "all or nothing". You can't round some coordinate but not others. – tom greene Dec 17 '09 at 18:06
  • 2
    @PavelMinaev A frequently used scenario is choosing the cursor file with appropriate size to use in WPF. WPF does not currently support multi-DPI cursor files, so you'll need to load different cursor files based on screen DPI. The support is going to be added in .NET 4.6. – SepehrM Mar 21 '15 at 16:21

11 Answers11

83

https://learn.microsoft.com/en-us/archive/blogs/jaimer/getting-system-dpi-in-wpf-app seems to work

PresentationSource source = PresentationSource.FromVisual(this);

double dpiX, dpiY;
if (source != null) {
    dpiX = 96.0 * source.CompositionTarget.TransformToDevice.M11;
    dpiY = 96.0 * source.CompositionTarget.TransformToDevice.M22;
}
timelmer
  • 135
  • 1
  • 10
Ana Betts
  • 73,868
  • 16
  • 141
  • 209
53
var dpiXProperty = typeof(SystemParameters).GetProperty("DpiX", BindingFlags.NonPublic | BindingFlags.Static);
var dpiYProperty = typeof(SystemParameters).GetProperty("Dpi", BindingFlags.NonPublic | BindingFlags.Static);

var dpiX = (int)dpiXProperty.GetValue(null, null);
var dpiY = (int)dpiYProperty.GetValue(null, null);
user1681882
  • 531
  • 4
  • 2
  • 2
    This Method works even when you don't have a reference to a control but it does use reflection so it has it's pros and cons but for my situation this one was better since I didn't have access to a control. – Farrah Stark Aug 01 '13 at 18:47
  • 3
    This method has the advantage that it works before the Loaded event of a Window. PresentationSource.FromVisual(myWindow) returns null until then. – Brian Rak Oct 16 '13 at 22:18
  • Works like a charm. I like that there are no assumptions in this approach, unlike other versions with 96 dpi. – B.K. Aug 20 '15 at 02:07
  • 2
    This returns the System DPI, which is the DPI of the primary display, and may or may not be what you want. If you are looking to translate WPF coordinates to screen coordinates, you *must* reference a Visual, because since Windows 8.1, the DPI of a window can be different depending on which monitor it's on, if those monitors have different DPI's. – caesay Dec 06 '20 at 16:19
47

With .NET 4.6.2 Preview and higher, you can call VisualTreeHelper.GetDpi(Visual visual). It returns a DpiScale structure, which tells you the DPI at which the given Visual will be or has been rendered.

Palec
  • 12,743
  • 8
  • 69
  • 138
rohit21agrawal
  • 1,018
  • 10
  • 15
  • I'm not able to call this function, it appears as undefined. Do you know why? I am doing this: `VisualTreeHelper.GetDpi()` – xandermonkey Mar 29 '17 at 19:23
  • 3
    @AlexRosenfeld make sure you are using the namespace System.Windows.Media, and your target framework version is 4.6.2 atleast. You also need to pass in an object of type Visual to this api. – rohit21agrawal Mar 30 '17 at 19:37
8

I have updated my answer from 2015. Here is some utility code that uses the latest DPI functions from Windows 10 (specifically GetDpiForWindow function which is the only method that supports the DPI_AWARENESS of the window/application/process, etc.) but falls back to older ones (dpi per monitor, and desktop dpi) so it should still work with Windows 7.

It has not dependency on WPF nor Winforms, only on Windows itself.

// note this class considers dpix = dpiy
public static class DpiUtilities
{
    // you should always use this one and it will fallback if necessary
    // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdpiforwindow
    public static int GetDpiForWindow(IntPtr hwnd)
    {
        var h = LoadLibrary("user32.dll");
        var ptr = GetProcAddress(h, "GetDpiForWindow"); // Windows 10 1607
        if (ptr == IntPtr.Zero)
            return GetDpiForNearestMonitor(hwnd);

        return Marshal.GetDelegateForFunctionPointer<GetDpiForWindowFn>(ptr)(hwnd);
    }

    public static int GetDpiForNearestMonitor(IntPtr hwnd) => GetDpiForMonitor(GetNearestMonitorFromWindow(hwnd));
    public static int GetDpiForNearestMonitor(int x, int y) => GetDpiForMonitor(GetNearestMonitorFromPoint(x, y));
    public static int GetDpiForMonitor(IntPtr monitor, MonitorDpiType type = MonitorDpiType.Effective)
    {
        var h = LoadLibrary("shcore.dll");
        var ptr = GetProcAddress(h, "GetDpiForMonitor"); // Windows 8.1
        if (ptr == IntPtr.Zero)
            return GetDpiForDesktop();

        int hr = Marshal.GetDelegateForFunctionPointer<GetDpiForMonitorFn>(ptr)(monitor, type, out int x, out int y);
        if (hr < 0)
            return GetDpiForDesktop();

        return x;
    }

    public static int GetDpiForDesktop()
    {
        int hr = D2D1CreateFactory(D2D1_FACTORY_TYPE.D2D1_FACTORY_TYPE_SINGLE_THREADED, typeof(ID2D1Factory).GUID, IntPtr.Zero, out ID2D1Factory factory);
        if (hr < 0)
            return 96; // we really hit the ground, don't know what to do next!

        factory.GetDesktopDpi(out float x, out float y); // Windows 7
        Marshal.ReleaseComObject(factory);
        return (int)x;
    }

    public static IntPtr GetDesktopMonitor() => GetNearestMonitorFromWindow(GetDesktopWindow());
    public static IntPtr GetShellMonitor() => GetNearestMonitorFromWindow(GetShellWindow());
    public static IntPtr GetNearestMonitorFromWindow(IntPtr hwnd) => MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
    public static IntPtr GetNearestMonitorFromPoint(int x, int y) => MonitorFromPoint(new POINT { x = x, y = y }, MONITOR_DEFAULTTONEAREST);

    private delegate int GetDpiForWindowFn(IntPtr hwnd);
    private delegate int GetDpiForMonitorFn(IntPtr hmonitor, MonitorDpiType dpiType, out int dpiX, out int dpiY);

    private const int MONITOR_DEFAULTTONEAREST = 2;

    [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr LoadLibrary(string lpLibFileName);

    [DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true)]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

    [DllImport("user32")]
    private static extern IntPtr MonitorFromPoint(POINT pt, int flags);

    [DllImport("user32")]
    private static extern IntPtr MonitorFromWindow(IntPtr hwnd, int flags);

    [DllImport("user32")]
    private static extern IntPtr GetDesktopWindow();

    [DllImport("user32")]
    private static extern IntPtr GetShellWindow();

    [StructLayout(LayoutKind.Sequential)]
    private partial struct POINT
    {
        public int x;
        public int y;
    }

    [DllImport("d2d1")]
    private static extern int D2D1CreateFactory(D2D1_FACTORY_TYPE factoryType, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, IntPtr pFactoryOptions, out ID2D1Factory ppIFactory);

    private enum D2D1_FACTORY_TYPE
    {
        D2D1_FACTORY_TYPE_SINGLE_THREADED = 0,
        D2D1_FACTORY_TYPE_MULTI_THREADED = 1,
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("06152247-6f50-465a-9245-118bfd3b6007")]
    private interface ID2D1Factory
    {
        int ReloadSystemMetrics();

        [PreserveSig]
        void GetDesktopDpi(out float dpiX, out float dpiY);

        // the rest is not implemented as we don't need it
    }
}

public enum MonitorDpiType
{
    Effective = 0,
    Angular = 1,
    Raw = 2,
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
7

The only way I found to get the "real" monitor dpi is the following. All other mentioned techniques just say 96 which is not correct for the most monitors.

 public class ScreenInformations
{
    public static uint RawDpi { get; private set; }

    static ScreenInformations()
    {
        uint dpiX;
        uint dpiY;
        GetDpi(DpiType.RAW, out dpiX, out dpiY);
        RawDpi = dpiX;
    }

    /// <summary>
    /// Returns the scaling of the given screen.
    /// </summary>
    /// <param name="dpiType">The type of dpi that should be given back..</param>
    /// <param name="dpiX">Gives the horizontal scaling back (in dpi).</param>
    /// <param name="dpiY">Gives the vertical scaling back (in dpi).</param>
    private static void GetDpi(DpiType dpiType, out uint dpiX, out uint dpiY)
    {
        var point = new System.Drawing.Point(1, 1);
        var hmonitor = MonitorFromPoint(point, _MONITOR_DEFAULTTONEAREST);

        switch (GetDpiForMonitor(hmonitor, dpiType, out dpiX, out dpiY).ToInt32())
        {
            case _S_OK: return;
            case _E_INVALIDARG:
                throw new ArgumentException("Unknown error. See https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx for more information.");
            default:
                throw new COMException("Unknown error. See https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510.aspx for more information.");
        }
    }

    //https://msdn.microsoft.com/en-us/library/windows/desktop/dd145062.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.aspx
    [DllImport("Shcore.dll")]
    private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);

    const int _S_OK = 0;
    const int _MONITOR_DEFAULTTONEAREST = 2;
    const int _E_INVALIDARG = -2147024809;
}

/// <summary>
/// Represents the different types of scaling.
/// </summary>
/// <seealso cref="https://msdn.microsoft.com/en-us/library/windows/desktop/dn280511.aspx"/>
public enum DpiType
{
    EFFECTIVE = 0,
    ANGULAR = 1,
    RAW = 2,
}
Andreas
  • 3,843
  • 3
  • 40
  • 53
  • 3
    [DllImport("Shcore.dll")] - means works only for Windows 8 and above – EpiGen Aug 17 '15 at 08:11
  • You didn't paste your using statement. What class is DpiType containedin? – rollsch Aug 10 '17 at 01:37
  • This works but I got different X,Y Dpi sizes (probably because I'm using a macbookpro with retina on a virtual machine) but the line `RawDpi = dpiX;` I changed to `RawDpi = Math.Max(dpiX,dpiY);` so RawDpi is the larger of the two – tkefauver Jul 05 '20 at 00:47
4

This is how I managed to get a "scale factor" in WPF. My laptop's resolution is 1920x1440.

int resHeight = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height;  // 1440
int actualHeight = SystemParameters.PrimaryScreenHeight;  // 960
double ratio = actualHeight / resHeight;
double dpi = resHeigh / actualHeight;  // 1.5 which is true because my settings says my scale is 150%
Elijah
  • 1,814
  • 21
  • 27
  • 1
    Unfortunately SystemParameters only supplies information on the primary screen. But if one is only intrested in that, this is the easiest way to do it afaik. – Blightbuster Nov 19 '20 at 15:55
2

Use GetDeviceCaps function:

    static void Main(string[] args)
    {
        // 1.25 = 125%
        var dpi = GetDpi();
    }

    [DllImport("user32.dll")]
    public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

    [DllImport("user32.dll")]
    public static extern IntPtr GetDC(IntPtr hwnd);

    [DllImport("gdi32.dll")]
    static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

    private static float GetDpi()
    {
        IntPtr desktopWnd = IntPtr.Zero;
        IntPtr dc = GetDC(desktopWnd);
        var dpi = 100f;
        const int LOGPIXELSX = 88;
        try
        {
            dpi = GetDeviceCaps(dc, LOGPIXELSX);
        }
        finally
        {
            ReleaseDC(desktopWnd, dc);
        }
        return dpi / 96f;
    }
codeDom
  • 1,623
  • 18
  • 54
1

You can try using ManagementClass:

public static string GetDPI()
        {
            using (ManagementClass mc = new ManagementClass("Win32_DesktopMonitor"))
            {
                using (ManagementObjectCollection moc = mc.GetInstances())
                {

                    int PixelsPerXLogicalInch = 0; // dpi for x
                    int PixelsPerYLogicalInch = 0; // dpi for y

                    foreach (ManagementObject each in moc)
                    {
                        PixelsPerXLogicalInch = int.Parse((each.Properties["PixelsPerXLogicalInch"].Value.ToString()));
                        PixelsPerYLogicalInch = int.Parse((each.Properties["PixelsPerYLogicalInch"].Value.ToString()));
                    }
                    return PixelsPerXLogicalInch + "," + PixelsPerYLogicalInch;
                }
            }
        }
Alpaca
  • 11
  • 2
0

There are https://blogs.windows.com/buildingapps/2017/01/25/calling-windows-10-apis-desktop-application/#FJtMAIFjbtXiLQAp.97

January 25, 2017 3:54 pm

"Calling Windows 10 APIs From a Desktop Application" and

https://learn.microsoft.com/en-us/uwp/api/windows.devices.display.displaymonitor

"Display​Monitor Class"

Namespace: Windows.Devices.Display Assemblies:Windows.Devices.Display.dll, Windows.dll

Provides information about a display monitor device connected to the system.

These data include commonly used information from the monitor's Extended Display Identification Data (EDID, which is an industry-standard display descriptor block that nearly all monitors use to provide descriptions of supported modes and general device information) and DisplayID (which is a newer industry standard that provides a superset of EDID).

Raw​DpiX
Gets the physical horizontal DPI of the monitor (based on the monitor’s native resolution and physical size).

Raw​DpiY
Gets the physical vertical DPI of the monitor (based on the monitor’s native resolution and physical size).

0

Basic monitor info in Windows from 2006

https://learn.microsoft.com/en-us/windows/desktop/wmicoreprov/msmonitorclass

MSMonitorClass class

WmiMonitorRawEEdidV1Block class

WmiMonitorBasicDisplayParams class

MaxHorizontalImageSize ( EDID byte 21 )

MaxVerticalImageSize ( EDID byte 22 )

( Sizes in EDID are in centimeters above and in millimeters in EDID Detailed Timing Descriptor

12 Horizontal image size, mm, 8 lsbits (0–4095 mm, 161 in)
13 Vertical image size, mm, 8 lsbits (0–4095 mm, 161 in)
14 Bits 7–4 Horizontal image size, mm, 4 msbits
Bits 3–0 Vertical image size, mm, 4 msbits

)

and

https://social.msdn.microsoft.com/Forums/vstudio/en-US/e7bb9384-b343-4543-ac0f-c98b88a7196f/wpf-wmi-just-get-an-empty-string

0

WPF can now natively provide DPI information.

You can obtain DPI information of a certain visual using VisualTreeHelper.GetDpi(Visual).

For more information on DPI scaling in Desktop applications see this article from MSDN High DPI Desktop Application Development on Windows

ironsight
  • 1
  • 3