Share via


Entwickeln einer DPI pro Monitor-fähigen WPF-Anwendung

Wichtige APIs

Hinweis

Auf dieser Seite wird die WPF-Legacyentwicklung für Windows 8.1 behandelt. Wenn Sie WPF-Anwendungen für Windows 10 entwickeln, lesen Sie die neueste Dokumentation auf GitHub.

 

Windows 8.1 bietet Entwicklern neue Funktionen zum Erstellen von Desktopanwendungen, die pro Monitor DPI-fähig sind. Um diese Funktionalität nutzen zu können, muss eine DPI-fähige Anwendung pro Monitor folgendes ausführen:

  • Ändern der Fensterdimensionen, um eine physische Größe beizubehalten, die auf jeder Anzeige konsistent angezeigt wird
  • Neulayout und Erneutes Rendern von Grafiken für die neue Fenstergröße
  • Auswählen von Schriftarten, die entsprechend der DPI-Ebene skaliert werden
  • Auswählen und Laden von Bitmapressourcen, die auf die DPI-Ebene zugeschnitten sind

Um die Erstellung einer dpi-fähigen Anwendung pro Monitor zu vereinfachen, stellt Windows 8.1 die folgenden Microsoft Win32APIs bereit:

  • SetProcessDpiAwareness (oder DPI-Manifesteintrag) legt den Prozess auf eine angegebene DPI-Sensibilisierungsstufe fest, die dann bestimmt, wie Windows die Benutzeroberfläche skaliert. Dadurch wird SetProcessDPIAware abgelöst.
  • GetProcessDpiAwareness gibt die DPI-Sensibilisierungsstufe zurück. Dadurch wird IsProcessDPIAware abgelöst.
  • GetDpiForMonitor gibt den DPI-Wert für einen Monitor zurück.
  • Die WM_DPICHANGED Fensterbenachrichtigung wird an monitorspezifische DPI-fähige Anwendungen gesendet, wenn sich die Position eines Fensters so ändert, dass der größte Teil des Bereichs einen Monitor mit einem DPI-Wert überschneidet, der sich von der DPI unterscheidet, bevor sich die Position ändert oder wenn der Benutzer den Anzeigeschieberegler bewegt. Verwenden Sie diese Benachrichtigung, um eine Anwendung zu erstellen, die die Größe ändert und sich selbst neu rendert, wenn ein Benutzer sie auf eine andere Anzeige verschiebt.

Weitere Informationen zu verschiedenen dpi-Sensibilisierungsstufen, die für Desktopanwendungen in Windows 8.1 unterstützt werden, finden Sie im Thema Schreiben DPI-Aware Desktop- und Win32-Anwendungen.

DPI-Skalierung und WPF

Windows Presentation Foundation-Anwendungen (WPF) sind standardmäßig systeminterne DPI-fähig. Definitionen der verschiedenen DPI-Sensibilisierungsstufen finden Sie im Thema Schreiben von DPI-Aware Desktop- und Win32-Anwendungen. Das WPF-Grafiksystem verwendet geräteunabhängige Einheiten, um die Unabhängigkeit von der Auflösung und vom Gerät zu aktivieren. WPF skaliert jedes geräteunabhängige Pixel automatisch basierend auf der aktuellen System-DPI. Dadurch können WPF-Anwendungen automatisch skaliert werden, wenn der DPI-Wert des Monitors, auf dem sich das Fenster befindet, identisch ist. Da WPF-Anwendungen jedoch systeminterne DPI-fähig sind, wird die Anwendung vom Betriebssystem skaliert, wenn die Anwendung auf einen Monitor mit einem anderen DPI verschoben wird oder wenn der Schieberegler in der Systemsteuerung verwendet wird, um die DPI zu ändern. Die Skalierung im Betriebssystem kann dazu führen, dass WPF-Anwendungen verschwommen erscheinen, insbesondere wenn die Skalierung nicht integral ist. Um die Skalierung von WPF-Anwendungen zu vermeiden, müssen sie so aktualisiert werden, dass sie für jeden Monitor DPI-fähig sind.

Exemplarische Vorgehensweise für "Pro Monitor aware WPF"

Das WPF-Beispiel für pro Monitor ist eine WPF-Beispielanwendung , die aktualisiert wurde, um die DPI-fähig für jeden Monitor zu sein. Das Beispiel umfasst zwei Projekte:

  • NativeHelpers.vcxproj: Dies ist ein natives Hilfsprojekt, das die Kernfunktionalität implementiert, um eine WPF-Anwendung mit den oben genannten Win32APIs als DPI-fähig für jeden Monitor zu machen. Das Projekt enthält zwei Klassen:
    • PerMonDPIHelpers: Eine Klasse, die Hilfsfunktionen für DPI-bezogene Vorgänge bereitstellt, z. B. das Abrufen des aktuellen DPI-Werts des aktiven Monitors, festlegen, dass ein Prozess pro Monitor DPI-fähig ist usw.
    • PerMonitorDPIWindow: Eine von System.Windows.Window abgeleitete Basisklasse, die Funktionen implementiert, um ein WPF-Anwendungsfenster für jeden Monitor dpi-fähig zu machen. Passt Fenstergröße, Grafikrenderinggröße und Schriftgrad basierend auf dem DPI-Wert des Monitors und nicht auf der System-DPI an.
  • WPFApplication.csproj: WPF-Beispielanwendung, die perMonitorDPIWindow (PerMonitorDPIWindow) verwendet, und zeigt, wie das Anwendungsfenster und das Rendering die Größe des Fensters ändern, wenn das Fenster auf einen Monitor mit einem anderen DPI verschoben wird oder wenn der Schieberegler in der Anzeige-Systemsteuerung verwendet wird, um die DPI zu ändern.

Führen Sie zum Ausführen des Beispiels die folgenden Schritte aus:

  1. Herunterladen und Entpacken des WPF-Beispiels pro Monitor
  2. Starten Sie Microsoft Visual Studio, und wählen Sie Datei > öffnen > Projekt/Projektmappe aus.
  3. Navigieren Sie zu dem Verzeichnis, das das entzippte Beispiel enthält. Navigieren Sie zum Verzeichnis mit dem Namen für das Beispiel, und doppelklicken Sie auf die Projektmappendatei (SLN) von Visual Studio.
  4. Drücken Sie F7, oder verwenden Sie > Buildprojektmappe , um das Beispiel zu erstellen.
  5. Drücken Sie STRG+F5, oder verwenden Sie Debuggen > starten ohne Debuggen , um das Beispiel auszuführen.

Um die Auswirkungen der Änderung von DPI auf eine WPF-Anwendung zu sehen, die mit der Basisklasse im Beispiel auf monitorspezifische DPI-fähig aktualisiert wird, verschieben Sie das Anwendungsfenster auf und von Bildschirmen mit unterschiedlichen DPIs. Wenn das Fenster zwischen Monitoren verschoben wird, werden die Fenstergröße und die Benutzeroberflächenskala basierend auf dem DPI-Wert der Anzeige mithilfe des skalierbaren Grafiksystems von WPF aktualisiert, anstatt vom Betriebssystem skaliert zu werden. Die Benutzeroberfläche der Anwendung wird nativ gerendert und erscheint nicht verschwommen. Wenn Sie nicht über zwei Bildschirme mit unterschiedlichen DPI-Werten verfügen, ändern Sie den DPI-Wert, indem Sie den Schieberegler in der Anzeige-Systemsteuerung ändern. Wenn Sie den Schieberegler ändern und auf Übernehmen klicken, wird die Größe des Anwendungsfensters geändert, und die Skalierung der Benutzeroberfläche wird automatisch aktualisiert.

Aktualisieren einer vorhandenen WPF-Anwendung, um mit hilfe des Hilfsprojekts im WPF-Beispiel die dpi-fähig für jeden Monitor zu sein

Wenn Sie über eine vorhandene WPF-Anwendung verfügen und das DPI-Hilfsprojekt aus dem Beispiel nutzen möchten, um dpi-fähig zu machen, führen Sie die folgenden Schritte aus.

  1. Herunterladen und Entpacken des WPF-Beispiels pro Monitor

  2. Starten Sie Visual Studio, und wählen Sie Datei > öffnen > Projekt/Projektmappe aus.

  3. Navigieren Sie zu dem Verzeichnis, das eine vorhandene WPF-Anwendung enthält, und doppelklicken Sie auf die Visual Studio-Projektmappendatei (SLN).

  4. Klicken Sie mit der rechten Maustaste auf Projektmappe > Vorhandenes Projekt hinzufügen >ein Screenshot, der die Menüauswahl

  5. Navigieren Sie im Dateiauswahldialogfeld zu dem Verzeichnis, das das entzippte Beispiel enthält. Öffnen Sie das Verzeichnis mit dem Namen für das Beispiel, navigieren Sie zum Ordner "NativeHelpers", wählen Sie die Visual C++-Projektdatei "NativeHelpers.vcxproj" aus, und klicken Sie auf OK.

  6. Klicken Sie mit der rechten Maustaste auf das Projekt NativeHelpers, und wählen Sie Erstellen aus. Dadurch wird NativeHelpers.dll generiert, der als Verweis auf die WPF-Anwendung im nächsten Schritthinzugefügt wird, ein Screenshot, der die Auswahl des Buildmenüs veranschaulicht.

  7. Fügen Sie einen Verweis auf NativeHelpers.dll aus Ihrer WPF-Anwendung hinzu. Erweitern Sie Ihr WPF-Anwendungsprojekt, klicken Sie mit der rechten Maustaste auf Verweise , und klicken Sie auf Verweis hinzufügen...

  8. Erweitern Sie im resultierenden Dialog den Abschnitt Lösung . Wählen Sie unter Projekte die Option NativeHelpers aus, und klicken Sie auf OK, um das Dialogfeld .

  9. Erweitern Sie Ihr WPF-Anwendungsprojekt, erweitern Sie Eigenschaften, und öffnen Sie AssemblyInfo.cs. Nehmen Sie die folgenden Ergänzungen zu "AssemblyInfo.cs" vor.

    • Hinzufügen eines Verweises auf System.Windows.Media im Referenzabschnitt (mithilfe von System.Windows.Media;)
    • Hinzufügen des DisableDpiAwareness-Attributs ([assembly: DisableDpiAwareness])

    Screenshot zur Veranschaulichung der zusätzlichen Eigenschaften

  10. Erben der Standard WPF-Fensterklasse von der PerMonitorDPIWindow-Basisklasse

    • Aktualisieren Sie die CS-Datei des Standard WPF-Fensters, um von der PerMonitorDPIWindow-Basisklasse zu erben.

      • Fügen Sie im Verweisabschnitt einen Verweis auf NativeHelpers hinzu, indem Sie die Zeile hinzufügen. using NativeHelpers;
      • Erben der Standard-Fensterklasse von der PerMonitorDPIWindow-Klasse

      Screenshot zur Veranschaulichung der c++-Referenz

    • Aktualisieren der XAML-Datei des Standard WPF-Fensters, um von der PerMonitorDPIWindow-Basisklasse zu erben

      • Fügen Sie im Verweisabschnitt einen Verweis auf NativeHelpers hinzu, indem Sie die Zeile hinzufügen. xmlns:src="clr-namespace:NativeHelpers;assembly=NativeHelpers"
      • Erben der Standard-Fensterklasse von der PerMonitorDPIWindow-Klasse

      Ein Screenshot, der das Hinzufügen des XAML-Verweises veranschaulicht

  11. Drücken Sie F7, oder verwenden Sie > Buildprojektmappe , um das Beispiel zu erstellen.

  12. Drücken Sie STRG+F5, oder verwenden Sie Debuggen > starten ohne Debuggen , um das Beispiel auszuführen.

Die WPF-Beispielanwendung "Per Monitor Aware " veranschaulicht, wie eine WPF-Anwendung aktualisiert werden kann, um monitorspezifische DPI-fähig zu sein, indem auf die WM_DPICHANGED-Fensterbenachrichtigung reagiert wird. Als Reaktion auf die Fensterbenachrichtigung aktualisiert das Beispiel die von WPF verwendete Skalierungstransformation basierend auf dem aktuellen DPI-Wert des Monitors, auf dem sich das Fenster befindet. Die wParam der Fensterbenachrichtigung enthält den neuen DPI-Wert in wParam. Der lParam enthält ein Rechteck mit der Größe und Position des neuen vorgeschlagenen Fensters, das für den neuen DPI-Wert skaliert ist.

Hinweis:

Hinweis

Da in diesem Beispiel die Fenstergröße und die Skalierungstransformation des Stammknotens des WPF-Fensters überschrieben werden, kann der Anwendungsentwickler weitere Schritte ausführen, wenn:

  • Die Größe des Fensters wirkt sich auf andere Teile der Anwendung aus, z. B. dieses WPF-Fenster, das in einer anderen Anwendung gehostet wird.
  • Die WPF-Anwendung, die diese Klasse erweitert, legt eine andere Transformation für das Stammvisual fest. Das Beispiel kann eine andere Transformation überschreiben, die von der WPF-Anwendung selbst angewendet wird.

 

Übersicht über das Hilfsprojekt im WPF-Beispiel

Um eine vorhandene WPF-Anwendung pro Monitor DPI-fähig zu machen, bietet die NativeHelpers-Bibliothek folgende Funktionen:

  • Markiert die WPF-Anwendung als pro Ponitor DPI-fähig: Die WPF-Anwendung wird als monitorspezifische DPI-fähig gekennzeichnet, indem SetProcessDpiAwareness für den aktuellen Prozess aufgerufen wird. Wenn Sie die Anwendung als pro Monitor dpi-fähig markieren, wird sichergestellt, dass

    • Das Betriebssystem skaliert die Anwendung nicht, wenn die System-DPI nicht mit dem aktuellen DPI-Wert des Monitors übereinstimmt, auf dem sich das Anwendungsfenster befindet.
    • Die WM_DPICHANGED Meldung wird gesendet, wenn sich der DPI-Wert des Fensters ändert.
  • Passt die Fensterdimension an, layoutt und rendert Grafikinhalte neu und wählt Schriftarten basierend auf dem anfänglichen DPI-Wert des Monitors aus, auf dem sich das Fenster befindet: Nachdem die Anwendung als dpi-fähig pro Monitor gekennzeichnet ist, skaliert WPF weiterhin die Fenstergröße, die Grafiken und den Schriftgrad basierend auf dem System-DPI. Da der System-DPI-Wert beim App-Start nicht mit dem DPI-Wert des Monitors übereinstimmt, auf dem das Fenster gestartet wird, passt die Bibliothek diese Werte an, sobald das Fenster geladen wird. Die Basisklasse PerMonitorDPIWindow aktualisiert diese im OnLoaded() -Handler.

    Die Fenstergröße wird aktualisiert, indem die Eigenschaften Breite und Höhe des Fensters geändert werden. Layout und Größe werden aktualisiert, indem eine entsprechende Skalierungstransformation auf den Stammknoten des WPF-Fensters angewendet wird.

    void PerMonitorDPIWindow::OnLoaded(Object^ , RoutedEventArgs^ ) 
    {   
    if (m_perMonitorEnabled)
        {
        m_source = (HwndSource^) PresentationSource::FromVisual((Visual^) this);
        HwndSourceHook^ hook = gcnew HwndSourceHook(this, &PerMonitorDPIWindow::HandleMessages);
        m_source->AddHook(hook); 
    
        //Calculate the DPI used by WPF.                    
        m_wpfDPI = 96.0 *  m_source->CompositionTarget->TransformToDevice.M11; 
    
        //Get the Current DPI of the monitor of the window. 
        m_currentDPI = NativeHelpers::PerMonitorDPIHelper::GetDpiForWindow(m_source->Handle);
    
        //Calculate the scale factor used to modify window size, graphics and text
        m_scaleFactor = m_currentDPI / m_wpfDPI; 
    
        //Update Width and Height based on the on the current DPI of the monitor
        Width = Width * m_scaleFactor;
        Height = Height * m_scaleFactor;
    
        //Update graphics and text based on the current DPI of the monitor
    UpdateLayoutTransform(m_scaleFactor);
        }
    }
    
    void PerMonitorDPIWindow::UpdateLayoutTransform(double scaleFactor)
    {
    // Adjust the rendering graphics and text size by applying the scale transform to the top         
    level visual node of the Window     
    
    if (m_perMonitorEnabled) 
        {       
            auto child = GetVisualChild(0);
            if (m_scaleFactor != 1.0) 
           {
            ScaleTransform^ dpiScale = gcnew ScaleTransform(scaleFactor, scaleFactor);
            child->SetValue(Window::LayoutTransformProperty, dpiScale);
            }
            else 
            {
            child->SetValue(Window::LayoutTransformProperty, nullptr);
            }           
        }
    }
    
  • Antwortet auf WM_DPICHANGED Fensterbenachrichtigung: Aktualisieren Sie die Fenstergröße, die Grafiken und den Schriftgrad basierend auf dem dpi-Wert, der in der Fensterbenachrichtigung übergeben wurde. Die Basisklasse PerMonitorDPIWindow verarbeitet die Fensterbenachrichtigung in der HandleMessages()- Methode.

    Die Fenstergröße wird aktualisiert, indem SetWindowPos mithilfe der informationen, die in der lparam der Fenstermeldung übergeben werden, aufgerufen wird. Das Layout und die Grafikgröße werden aktualisiert, indem eine entsprechende Skalierungstransformation auf den Stammknoten des WPF-Fensters angewendet wird. Der Skalierungsfaktor wird mithilfe des DPI-Werts berechnet, der im Wparam der Fenstermeldung übergeben wird.

    IntPtr PerMonitorDPIWindow::HandleMessages(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, bool% )
    {
    double oldDpi;
    switch (msg)
        {
        case WM_DPICHANGED:
        LPRECT lprNewRect = (LPRECT)lParam.ToPointer();
        SetWindowPos(static_cast<HWND>(hwnd.ToPointer()), 0, lprNewRect->left, lprNewRect-
            >top, lprNewRect->right - lprNewRect->left, lprNewRect->bottom - lprNewRect->top, 
           SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE);
        oldDpi = m_currentDPI;
        m_currentDPI = static_cast<int>(LOWORD(wParam.ToPointer()));
        if (oldDpi != m_currentDPI) 
            {
            OnDPIChanged();
            }
        break;
        }
    return IntPtr::Zero;
    }
    
    void PerMonitorDPIWindow::OnDPIChanged() 
    {
    m_scaleFactor = m_currentDPI / m_wpfDPI;
    UpdateLayoutTransform(m_scaleFactor);
    DPIChanged(this, EventArgs::Empty);
    }
    
    void PerMonitorDPIWindow::UpdateLayoutTransform(double scaleFactor)
    {
    // Adjust the rendering graphics and text size by applying the scale transform to the top         
    level visual node of the Window     
    
    if (m_perMonitorEnabled) 
        {       
            auto child = GetVisualChild(0);
            if (m_scaleFactor != 1.0) 
           {
            ScaleTransform^ dpiScale = gcnew ScaleTransform(scaleFactor, scaleFactor);
            child->SetValue(Window::LayoutTransformProperty, dpiScale);
            }
            else 
            {
            child->SetValue(Window::LayoutTransformProperty, nullptr);
            }           
        }
    }
    

Behandeln von DPI-Änderungen für Ressourcen wie Bilder

Um Grafikinhalte zu aktualisieren, wendet die WPF-Beispielanwendung eine Skalierungstransformation auf den Stammknoten der WPF-Anwendung an. Dies funktioniert zwar gut für Inhalte, die nativ von WPF gerendert werden (Rechteck, Text usw.), aber dies impliziert, dass Bitmapressourcen wie Bilder von WPF skaliert werden.

Um verschwommene Bitmaps durch Skalierung zu vermeiden, kann der WPF-Anwendungsentwickler ein benutzerdefiniertes DPI-Bildsteuerelement schreiben, das basierend auf dem aktuellen DPI-Wert des Monitors, auf dem sich das Fenster befindet, ein anderes Medienobjekt auswählt. Das Bildsteuerelement kann sich auf das DPIChanged() -Ereignis verlassen, das für das WPF-Fenster ausgelöst wird, das von PerMonitorDPIWindow verwendet wird, wenn sich der DPI-Wert ändert.

Hinweis

Das Bildsteuerelement sollte auch das richtige Steuerelement während des App-Starts im Loaded() -WPF-Fenster-Ereignishandler auswählen.