How can I get the DPI in WPF?

How can I get the DPI in WPF?

57274 次浏览

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

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

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

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.

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

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

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

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%

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