Wie kann ich die DPI in WPF erhalten?

69

Wie kann ich die DPI in WPF erhalten?

Tom Greene
quelle
1
Warum sollten Sie es ausgerechnet in WPF tun und was werden Sie mit dem Wert tun, wenn Sie ihn erhalten? WPF kann keine Koordinaten in geräteabhängigen Pixeln angeben. Es mag scheinen, dass seine Größen in Pixel angegeben sind, aber dies sind "virtuelle Pixel" - die Größe eines Pixels wie bei 96 DPI. Es wächst oder schrumpft, wenn Sie die DPI des Systems ändern, sodass eine mit WPF gezeichnete "ein Pixel dicke" Linie möglicherweise nicht ein Pixel dick ist.
Pavel Minaev
1
Weil ich Pixel runden möchte
Tom Greene
2
Funktioniert "SnapToDevicePixels" bei Ihnen nicht?
Ana Betts
2
SnapToDevicePixels funktioniert nicht sehr gut. Aus diesem Grund hat Microsoft UseLayoutRounding eingeführt. Aber UseLayoutRounding ist "alles oder nichts". Sie können einige Koordinaten nicht runden, andere jedoch nicht.
Tom Greene
2
@PavelMinaev Ein häufig verwendetes Szenario ist die Auswahl der Cursor-Datei mit der entsprechenden Größe für die Verwendung in WPF. WPF unterstützt derzeit keine Multi-DPI-Cursor-Dateien. Daher müssen Sie verschiedene Cursor-Dateien basierend auf der Bildschirm-DPI laden. Die Unterstützung wird in .NET 4.6 hinzugefügt.
September

Antworten:

77

https://docs.microsoft.com/en-us/archive/blogs/jaimer/getting-system-dpi-in-wpf-app scheint zu funktionieren

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;
}
Ana Betts
quelle
3
Beachten Sie jedoch, dass WPF-Einheiten keine Pixel sind, sondern geräteunabhängig bei 96DPI "Pixel-Einheiten". Also wirklich, was Sie wollen, ist der Skalierungsfaktor zwischen 96 DPI und dem aktuellen DPI (so wie 1,5 für 144 DPI)
Ana Betts
Also das ist es nicht, was ich dann brauche :( Wie bekomme ich den Skalierungsfaktor?
Tom Greene
Soll ich GetDeviceCaps (.., LOGPIXELSX) verwenden?
Tom Greene
2
@ Tom ist es nur [dpiX, dpiY] / 96.0
Ana Betts
1
Ihr Code hat meine WritableBitmap korrigiert, sodass das gewünschte Raster jetzt ordnungsgemäß angezeigt wird. Danke :-)
Endrju
50
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
quelle
2
Diese Methode funktioniert auch dann, wenn Sie keinen Verweis auf ein Steuerelement haben, aber Reflexion verwendet, sodass sie Vor- und Nachteile hat. Für meine Situation war diese Methode jedoch besser, da ich keinen Zugriff auf ein Steuerelement hatte.
Paul Stegler
3
Diese Methode hat den Vorteil, dass sie vor dem Loaded-Ereignis eines Fensters funktioniert. PresentationSource.FromVisual (myWindow) gibt bis dahin null zurück.
Brian Rak
Klappt wunderbar. Ich finde es gut, dass dieser Ansatz im Gegensatz zu anderen Versionen mit 96 dpi keine Annahmen enthält.
BK
Dies gibt die System-DPI zurück, die die DPI der primären Anzeige ist und möglicherweise nicht Ihren Wünschen entspricht. Wenn Sie WPF-Koordinaten in Bildschirmkoordinaten übersetzen möchten, müssen Sie auf ein Visual verweisen, da die Windows-DPI eines Fensters seit Windows 8.1 je nach eingeschaltetem Monitor unterschiedlich sein kann, wenn diese Monitore unterschiedliche DPIs haben.
Caesay
33

Mit .NET 4.6.2 Preview und höher können Sie anrufen VisualTreeHelper.GetDpi(Visual visual). Es gibt eine DpiScaleStruktur zurück, die Ihnen die DPI angibt, bei der die angegebene Visualgerendert wird oder wurde.

rohit21agrawal
quelle
Ich kann diese Funktion nicht aufrufen, sie wird als undefiniert angezeigt. Weißt du, warum? Ich mache das:VisualTreeHelper.GetDpi()
Xandermonkey
2
@AlexRosenfeld Stellen Sie sicher, dass Sie den Namespace System.Windows.Media verwenden und Ihre Ziel-Framework-Version mindestens 4.6.2 ist. Sie müssen auch ein Objekt vom Typ Visual an diese API übergeben.
Rohit21agrawal
6

Der einzige Weg, den ich gefunden habe, um die "echte" Monitor-dpi zu erhalten, ist der folgende. Alle anderen genannten Techniken sagen nur 96, was für die meisten Monitore nicht korrekt ist.

 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
quelle
3
[DllImport ("Shcore.dll")] - bedeutet, funktioniert nur für Windows 8 und höher
EpiGen
Sie haben Ihre using-Anweisung nicht eingefügt. In welcher Klasse ist DpiType enthalten?
rollt
Dies funktioniert, aber ich habe unterschiedliche X-, Y-Dpi-Größen (wahrscheinlich, weil ich ein Macbookpro mit Retina auf einer virtuellen Maschine verwende), aber die Zeile, in die RawDpi = dpiX;ich geändert habe RawDpi = Math.Max(dpiX,dpiY);, ist RawDpi die größere der beiden
0tombo0
5

Ich habe meine Antwort von 2015 aktualisiert. Hier ist ein Dienstprogrammcode, der die neuesten DPI-Funktionen von Windows 10 verwendet (insbesondere die GetDpiForWindow-Funktion die die einzige Methode ist, die die DPI_AWARENESS des Fensters / der Anwendung / des Prozesses usw. unterstützt), auf die jedoch zurückgegriffen wird ältere (dpi pro Monitor und Desktop-dpi), daher sollte es weiterhin mit Windows 7 funktionieren.

Es ist weder von WPF noch von Winforms abhängig, nur von Windows selbst.

// note this class considers dpix = dpiy
public static class DpiUtilities
{
    // you should always use this one and it will fallback if necessary
    // https://docs.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
quelle
3

So habe ich es geschafft, einen "Skalierungsfaktor" in WPF zu erhalten. Die Auflösung meines Laptops beträgt 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%
Eli
quelle
Leider liefert SystemParameters nur Informationen auf dem Primärbildschirm. Aber wenn man nur daran interessiert ist, ist dies der einfachste Weg, es afaik zu tun.
Blightbuster
3

Verwendung GetDeviceCapsFunktion:

    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;
    }
google dev
quelle
1
Gute Antwort, aber achten Sie darauf, dass es unterschiedliche Aufrufe für die X- und Y-Achse gibt.
Astef
0

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

25. Januar 2017 15:54 Uhr

"Aufrufen von Windows 10-APIs von einer Desktop-Anwendung" und

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

"Display Monitor Class"

Namespace: Windows.Devices.Display-Baugruppen: Windows.Devices.Display.dll, Windows.dll

Enthält Informationen zu einem an das System angeschlossenen Anzeigemonitor.

Diese Daten umfassen häufig verwendete Informationen aus den EDID (Extended Display Identification Data) des Monitors, einem branchenüblichen Anzeigedeskriptorblock, mit dem fast alle Monitore Beschreibungen der unterstützten Modi und allgemeinen Geräteinformationen bereitstellen, und der DisplayID (einem neueren Industriestandard) das liefert eine Obermenge von EDID).

Raw DpiX
Ruft die physische horizontale DPI des Monitors ab (basierend auf der nativen Auflösung und der physischen Größe des Monitors).

Raw DpiY
Ruft die physische vertikale DPI des Monitors ab (basierend auf der nativen Auflösung und der physischen Größe des Monitors).

Alexander Alexander
quelle
0

Grundlegende Monitorinformationen in Windows von 2006

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

MSMonitorClass-Klasse

WmiMonitorRawEEdidV1Block-Klasse

WmiMonitorBasicDisplayParams-Klasse

MaxHorizontalImageSize ( EDID byte 21 )

MaxVerticalImageSize ( EDID byte 22 )

(Die EDID-Größen sind im EDID Detailed Timing Descriptor in Zentimetern über und in Millimetern angegeben

12 Horizontale Bildgröße, mm, 8 lsbits (0–4095 mm, 161 Zoll)
13 Vertikale Bildgröße, mm, 8 lsbits (0–4095 mm, 161 Zoll)
14 Bits 7–4 Horizontale Bildgröße, mm, 4 msbits
Bits 3–0 Vertikale Bildgröße, mm, 4 msbits

)

und

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

Alexander Alexander
quelle