High-DPI-Unterstützung in WPF — Warum es trotz „eigentlich DPI-bewusst“ immer noch verschwimmt und durchblutet, und wie Sie es beheben
· Go Komura · WPF, High DPI, Windows, .NET, C#, .NET Framework, XAML, UI, Technische Beratung
„WPF sollte doch überhaupt keine DPI-Arbeit brauchen, oder?“ ist eine Frage, die wir in High-DPI-Beratungen häufig hören. Sie ist halb richtig und halb falsch. Die Symptome, die tatsächlich an uns herangetragen werden, sehen so aus: „Auf dem Laptop allein sieht es gut aus, doch der gesamte Bildschirm verschwimmt, sobald ich ihn an den externen Monitor im Besprechungsraum anschließe.“ „Der Text ist scharf, aber nur die Symbole in der Symbolleiste wirken unscharf.“ „Die Rasterlinien in einer Liste werden je nach Position dicker oder verschwinden.“ „Nur das alte, in den Bildschirm eingebettete Berichtssteuerelement wird kleiner gerendert als alles andere.“ Jeder dieser Fälle tritt tatsächlich in echten WPF-Apps auf, die „eigentlich DPI-bewusst“ sind.
Der gestrige Artikel „High-DPI-Unterstützung in WinForms“ legte dar, wie die DPI-Skalierung von Windows funktioniert, die DPI-Bewusstseinsmodi (Unaware / System Aware / Per-Monitor V2) sowie, wie man eine in der Ära der fest codierten 96-DPI geschriebene WinForms-App rettet. Dieser Artikel ist das WPF-Gegenstück. Ich überlasse die allgemeine Theorie zur DPI-Virtualisierung und den DPI-Bewusstseinsmodi dem WinForms-Artikel und konzentriere mich hier auf das WPF-Spezifische: warum und wo trotz eines von Anfang an angeblich DPI-bewussten Frameworks Probleme bestehen bleiben. Ich gehe in der Reihenfolge vor, in der Sie das in der Praxis tatsächlich verwenden würden — Diagnose der Symptome, wie man Per-Monitor-DPI-Unterstützung erklärt (unter .NET Framework 4.6.2 bzw. unter .NET), Behebungen für unscharfe dünne Linien und Bitmaps, die Fallstricke beim Mischen von WindowsFormsHost-Inhalten, und schließlich eine Entscheidungstabelle dafür, wie weit man gehen sollte.
1. Die Kurzfassung
- WPF verfolgt die DPI beim Start bereits von Haus aus korrekt. Seine Layout-Einheit ist die geräteunabhängige Einheit (DIP), 1/96 Zoll, und die Rendering-Pipeline skaliert automatisch passend zur System-DPI, sodass es standardmäßig System DPI Aware ist. Es gibt keine AutoScaleMode-artige Einstellung oder Prüfung, um die man sich sorgen müsste, anders als bei WinForms.1
- Die weiterhin bestehenden Probleme fallen in vier Kategorien: (a) allgemeine Unschärfe beim Verschieben auf einen Monitor mit anderer DPI, (b) unscharfe Bitmap-Bilder/Symbole, (c) unscharfe dünne Linien und Rahmen, sowie (d) gemischte Inhalte wie WindowsFormsHost / WebBrowser. Ursache und Behebung unterscheiden sich für jeden Fall — nutzen Sie zunächst die Tabelle in Kapitel 3, um zu diagnostizieren, womit Sie es zu tun haben.
- Die eigentliche Behebung für (a) ist Per-Monitor-DPI-Unterstützung. WPF unter .NET Framework 4.6.2 und später unterstützt dies auf Framework-Ebene — erklären Sie es einfach im Manifest, und die Neuskalierung des Fensters selbst geschieht automatisch.23
- WPF unter .NET (Core 3.1 bis .NET 8) ist ebenfalls standardmäßig System Aware. Es gibt kein WPF-Äquivalent zu
ApplicationHighDpiMode/SetHighDpiModevon WinForms; Sie erklären es auf demselben Weg wie unter .NET Framework, über das Manifest. - (c) ist eigentlich ein Problem der Subpixel-Positionierung, das älter ist als DPI. Das Setzen von
UseLayoutRounding="True"auf dem Wurzelelement ist der erste Schritt;SnapsToDevicePixelsist ein separates Werkzeug, das zur Renderzeit einrastet. Beide sind standardmäßig aus.45 - Für (b) sollten Vektor-Assets wie Path / Geometry Ihre erste Wahl sein. Assets, die nur als Bitmap existieren, sollten in mehreren Auflösungen vorbereitet und je nach DPI umgeschaltet werden. Die Standardinterpolation von WPF (Linear) ist es, die kleine Bilder wie Symbole am stärksten verschwimmen lässt, besonders bei nicht ganzzahligen Skalierungsfaktoren.6
- Gemischte Inhalte in (d) setzen die Obergrenze dafür, wie weit die Per-Monitor-Unterstützung gehen kann. Insbesondere ist das Per-Monitor-Verhalten für WPF, das innerhalb von ElementHost / HwndSource „gehostet“ wird, ausdrücklich als nicht unterstützt dokumentiert.3
- Die allgemeine Theorie zu DPI-Bewusstseinsmodi (DPI-Virtualisierung, der Unterschied zwischen System Aware und Per-Monitor V2, das Überschreiben durch Benutzer über die Eigenschaften der exe) sowie das Einrichten einer Test-Umgebung mit gemischter DPI teilen sich mit den Kapiteln 2–3 und 7 des WinForms-Artikels, deshalb wiederhole ich sie hier nicht.
2. Warum WPF „DPI-resilient“ ist — DIPs und System DPI Aware
Die Grundursache des High-DPI-Problems von WinForms ist, dass sowohl Koordinaten als auch Größen in physischen Pixeln fest codiert sind und der Mechanismus, der ein unter der Annahme von 96 DPI gebautes Layout zur Laufzeit neu berechnet (AutoScale), nachträglich angeflanscht wurde. WPF startet von einem anderen Punkt aus. Das 120 in einem in XAML geschriebenen Width="120" sind keine physischen Pixel — es ist eine geräteunabhängige Einheit (DIP), bei der 1 Einheit 1/96 Zoll entspricht. Das gesamte Layout wird in dieser Einheit berechnet und zur Renderzeit auf einen zur System-DPI passenden Multiplikator umgerechnet (1,5-fach bei 150 %). Text wird ebenfalls als Vektorschrift gerendert, sodass es beim Hochskalieren keine bitmapartige Verschlechterung gibt. Dank dieses Entwurfs verhält sich eine WPF-App ohne jede Erklärung als System DPI Aware.1
Hier ein Vergleich mit WinForms.
| WinForms | WPF | |
|---|---|---|
| Einheit für Koordinaten/Größe | Physische Pixel | DIP (1/96 Zoll) |
| Verhalten ohne Erklärung | Unaware (vom Betriebssystem durch Bitmap-Streckung verschwommen) | System Aware (scharf auf dem primären Monitor) |
| Nachverfolgung der Start-DPI | Neuberechnung pro Formular über AutoScaleMode. Erfordert Konfiguration und Überprüfung | Rendering-Pipeline skaliert automatisch. Keine Konfiguration nötig |
| Typischer Bruch bei hoher DPI | Überlappende/abgeschnittene Steuerelemente durch Layouts mit festen Koordinaten | Layout bricht nicht; äußert sich stattdessen als Unschärfe |
| Vom Designer verursachte Pannen | Überschriebenes AutoScaleDimensions (ein Klassiker) | Im Grunde keine (XAML wird unverändert in DIPs gespeichert) |
Wie die Tabelle zeigt, existiert die Arbeit, die den Aufwand bei WinForms dominierte — kaputte Layouts beheben und sich gegen Designer-Pannen absichern — bei WPF kaum. „WPF ist DPI-resilient“ ist eine faire Beschreibung.
Allerdings deckt die Automatisierung Sie nur bis zu System Aware ab. System Aware bedeutet „die DPI des primären Monitors zum Zeitpunkt der Anmeldung übernehmen“ — es schließt nicht das Nachverfolgen (Per-Monitor) einer Umgebung ein, in der unterschiedliche Monitore unterschiedliche DPI besitzen. Und was tatsächlich in DIPs skaliert wird, ist nur das, was WPF selbst zeichnet (Text, Formen, Steuerelemente); Bitmap-Bilder verschwimmen beim Hochskalieren weiterhin, und gemischte Inhalte, die ein HWND mitbringen, liegen vollständig außerhalb der Skalierung von WPF. Fast jede Beratungsanfrage nach dem Muster „aber es sollte doch DPI-resilient sein“, die wir erhalten, geht auf diesen verbleibenden Anteil zurück.
3. Probleme, die weiterhin auftreten — Ursachendiagnose aus dem Symptom
Dies ist die Triage-Tabelle, zu der wir bei jeder Beratung zuerst greifen. Bei WPF ist die Zuordnung zwischen Symptom und Ursache noch klarer als bei WinForms, sodass allein diese Tabelle die Ursache meist eingrenzt.
| Symptom | Ursache | Behebung |
|---|---|---|
| Das Verschieben des Fensters auf einen Monitor mit anderer DPI verschwimmt alles gleichmäßig; das Zurückverschieben behebt es | Immer noch System Aware — das Betriebssystem streckt das gesamte Fenster als Bitmap | Kapitel 4 (zu Per-Monitor wechseln) |
| Verschwimmt direkt nach dem Ändern der Skalierungseinstellung, oder über eine RDP-Verbindung von einem High-DPI-Client | Dasselbe wie oben (System-DPI ist bei der Anmeldung festgelegt) | Kapitel 4 |
| Text ist scharf, aber nur Symbole/Bilder sind unscharf oder zu klein | Bitmap-Assets werden beim Hochskalieren interpoliert | Abschnitt 5.3 |
| 1px-Rasterlinien/Rahmen variieren je nach Position in der Dicke, oder wirken leicht unscharf | Subpixel-Positionierung und Anti-Aliasing | Abschnitt 5.1 |
| Kleiner Text wirkt selbst auf einem 100-%-Monitor (96 DPI) unscharf | Anti-Aliasing durch die standardmäßige Textformatierung (Ideal) | Abschnitt 5.2 |
| Selbst erzeugte Bilder (WriteableBitmap, RenderTargetBitmap usw.) wirken unscharf | Pixelgröße wird unter Annahme von 96 DPI berechnet | Kapitel 6 |
| Fensterposition, Mauskoordinaten oder Screenshot-Koordinaten stimmen nicht | Verwechslung von DIPs mit physischen Pixeln | Kapitel 6 |
| Nur innerhalb eines WindowsFormsHost / WebBrowser / bestimmten Drittanbieter-Steuerelements wirkt der Inhalt klein, grob oder kaputt | Gemischte Inhalte (außerhalb der Skalierung von WPF) | Kapitel 7 |
Ein Hinweis zur ersten Zeile, „verschwimmt überall gleichmäßig“. Dies ist, wie in Kapitel 2 des WinForms-Artikels behandelt, DPI-Virtualisierung (die Bitmap-Streckung des Betriebssystems), die bei einer System-Aware-App wirkt — die App selbst ist nicht kaputt. Eine System-Aware-App zeichnet mit „der DPI des primären Monitors zum Zeitpunkt der Anmeldung“, und auf jedem anderen DPI-Monitor skaliert das Betriebssystem sie hoch oder herunter, um das auszugleichen. Es ist die Rückfalllösung des Betriebssystems: Das Layout bricht nicht, verschwimmt aber stattdessen.1 Die Behebung bedeutet, diese Rückfalllösung abzulehnen und zu erklären: „Ich verfolge die DPI jedes Monitors selbst“ — mit anderen Worten, zu Per-Monitor zu wechseln.
Umgekehrt lassen sich die Symptome ab Zeile 3 beheben, während man System Aware bleibt. Lautet die Anfrage „wir betreiben keine Mehrmonitor-Setups, aber Symbole und Rasterlinien wirken bei 150 % unordentlich“, ist es unbedenklich, Kapitel 4 zu überspringen und direkt bei Kapitel 5 zu beginnen.
4. Unschärfe bei mehreren Monitoren — Per-Monitor-DPI-Unterstützung
4.1 Die Grenzen von System Aware
Die System-DPI ist auf die DPI des primären Monitors zum Zeitpunkt der Anmeldung festgelegt. Ist das eingebaute 150-%-Display eines Laptops der primäre Monitor, zeichnet WPF jedes Fenster mit dem 1,5-fachen, und beim Verschieben auf einen 100-%-externen Monitor zeigt das Betriebssystem es auf zwei Drittel geschrumpft an. Diese Skalierung auf Betriebssystemebene wirkt bei nicht ganzzahligen Verhältnissen besonders unscharf.1 Testet man es tatsächlich, stellt man fest, dass eine unbequeme Kombination wie 125 % und 150 % visuell stärker leidet als ein sauberer 2-facher Unterschied wie 200 % und 100 %.
Mit anderen Worten: Die Situationen, in denen System-Aware-WPF tatsächlich Ärger verursacht, beschränken sich auf das Verschieben zwischen Monitoren mit unterschiedlicher DPI und Skalierungseinstellungen, die sich nach der Anmeldung ändern (der primäre Monitor wechselt, wenn eine Dockingstation an-/abgesteckt wird, eine RDP-Sitzung bringt die DPI des Clients mit, und Ähnliches). Für eine interne App, die alle auf einem festen Einzelmonitor-Desktop nutzen, ist es praktisch ausreichend, System Aware zu bleiben. Wir kommen in der Tabelle von Kapitel 8 auf diese Entscheidung zurück.
4.2 Erklärung unter .NET Framework 4.6.2 und später
Die Per-Monitor-DPI-Unterstützung von WPF kam mit .NET Framework 4.6.2.2 Davor verfolgte WPF selbst DPI-Änderungen nicht, selbst wenn man dem Betriebssystem Per-Monitor erklärte, sodass man die gesamte Neuskalierung des Fensters selbst schreiben musste (weshalb Beispiele aus der Windows-8.1-Ära eine so aufwendige Struktur um eine native Hilfs-DLL herum hatten). Ab 4.6.2 handhabt WPF WM_DPICHANGED selbst und kümmert sich automatisch um die Größenänderung des Fensters, das Neu-Layout und das Neuzeichnen.
Es gibt zwei Voraussetzungen: ein Betriebssystem ab dem Windows-10-Anniversary-Update (1607) und einen Build, der auf .NET Framework 4.6.2 oder höher zielt.3 Sie erklären dies im Anwendungsmanifest.
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings>
<!-- Unter .NET Framework 4.6.2-4.7.2 nur PerMonitor erklären (entspricht dem Entwicklerleitfaden).
Die PerMonitorV2-Unterstützung von WPF erfordert .NET Framework 4.8 oder höher,
setzen Sie daher unter 4.8+ / .NET V2 zuerst als "PerMonitorV2, PerMonitor" -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"
>PerMonitor</dpiAwareness>
<!-- Für ältere Betriebssysteme, die dpiAwareness nicht kennen (Rückfall auf System Aware) -->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
Dieser zweistufige Aufbau existiert aus gutem Grund. Das Element dpiAwareness wird ab Windows 10 1607 erkannt, und unter den durch Kommas getrennten Werten gewinnt der erste, der erkannt wird.7 Auf älteren Betriebssystemen, die das Element dpiAwareness überhaupt nicht kennen, fällt es auf die dpiAware-Erklärung (System Aware) zurück — das ist der wirkende Mechanismus.
Achten Sie hier darauf, zwischen PerMonitor und PerMonitorV2 anhand des Ziel-Frameworks zu wählen. Die offizielle Unterstützung von WPF für PerMonitorV2 (und Mixed-Mode-DPI) beginnt mit .NET Framework 4.88, und selbst das Beispiel im 4.6.2-Entwicklerleitfaden erklärt nur PerMonitor.3 Zielen Sie auf 4.6.2–4.7.2 und setzen V2 zuerst, wählt ein Betriebssystem ab Windows 10 1703 den V2-Modus, den das Framework tatsächlich nicht unterstützt, was zu unerwartetem Verhalten führt, besonders rund um gemischte Inhalte wie WindowsFormsHost. Unsere Empfehlung ist, zuerst auf 4.8 oder höher (oder WPF unter .NET) zu aktualisieren, dann V2 zuerst als PerMonitorV2, PerMonitor zu setzen und das Betriebssystem die Skalierung von Nicht-Client-Bereichen wie Titelleiste und Bildlaufleisten übernehmen zu lassen (siehe Kapitel 3 des WinForms-Artikels).
Ein Ziel-Framework-Stolperstein, den man kennen sollte: Selbst wenn das auf dem Rechner installierte .NET Framework 4.6.2 oder höher ist, ist die Per-Monitor-Nachverfolgung standardmäßig deaktiviert, wenn das Projekt noch auf 4.6.1 oder früher zielt. Aktivieren Sie sie in diesem Fall explizit mit einem AppContext-Switch in app.config.3
<configuration>
<runtime>
<!-- Auf die doppelte Verneinung achten: "nicht bei DPI-Änderung skalieren" auf false setzen = aktivieren -->
<AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/>
</runtime>
</configuration>
Nach unserer Erfahrung führt „ich habe das Manifest geschrieben, und es funktioniert trotzdem nicht“ fast immer entweder auf dieses Ziel-Framework-Problem zurück, oder darauf, dass das Manifest tatsächlich nicht in den Build aufgenommen wurde (die Projekteinstellungen zeigen noch auf das Standardmanifest).
4.3 Erklärung unter .NET (Core 3.1 bis .NET 8)
WPF unter .NET ist ebenfalls standardmäßig System Aware ohne Manifest. Während WinForms einen dedizierten Einstiegspunkt über ApplicationHighDpiMode in der Projektdatei oder Application.SetHighDpiMode besitzt, hat WPF keinen offiziellen Mechanismus, um den DPI-Bewusstseinsmodus per Code oder Projekteinstellungen umzuschalten. Sie erklären es auf demselben Weg wie unter .NET Framework: fügen Sie dem Projekt ein app.manifest hinzu (in Visual Studio: Neues Element hinzufügen → Anwendungsmanifestdatei), schreiben Sie dieselbe dpiAwareness-Erklärung wie in Abschnitt 4.2, und referenzieren Sie sie aus der Projektdatei.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
</Project>
Da die Laufzeit neuer ist, wird der AppContext-Switch aus Abschnitt 4.2 nicht benötigt. Seien Sie sich nur bewusst, dass „zu .NET migrieren“ High-DPI-Probleme nicht automatisch für Sie löst. Der Migrationsgewinn in dieser Hinsicht gehört WinForms (Abschnitt 4.1 des WinForms-Artikels) — WPF hatte das heutige Niveau bereits mit .NET Framework 4.6.2 erreicht.
Nach der Erklärung bleibt, genau wie bei WinForms, noch Arbeit übrig, doch der Inhalt unterscheidet sich. Bei WinForms war das Beheben kaputter Layouts das Hauptschlachtfeld. Bei WPF übernimmt das Framework die Layout-Nachverfolgung für Sie, sodass eine visuelle Überprüfung jedes Bildschirms, das Umschalten von Bitmap-Assets je nach DPI (Abschnitt 5.3), das Aktualisieren von Code, der Pixel direkt anfasst (Kapitel 6), und das Prüfen gemischter Inhalte (Kapitel 7) übrig bleiben. Bei gleicher Bildschirmzahl fällt der Gesamtaufwand, um zu Per-Monitor zu wechseln, typischerweise eine Stufe geringer aus als bei WinForms.
5. Rendering-Unschärfe — dünne Linien, Text und Bitmaps
Der Inhalt dieses Kapitels ist unabhängig vom Wechsel zu Per-Monitor. Er wirkt auch, während man System Aware bleibt, und lohnt sich deshalb selbst für Apps, die nie in einem Mehrmonitor-Setup laufen.
5.1 Unscharfe dünne Linien und Rahmen — UseLayoutRounding und SnapsToDevicePixels
Das Layouten in DIPs bedeutet, dass nicht garantiert ist, dass die Grenze eines Elements auf einer ganzzahligen physischen Pixelposition landet. Bei 125 % entspricht 1 DIP 1,25 px, sodass eine 1-DIP breite Linie 1,25 physischen Pixeln entspricht, und eine Kante, die zwischen Pixel fällt, wird per Anti-Aliasing halbtransparent gezeichnet. Das Ergebnis ist, „die Linie wirkt unscharf“ oder „sie ist auf dasselbe 1px gesetzt, aber Zeilen sehen unterschiedlich dick aus“. Dasselbe kann sogar bei 96 DPI passieren, wenn eine sternförmig bemessene (*) Unterteilung eines Grid oder eine Margin-Berechnung bei zentrierter Ausrichtung auf 0,5 px landet. Dies ist eher eine Eigenschaft des Subpixel-Renderings von WPF als streng genommen ein DPI-Problem — höher aufgelöst wird es eher sichtbarer.4
Der erste Schritt ist die Layout-Rundung. Fügen Sie dem Wurzelelement eine Zeile hinzu.
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
UseLayoutRounding="True">
UseLayoutRounding ist ein Mechanismus, der nicht ganzzahlige Pixelwerte während des Layout-Durchgangs rundet; er ist standardmäßig aus, und das Setzen auf der Wurzel pflanzt sich durch den gesamten visuellen Baum fort.4 Das ähnlich benannte SnapsToDevicePixels spielt eine andere Rolle — statt beim Layout rastet es Kanten zur Renderzeit an Pixelgrenzen ein. Auch dies ist standardmäßig false, und die Einstellung wird an den Unterbaum vererbt. Die offizielle Dokumentation selbst nennt die Reduzierung anti-aliasing-bedingter Unschärfe rund um dünne Linien oberhalb von 96 DPI als Anwendungsfall.5
| UseLayoutRounding | SnapsToDevicePixels | |
|---|---|---|
| Wann es wirkt | Während des Layout-Durchgangs (rundet Measure-/Arrange-Ergebnisse auf ganzzahlige Pixel) | Zur Renderzeit (rastet Kanten an Pixelgrenzen ein) |
| Standard | false | false |
| Umfang | Pflanzt sich zu Nachkommen fort, wenn auf der Wurzel gesetzt | Wird ebenfalls an Nachkommen vererbt |
| Hauptwirkung | Layout im Allgemeinen — der 0,5-px-Versatz bei Größe/Position eines Elements und die daraus resultierende Unschärfe bei Linien/Rahmen | Bei einzelnen Elementen wie Border oder dünnen Linien verbleibende Unschärfe |
| Faustregel | Zuerst auf dem Wurzel-Window setzen; bei neuen Apps in einem gemeinsamen Stil für alle Fenster aufnehmen | Gezielt auf Stellen anwenden, die nach UseLayoutRounding noch unscharf sind |
Gehen Sie im Zweifel in dieser Reihenfolge vor: UseLayoutRounding="True" auf der Wurzel, dann SnapsToDevicePixels="True" auf allem, was noch unscharf ist. Als Nebeneffekt der Rundung können sternförmig gleichmäßig aufgeteilte Spaltenbreiten um ein bis zwei Pixel ungleich werden, doch wir haben selten gesehen, dass dies auf einem Business-App-Bildschirm tatsächlich zu einem Problem führt. Für Unschärfe in Code, der selbst über DrawingContext zeichnet, gibt es auch ein niedrigeres Werkzeug namens GuidelineSet, doch das bloße Runden der Zeichenkoordinaten löst die meisten Fälle.
5.2 Unscharfer Text — TextFormattingMode
Die alte Beschwerde, „WPF-Text wirkt blass/unscharf“, betrifft eigentlich eher den Textformatierungsmodus als DPI an sich. Die Textformatierung von WPF kennt zwei Modi: Ideal (der Standard), das Glyphen anhand der wahren idealen Metriken der Schriftart positioniert, und Display, das sie anhand GDI-kompatibler Metriken positioniert.9 Ideal erzeugt einen schönen Zeichenabstand und skaliert sauber, doch das Rendern kleinen Texts bei 96 DPI (100 %) kann durch Anti-Aliasing unscharf wirken. Das Setzen von TextOptions.TextFormattingMode="Display" auf der Wurzel gibt Ihnen dieses scharfe, WinForms-artige Gefühl.
In einer High-DPI-Umgebung dreht sich die Geschichte jedoch um. Display formatiert Glyphen passend zum 96-DPI-Pixelraster, sodass sich die Qualität unter Skalierung tatsächlich verschlechtern kann. Die praktische Faustregel: verwenden Sie Display, wenn die meisten Ihrer Benutzer bei 100 % sind; ansonsten, in der heute üblichen Standardumgebung, in der 125 %+ die Norm ist, bleiben Sie beim Standard Ideal. Es ist möglich, nur bei 100 % zu Display umzuschalten, doch die Bildschirme, bei denen sich dieser Aufwand lohnt (textedito-artige Bildschirme), sind begrenzt.
5.3 Unscharfe Bilder und Symbole — zuerst Vektor, dann mehrere Auflösungen
WPF platziert auch einem Image zugewiesene Bitmaps in DIPs und skaliert sie zur DPI hoch. Ein 16×16-px-Symbol wird bei 150 % auf 24×24 px hochinterpoliert, und der standardmäßige Interpolationsalgorithmus (Linear) macht es merklich unscharf.6 Der Eindruck, dass „der Text scharf ist, aber nur die Symbole weich wirken“, in einer WPF-App ist fast immer genau dies.
Es gibt drei Gegenmaßnahmen, in Prioritätsreihenfolge.
- Vektor-Assets verwenden (die erste Wahl). Als
Path/Geometry/DrawingImagegehaltene Symbole rendern bei jeder DPI scharf und benötigen keinen Umschaltcode. Es gibt reichlich Werkzeuge zur Konvertierung aus Design-Tools oder SVG nach XAML. Es lohnt sich, „neue Symbole starten als Vektoren“ zur Hausregel für neue Apps zu machen. Symbolschriften wie Segoe MDL2 Assets bieten denselben Vorteil. - Zwischen mehreren Bitmap-Auflösungen je nach DPI umschalten. Für Assets, die nicht vektorisiert werden können — Fotos, Screenshots und Ähnliches —, bereiten Sie Versionen für 96/120/144/192 DPI vor (z. B. 16/20/24/32 px) und wählen anhand der aktuellen DPI aus. Auch der offizielle Entwicklerleitfaden empfiehlt das Umschalten von Assets je DPI als Behebung für Unschärfe.1
- Die Interpolation über
RenderOptions.BitmapScalingModewählen. Dies ist eine Abhilfe für den Fall, dass keine weiteren Assets ergänzt werden können. Bei einem ganzzahligen Verhältnis wie 200 % wirktNearestNeighbor— Hochskalieren bei erhaltenen scharfen Punkten — schärfer, undHighQuality(Fant) eignet sich für das Verkleinern großer Bilder.6
Das Umschalten in Option 2 erfolgt, sobald Sie Per-Monitor-fähig sind, in OnDpiChanged (verfügbar ab .NET Framework 4.6.2).
public partial class MainWindow : Window
{
protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi)
{
base.OnDpiChanged(oldDpi, newDpi);
// Wird jedes Mal aufgerufen, wenn das Fenster zwischen Monitoren wechselt oder sich die Skalierung ändert
AppIcon.Source = IconAssets.SelectFor(newDpi.DpiScaleX);
}
}
public static class IconAssets
{
// Das zum Skalierungsfaktor passende Asset aus den vorhandenen 16/24/32-px-Versionen wählen
public static BitmapImage SelectFor(double scale) => scale switch
{
<= 1.0 => Load("icon16.png"),
<= 1.5 => Load("icon24.png"),
_ => Load("icon32.png"),
};
private static BitmapImage Load(string name) =>
new(new Uri($"pack://application:,,,/Assets/{name}"));
}
Bleiben Sie System Aware, ändert sich die DPI nach dem Start nie, sodass es ausreicht, einmalig beim Start (Loaded) mit VisualTreeHelper.GetDpi(this) auszuwählen. Sich vor dem Schreiben von Umschaltcode immer zu fragen, „könnte dieses Asset stattdessen ein Vektor sein“, erweist sich am Ende als das am leichtesten zu pflegende Vorgehen.
6. Code, der Pixel handhabt, und die Nachverfolgung von DPI-Änderungen
Was außerhalb des Regenschirms der automatischen Skalierung von WPF liegt, ist Code, der physische Pixel selbst zählt. Es gibt drei typische Fälle, und alle zeigen sich auf dieselbe unangenehme Weise: „perfekt auf der Entwicklungsmaschine (100 %), unscharf oder daneben nur beim Kunden bei 150 %“.
Der erste Fall ist Code, der selbst einen Pixelpuffer baut, wie WriteableBitmap / RenderTargetBitmap. Konstruieren Sie ihn mit einer Pixelanzahl gleich der DIP-Größe, wird er bei 150 % um das 1,5-fache hochinterpoliert und verschwimmt. Sie können die aktuelle DPI aus der von VisualTreeHelper.GetDpi zurückgegebenen DpiScale-Struktur abrufen, also bemessen Sie die Pixelanzahl in physischen Pixeln und backen die richtige DPI in den Puffer ein.
// imageHost: das Element, das das gerenderte Ergebnis anzeigt (ActualWidth/Height sind in DIPs)
DpiScale dpi = VisualTreeHelper.GetDpi(imageHost);
int pixelWidth = (int)Math.Ceiling(imageHost.ActualWidth * dpi.DpiScaleX);
int pixelHeight = (int)Math.Ceiling(imageHost.ActualHeight * dpi.DpiScaleY);
// Nicht 96,96 fest codieren — die tatsächliche DPI übergeben (so wird 1 physisches Pixel = 1 Pufferpixel)
var bitmap = new WriteableBitmap(
pixelWidth, pixelHeight,
dpi.PixelsPerInchX, dpi.PixelsPerInchY,
PixelFormats.Bgra32, null);
Der zweite Fall sind Bildschirmkoordinaten und Win32-Interop. Von PointToScreen zurückgegebene Koordinaten, über WM_MOUSEMOVE oder einen Hook empfangene Koordinaten sowie an MoveWindow übergebene Koordinaten sind allesamt physische Pixel — mischen Sie diese mit DIPs auf der XAML-Seite, liegen Sie bei 125 % um das 1,25-fache daneben. Verwenden Sie die Matrix von CompositionTarget zur Umrechnung.
var source = PresentationSource.FromVisual(this);
if (source?.CompositionTarget is { } target)
{
// DIP -> physische Pixel
Point device = target.TransformToDevice.Transform(new Point(x, y));
// physische Pixel -> DIP
Point dip = target.TransformFromDevice.Transform(devicePoint);
}
Der dritte Fall ist Caching. Wenn Sie Bitmaps, Layoutwerte oder formatierten Text zwischenspeichern, die mit einer bestimmten eingebackenen DPI gebaut wurden, werden sie ungültig, sobald das Fenster nach dem Wechsel zu Per-Monitor zwischen Monitoren verschoben wird. Bauen Sie sie, wie in Abschnitt 5.3, in OnDpiChanged (oder dem DpiChanged-Ereignis des Fensters) neu auf. Und bleiben Sie erneut System Aware, ist die DPI beim Start festgelegt, sodass kein Nachverfolgungscode benötigt wird. Die Faustregel „die Kosten des Wechsels zu Per-Monitor entsprechen der Menge an Code, der Pixel anfasst“ passt ziemlich gut zur Realität.
Wie man mit dem UI-Thread umgeht, wenn diese Art von Neuerzeugungsarbeit asynchron erfolgt, steht in „async und der UI-Thread in WPF/WinForms auf einem Blatt“.
7. Die Falle gemischter Inhalte — WindowsFormsHost, WebBrowser, Drittanbieter-Steuerelemente
Die automatische Skalierung von WPF reicht nur bis zu Dingen, die WPF selbst zeichnet. Elemente, die ein HWND mitbringen, liegen „außerhalb“ des Renderings von WPF, was die DPI-Handhabung um eine Stufe komplizierter macht. Die offizielle Windows-Dokumentation stellt dies unmissverständlich fest: Ein anderes, in WPF gehostetes Framework, oder WPF, das in einem anderen Framework gehostet wird, skaliert nicht automatisch.10
| Form der Mischung | Was passiert | Praktischer Umgang |
|---|---|---|
| WindowsFormsHost (WinForms in WPF hosten) | Der Host rechnet zwischen den beiden Koordinatensystemen, DIPs und physischen Pixeln, um, doch der Inhalt selbst skaliert nur so weit, wie das gehostete WinForms-Steuerelement es unterstützt11 | Die DPI-Unterstützung (AutoScaleMode usw.) des inneren WinForms-Inhalts anhand der Standards des WinForms-Artikels prüfen. Nicht erwarten, dass er Per-Monitor nachverfolgt |
| WebBrowser-Steuerelement (IE-Engine) | Nativer Inhalt in einem separaten HWND. Seine Zoomstufe kann mit der eigenen Skalierung der App aus dem Takt geraten | Als Legacy behandeln. Dies beim Abwägen eines Wechsels zu WebView2 berücksichtigen |
| ElementHost / HwndSource (WPF in WinForms oder Win32 hosten) | Das Per-Monitor-Szenario wird offiziell nicht unterstützt3 | Nur bis System Aware entwerfen, passend zum DPI-Bewusstseinsmodus der hostenden App |
| Drittanbieter-Steuerelemente | Der Unterstützungsgrad variiert je nach Produkt. Ein Produkt ohne Per-Monitor-Unterstützung begrenzt, was auf diesem Bildschirm erreichbar ist | Den Unterstützungsstatus und die Version jedes Herstellers inventarisieren, bevor Sie sich für Per-Monitor entscheiden |
In der Praxis ist die erste Zeile mit Abstand die häufigste. Nehmen Sie eine App, die zu WPF migriert wurde, aber weiterhin ein altes WinForms-/ActiveX-Steuerelement für Berichtsvorschauen oder Diagramme über WindowsFormsHost hostet — wechseln Sie zu Per-Monitor, und der WPF-Teil folgt perfekt, doch nur der Inhalt innerhalb des Hosts folgt der DPI nach einem Wechsel nicht und bleibt klein und grob. Es gibt einen Win32-Mechanismus zum Hosten gemischter DPI (SetThreadDpiHostingBehavior), doch das ist nichts, das man aus WPF beiläufig greifen könnte; unser Ansatz besteht darin, zu akzeptieren, dass „der Teil mit gemischten Inhalten die Obergrenze der Per-Monitor-Unterstützung setzt“, und zunächst eine Inventarisierung gemischter Inhalte aufzubauen. Material zur Entscheidung, sich von gemischten Inhalten zu lösen, finden Sie in „Wie man zwischen WinForms, WPF und WinUI wählt“ und „Behalten, Wrappen oder Ersetzen eines ActiveX-/OCX-Steuerelements“.
8. Wie weit man gehen sollte — eine Entscheidungstabelle und ein stufenweiser Ansatz
Bei WPF gibt es eigentlich nur zwei Optionen (es gibt keinen WinForms-artigen Weg „nichts tun = Unaware“, da es ohne jede Erklärung bereits System Aware ist).
| System Aware bleiben | Zu Per-Monitor wechseln | |
|---|---|---|
| Erscheinungsbild | Scharf auf dem primären Monitor. Verschwimmt auf einem Monitor mit anderer DPI, nach einer Skalierungsänderung oder über RDP | Scharf auf jedem Monitor |
| Erklärungsarbeit | Nicht nötig (Standard) | Nur das Manifest hinzufügen (Kapitel 4) |
| Verbleibende Arbeit nach der Erklärung | — | Visuelle Überprüfung jedes Bildschirms + DPI-Umschaltung für Bitmap-Assets (Abschnitt 5.3) + DpiChanged-Handhabung für pixelbasierten Code (Kapitel 6) + Prüfung gemischter Inhalte (Kapitel 7) |
| Was die Obergrenze setzt | — | WindowsFormsHost, WebBrowser, Drittanbieter-Steuerelemente |
| Gut geeignet für | Interne Apps, zentriert auf einen Einzelmonitor-Desktop mit fester Konfiguration. Apps mit viel gemischtem Inhalt | Gemischte Nutzung von Laptop + externem Monitor, langlebige Flaggschiff-Apps, an Kunden verteilte Produkte |
Die Entscheidungsachsen sind dieselben wie in Kapitel 7 des WinForms-Artikels (Lebensdauer der App, Einsatzumgebung, Budget für die Nacharbeit), doch der Unterschied ist, dass die Grenzkosten von WPF für den Wechsel zu Per-Monitor gering sind. Die Erklärung ist eine einzige Datei, das Framework verfolgt das Layout, und die verbleibende Arbeit skaliert mit der Menge an pixelbasiertem Code und Assets. Für eine App mit wenig gemischtem Inhalt ist die Kosten-Nutzen-Relation eines vollständigen Wechsels zu Per-Monitor eindeutig höher als bei WinForms. Der stufenweise Ansatz, den wir bei realen Projekten tatsächlich empfehlen, hat drei Schritte.
- Qualität verbessern, während man System Aware bleibt:
UseLayoutRoundingauf der Wurzel, Vektorisierung/Mehrfachauflösung von Symbolen, DPI-Korrekturen für Code derWriteableBitmap-Familie. Alles außer der Unschärfe bei mehreren Monitoren wird hier bereinigt, und diese Arbeit bleibt als dauerhaftes Gut erhalten, selbst wenn Sie sich entscheiden, nicht zu Per-Monitor zu wechseln. - Per-Monitor erklären und überprüfen: das Manifest hinzufügen und jeden Bildschirm in einer gemischten DPI-Umgebung durchgehen, um Problemstellen zu markieren. Alles, was Sie hier finden, sollte sich einem der Kapitel 5–7 zuordnen lassen.
- Die Nachverfolgung von DPI-Änderungen implementieren: Asset-Umschaltung und Cache-Neuerzeugung in
OnDpiChanged. Dies ist die Stufe, in der Sie entscheiden, wie viel gemischten Inhalt Sie tolerieren wollen.
Die im Kapitel 7 des WinForms-Artikels beschriebene Konfiguration der Testumgebung — zwei Monitore mit unterschiedlicher Skalierung, das Tauschen des primären Monitors plus erneute Anmeldung, das Ändern der Skalierung während die App läuft, sowie RDP von einem High-DPI-Client — überträgt sich direkt.10 Was sich bei WPF besonders lohnt, ist der Fokus auf Rasterlinien, Symbole und selbst gezeichneten Inhalt bei nicht ganzzahligen Verhältnissen (125 % / 150 %), sowie das Verhalten beim Ziehen eines Fensters über Monitore hinweg. Die Verteilung Ihrer Einsatzumgebung im Voraus zu kennen (welcher Prozentsatz der Benutzer Mehrmonitor-Setups betreibt) hält die Investitionsentscheidung stabil. Wir haben diesen Blickwinkel auch in „Windows-App-UX-Design“ angesprochen.
9. Zusammenfassung
Dank DIPs und automatischer Skalierung ist WPF von Anfang an System DPI Aware, und der Kampf gegen kaputte Layouts, der WinForms dominierte, existiert hier kaum. Die Probleme, die tatsächlich bestehen bleiben, fallen in vier Kategorien — Unschärfe bei mehreren Monitoren (keine Per-Monitor-Unterstützung), unscharfe Bitmaps, unscharfe dünne Linien und gemischte Inhalte — und jede hat eine etablierte Behebung.
- Unschärfe bei mehreren Monitoren: Bei WPF unter .NET Framework 4.6.2+ oder .NET reicht allein die Erklärung im Manifest, um automatische Per-Monitor-Nachverfolgung zu erhalten
- Dünne Linien/Rahmen:
UseLayoutRounding="True"auf der Wurzel ist der erste Schritt, mitSnapsToDevicePixelsfür alles Verbleibende - Symbole: Vektor-Assets sind die erste Wahl; Bitmaps brauchen mehrere Auflösungen plus DPI-basiertes Umschalten
- Nur Code, der Pixel handhabt —
WriteableBitmap, Bildschirmkoordinaten und Ähnliches — ist das eigentliche Ziel der Nacharbeit; seine Menge bestimmt den Aufwand des Wechsels zu Per-Monitor - WindowsFormsHost / WebBrowser / Drittanbieter-Steuerelemente setzen die Obergrenze der Unterstützung. Inventarisieren Sie sie zuerst
Selbst eine App, die bei „es ist WPF, also sollte es in Ordnung sein“ steckengeblieben ist, stellt sich, wenn man sie durch diesen Rahmen betrachtet, meist als eine mit nur einer Handvoll tatsächlich zu behebender Stellen heraus. Wenn Sie unsicher sind, wie weit sich Ihre eigene App beheben lässt, oder Unterstützung bei der Bewertung des aktuellen Zustands — einschließlich gemischter Inhalte — und bei der Festlegung der richtigen Unterstützungsstufe benötigen, können wir helfen.
Verwandte Artikel
- High-DPI-Unterstützung in WinForms — Warum die UI auf 4K-Monitoren verschwimmt oder zerbricht, und wie Sie das praktisch beheben
- Wie man zwischen WinForms, WPF und WinUI wählt — Eine praxisnahe Entscheidungstabelle
- async und der UI-Thread in WPF/WinForms auf einem Blatt
- Windows-App-UX-Design — Priorisierung nach Einsatzumgebung
Verwandte Beratungsbereiche
Die Komura Soft LLC übernimmt die High-DPI-Nachbesserung für WPF-/WinForms-Apps (Bestandsaufnahme, Beurteilung der Machbarkeit eines Wechsels zu Per-Monitor, Inventarisierung und Behebung gemischter Inhalte), die Untersuchung von Darstellungsproblemen, die nach PC-Austauschen oder der Einführung von 4K-Monitoren auftreten, sowie Beratung zur UI-Modernisierung.
- Technische Beratung & Design-Review
- Windows-App-Entwicklung
- Nutzung von Altbeständen & Migrationsunterstützung
- Kontakt
Referenzlinks
-
Microsoft Learn, Developing a Per-Monitor DPI-Aware WPF Application. Dazu, dass WPF standardmäßig System DPI Aware ist, zum automatischen Skalierungsmechanismus über DIPs, dazu, wie das Betriebssystem beim Verschieben auf einen Monitor mit anderer DPI skaliert (besonders unscharf bei nicht ganzzahligen Verhältnissen), sowie zur Praxis, Bitmap-Assets je DPI umzuschalten. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, What’s new in .NET Framework. Dazu, dass Per-Monitor-DPI-Bewusstheit für WPF in .NET Framework 4.6.2 aktiviert wurde, zum Switch Switch.System.Windows.DoNotScaleForDpiChanges und zum Entwicklerleitfaden auf GitHub. ↩ ↩2
-
GitHub (microsoft/WPF-Samples), Per Monitor DPI Developer Guide. Zu den Anforderungen Windows-10-Anniversary-Update und .NET Framework 4.6.2+, zu den Manifest-Erklärungen dpiAwareness / dpiAware, zu AppContextSwitchOverrides für Ziele älter als 4.6.2, und dazu, dass Per-Monitor für in HwndSource / ElementHost gehostetes WPF nicht unterstützt wird. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Layout - WPF. Zum Layout über DIPs und dem Subpixel-Rendering-Mechanismus, der unscharfe Kanten verursacht, zur Layout-Rundung (UseLayoutRounding), die standardmäßig aus ist, sowie dazu, wie das Setzen auf dem Wurzelelement sich durch den visuellen Baum fortpflanzt. ↩ ↩2 ↩3
-
Microsoft Learn, UIElement.SnapsToDevicePixels Property. Zum Standardwert false, dazu, dass die Einstellung an den Unterbaum vererbt wird, wenn sie auf der Wurzel gesetzt ist, sowie dazu, wie sie visuelle Artefakte durch Anti-Aliasing rund um dünne Linien in Umgebungen oberhalb von 96 DPI reduzieren kann. ↩ ↩2
-
Microsoft Learn, BitmapScalingMode Enum. Dazu, dass der Standard (Unspecified) Linear ist, sowie zu den Eigenschaften der Interpolationsalgorithmen HighQuality (Fant) und NearestNeighbor. ↩ ↩2 ↩3
-
Microsoft Learn, Setting the default DPI awareness for a process. Dazu, dass das Element dpiAwareness (Windows 10 1607+) Vorrang vor dpiAware hat, sowie zum Rückfallverhalten, bei dem der erste erkannte Wert in einer durch Kommas getrennten Liste verwendet wird. ↩
-
Microsoft Learn, What’s new in .NET Framework. Dazu, dass .NET Framework 4.8 Unterstützung für Per-Monitor-V2-DPI-Bewusstheit und Mixed-Mode-DPI-Skalierung in WPF hinzufügt, zu Verbesserungen bei der Interop mit gehosteten HWNDs / WinForms, sowie zum zur Aktivierung nötigen AppContext-Switch. ↩
-
Microsoft Learn, TextFormattingMode Enum. Zu den beiden Textformatierungsmodi, Ideal (ideale Metriken) und Display (GDI-kompatible Metriken). ↩
-
Microsoft Learn, High DPI Desktop Application Development on Windows. Zur Tabelle der Per-Monitor-Unterstützung nach UI-Framework, dazu, wie ein in WPF gehostetes anderes Framework (oder in einem anderen Framework gehostetes WPF) nicht automatisch skaliert, sowie zu Testüberlegungen in gemischten DPI-Umgebungen. ↩ ↩2
-
Microsoft Learn, Layout Considerations for the WindowsFormsHost Element. Dazu, wie WindowsFormsHost zwischen den beiden Koordinatensystemen, DIPs und physischen Pixeln, umrechnet, und dazu, dass die Skalierung nur so weit reicht, wie das gehostete Windows-Forms-Steuerelement es unterstützt. ↩
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
High-DPI-Unterstützung in WinForms — Warum die UI auf 4K-Monitoren verschwimmt oder zerbricht, und wie Sie das praktisch beheben
Dieser Artikel ordnet die Gründe, warum WinForms-Apps auf 4K-Monitoren und bei 150 % Skalierung verschwimmen oder ihr Layout zerbricht, a...
Entra-ID-Authentifizierung in WinForms/WPF-Apps integrieren — Eine praxistaugliche Architektur mit MSAL.NET und dem WAM-Broker
Ein praxisnaher Blick auf die Integration der Entra-ID-Authentifizierung (früher Azure AD) in WinForms/WPF-Desktop-Apps: das Public-Clien...
Datum, Uhrzeit und Zeitzonen in Business-Apps — Von den Fallstricken von DateTime über das UTC-Speicherprinzip bis zum Testdesign
Zeitstempel verschieben sich nach einer Servermigration um neun Stunden; nur beim Auslandsbüro springt das Datum auf den Vortag zurück – ...
Infobereich-Symbole und Toast-Benachrichtigungen in Windows-Apps — Fallstricke von NotifyIcon und die richtige AppNotification-Wahl
Ein praktischer Leitfaden dafür, eine geschäftliche Windows-Anwendung im Infobereich (System Tray) resident zu halten und den Benutzer üb...
Mehrsprachigkeit für WinForms/WPF-Anwendungen ── resx, Satelliten-Assemblies und Kulturumschaltung in der Praxis
Dieser Artikel behandelt die Mehrsprachigkeit von Windows-Desktopanwendungen aus Praxissicht: den Unterschied zwischen CurrentCulture und...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
UI-Threading und Timer
WPF-/WinForms-UI-Thread, asynchrone Abläufe, Dispatcher und Timer-Entscheidungen.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Ist WPF automatisch DPI-bewusst?
- Ja, bis zu einem gewissen Grad: WPF layoutet die UI in geräteunabhängigen Einheiten (DIPs, 1/96 Zoll), und seine Rendering-Pipeline skaliert automatisch auf die System-DPI, sodass es sich ohne jede Erklärung als System DPI Aware verhält und auf dem primären Monitor scharf wirkt. Was die Automatisierung nicht abdeckt, ist das Nachverfolgen von Monitoren mit unterschiedlicher DPI (Per-Monitor-Unterstützung), Bitmap-Bilder, die beim Skalieren weiterhin verschwimmen, Subpixel-Unschärfe bei dünnen Linien und gemischte Inhalte wie WindowsFormsHost, die außerhalb des Renderings von WPF liegen. Fast jede Beschwerde nach dem Muster „aber WPF sollte doch DPI-resilient sein“ geht auf einen dieser vier Punkte zurück.
- Warum verschwimmt mein WPF-Fenster, wenn ich es auf einen anderen Monitor verschiebe?
- Weil die App noch System Aware ist: Sie zeichnet mit der DPI des primären Monitors zum Zeitpunkt der Anmeldung, und auf jedem Monitor mit anderer DPI streckt das Betriebssystem das gesamte Fenster als Bitmap — besonders unschön bei nicht ganzzahligen Verhältnissen wie 125 % zu 150 %. Die Behebung ist Per-Monitor-DPI-Unterstützung, die bei WPF unter .NET Framework 4.6.2+ (und unter .NET) größtenteils darauf hinausläuft, sie im Anwendungsmanifest zu erklären; das Framework übernimmt dann automatisch WM_DPICHANGED, Größenänderung, Neu-Layout und Neuzeichnen. Sie erfordert Windows 10 1607 oder höher, und die PerMonitorV2-Unterstützung von WPF im Speziellen erfordert .NET Framework 4.8 oder höher.
- Wie behebe ich unscharfe 1px-Linien und -Rahmen in WPF?
- Setzen Sie zuerst UseLayoutRounding="True" auf dem Wurzel-Window — es rundet Layoutergebnisse auf ganzzahlige Pixel und pflanzt sich durch den visuellen Baum fort — und wenden Sie dann gezielt SnapsToDevicePixels="True" auf alles an, was noch unscharf ist, was Kanten zur Renderzeit an Pixelgrenzen einrastet. Beide sind standardmäßig aus. Die zugrunde liegende Ursache ist Subpixel-Positionierung: Layouten in DIPs bedeutet, dass eine 1-DIP-Linie bei 125 % 1,25 physischen Pixeln entspricht, und Kanten, die zwischen Pixel fallen, werden per Anti-Aliasing zu Halbtransparenz. Das kann sogar bei 96 DPI passieren, wenn eine sternförmig bemessene Grid-Unterteilung auf 0,5 px landet.
- Warum sind in meiner WPF-App nur die Symbole unscharf, während Text scharf bleibt?
- Text wird als Vektorschrift gerendert und skaliert sauber, doch einem Image zugewiesene Bitmaps werden beim Hochskalieren interpoliert — ein 16x16-Symbol wird bei 150 % zu 24x24, und die standardmäßige lineare Interpolation von WPF macht kleine Bilder merklich weich. Die erste Wahl sind Vektor-Assets (Path, Geometry, DrawingImage oder Symbolschriften), die bei jeder DPI ohne Umschaltcode scharf rendern. Für Assets, die zwangsläufig Bitmaps bleiben müssen, bereiten Sie mehrere Auflösungen vor und schalten Sie je nach DPI um — in OnDpiChanged, falls Sie Per-Monitor sind, oder einmalig beim Start über VisualTreeHelper.GetDpi, falls Sie System Aware bleiben —, und nutzen Sie RenderOptions.BitmapScalingMode als Abhilfe, wenn Sie keine Assets ergänzen können.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.