High-DPI-Unterstützung in WinForms — Warum die UI auf 4K-Monitoren verschwimmt oder zerbricht, und wie Sie das praktisch beheben
· Go Komura · WinForms, High DPI, Windows, .NET, C#, .NET Framework, Pflege von Altsystemen, UI, Technische Beratung
„Nach dem Wechsel auf einen neuen Laptop wirkt der Text in unserer Businessanwendung verschwommen und schwer lesbar.“ „Als wir sie an einen 4K-Monitor angeschlossen haben, überlappten sich Schaltflächen und Beschriftungen.“ „Die Berichtsvorschau zerbricht nur bei der 125-%-Einstellung des Kunden.“ In den letzten Jahren ist diese Art von Anfrage jedes Mal sprunghaft gestiegen, wenn Unternehmen ihre PCs ausgetauscht haben. Auflösungen jenseits von Full HD sowie eine Bildschirmskalierung von 125–200 % sind inzwischen auch auf Business-PCs Standard geworden, und in der Ära von 96 DPI / 100 % Bildschirmdarstellung entwickelte WinForms-Apps lassen sich so nicht mehr angenehm darstellen. Unter den Beratungen zur Pflege alter WinForms-Apps ist dies heute wohl das mit Abstand häufigste Thema.
Was die Sache schwierig macht, ist, dass die Symptome verstreut wirken — „verschwommen“, „kaputt“, „nur ein Teil ist zu klein“. Tatsächlich fallen die Ursachen in nur eine Handvoll Kategorien, und sobald man weiß, welches Symptom zu welcher Ursache gehört, lässt sich sowohl klären, wie man es behebt, als auch, wie weit man gehen sollte. Dieser Artikel ordnet die High-DPI-Unterstützung für WinForms-Apps (sowohl .NET als auch .NET Framework), ausgehend davon, wie die DPI-Skalierung von Windows und die DPI-Bewusstseinsmodi funktionieren, über die Konfigurationsmethoden und klassische Fallstricke bis hin zu einer stufenweisen Reaktionsstrategie — alles aus praxisnaher Sicht.
1. Zuerst das Fazit
- Dass eine alte App in einer High-DPI-Umgebung verschwimmt, ist kein Bug — es ist eine Entlastungsmaßnahme des Betriebssystems (DPI-Virtualisierung). Windows lässt die DPI-unbewusste App bei 96 DPI rendern und streckt anschließend das entstandene Bitmap, sodass das Layout nicht zerbricht, aber unscharf wirkt.1
- Umgekehrt ist „scharf, aber kaputt“ die gegenteilige Situation: die App hat DPI-Bewusstheit erklärt, aber das Layout ist nicht nachgezogen. Da Unschärfe und Kaputtheit entgegengesetzte Grundursachen haben, klären Sie zuerst, mit welcher Sie es zu tun haben, bevor Sie eine Behebung versuchen.
- Der erste Schritt zur Behebung besteht darin, herauszufinden, in welchem DPI-Bewusstseinsmodus (Unaware / System Aware / Per-Monitor V2) Ihre App gerade läuft. Prüfen Sie, ob er über das Manifest, app.config oder einen API-Aufruf erklärt wird — oder gar nicht erklärt ist.2
- Die Konfigurationsmethode unterscheidet sich je Generation. Verwenden Sie für .NET (Core 3.1 bis .NET 8) die Projektdatei oder
Application.SetHighDpiMode; für .NET Framework 4.7 und später app.config; für 4.6 und früher ist es die realistische Obergrenze, über das Manifest immerhin bis System Aware zu gehen.34 - Öffnen Sie den Designer immer auf einem 100-%-Monitor (96 DPI). Öffnet und speichert man ein Formular bei 150 %, wird
AutoScaleDimensionsumgeschrieben — ein klassischer Vorfall, der das Layout für das gesamte Team zerstört.5 - Vollständige Unterstützung (Per-Monitor V2) erfordert echten Aufwand. Ein praktischer, zweistufiger Ansatz — „System Aware, damit der primäre Monitor scharf ist“ als erste Stufe und vollständige PMv2-Unterstützung als zweite Stufe — erlaubt es, die Investition an die verbleibende Lebensdauer der App und ihre Einsatzumgebung anzupassen.
- Als Notlösung, wenn interne Behebungen nicht machbar sind oder die Zeit fehlt, lohnt es sich, das für Endbenutzer verfügbare Override „High-DPI-Einstellungen“ über Eigenschaften → Kompatibilität der exe zu kennen — es erleichtert den Umgang mit Support-Anfragen erheblich.6
2. Warum es verschwimmt — Wie die DPI-Virtualisierung funktioniert
Die Bildschirmskalierung von Windows wird als Multiplikator ausgedrückt, wobei 96 DPI 100 % entsprechen. 125 % = 120 DPI, 150 % = 144 DPI, 200 % = 192 DPI. Auf einem 27-Zoll-4K-Monitor (3840×2160) wäre Text bei 100 % zu klein, weshalb Windows standardmäßig etwa 150 % empfiehlt. Die Verbreitung hochauflösender Displays bedeutet also unmittelbar die Verbreitung von Umgebungen, die mit etwas anderem als 96 DPI laufen.
Das Problem ist, dass viele ältere Desktop-Apps unter der Annahme geschrieben wurden, „der Bildschirm hat immer 96 DPI“. Koordinaten, Schriftarten und Symbole sind allesamt in Pixeln fest codiert, sodass alles physisch auf zwei Drittel seiner beabsichtigten Größe schrumpft, wenn man es unverändert in einer 150-%-Umgebung rendert. Um damit umzugehen, belügt Windows Apps, die keine DPI-Bewusstheit erklärt haben, mit der Aussage „der Bildschirm hat 96 DPI“, und zeigt dann das, was die App — in dem Glauben, bei 96 DPI zu sein — zeichnet, als gestrecktes Bitmap an. Dies ist die DPI-Virtualisierung (Bitmap-Streckung).1
Mit diesem Mechanismus im Kopf lassen sich Symptome und Ursachen sauber zuordnen. Nutzen Sie diese Tabelle für Ihre erste Einordnung.
| Symptom | Ursache | Zustand |
|---|---|---|
| Alles ist gleichmäßig verschwommen/unscharf; Layout ist nicht kaputt | DPI-Virtualisierung (das Betriebssystem streckt ein Bitmap) | DPI-unbewusst (Unaware). Sicher, aber unschön |
| Text ist scharf, aber Steuerelemente überlappen sich oder werden abgeschnitten | DPI-Bewusstheit wurde erklärt, aber das Layout ist nicht nachgezogen | Halb fertige DPI-Unterstützung. Das ist das Ziel der Nachbesserung |
| Scharf auf dem primären Monitor, unscharf beim Verschieben auf einen zweiten Monitor | System Aware (DPI beim Start auf den primären Monitor festgelegt) | Funktioniert wie vorgesehen. Entscheiden, ob zu PMv2 gewechselt wird |
| Textgröße ist in Ordnung, aber Symbole/Bilder sind klein oder grob | Bitmap-Assets sind noch für 96 DPI gebaut | Bild-Assets noch nicht aktualisiert (Abschnitt 6) |
| Nur ein bestimmter Bildschirm oder ein bestimmtes Steuerelement ist kaputt | Layout mit festen Koordinaten, eigenes Zeichnen, Drittanbieter-Steuerelemente | Ziel individueller Nachbesserung (Abschnitt 6) |
Wichtig ist: Eine App, die lediglich „verschwommen“ ist, ist eigentlich nicht kaputt — die Entlastungsmaßnahme des Betriebssystems funktioniert korrekt. Nachbesserung bedeutet, diese Entlastungsmaßnahme abzuschalten und zu erklären: „Ich kümmere mich selbst um die Skalierung“ (also den DPI-Bewusstseinsmodus anzuheben), was bedeutet, dass in dem Moment, in dem Sie diese Erklärung abgeben, jedes Layoutproblem zu Ihrer eigenen Verantwortung wird. Beratungsanfragen wie „wir sind schnell mal auf PMv2 umgestiegen, und es wurde noch schlimmer“ entstehen genau aus dieser Struktur.
3. Die DPI-Bewusstseinsmodi einordnen
Apps erklären dem Betriebssystem pro Prozess (genauer, ab Windows 10 pro oberstem Fenster), wie weit sie DPI selbst handhaben können. Es gibt effektiv vier Modi.16
| Modus | Eingeführt in | Von der App wahrgenommene DPI | Bei Monitorwechsel / DPI-Änderung | Praktische Position in WinForms |
|---|---|---|---|---|
| Unaware | — | Immer 96 | Betriebssystem streckt ein Bitmap (verschwommen) | Standard, wenn nichts erklärt ist. Verschwommen, aber bricht nicht |
| System Aware | Vista | Bei der Anmeldung auf die DPI des primären Monitors festgelegt | Betriebssystem streckt (verschwommen) auf jedem anderen Monitor als dem primären, oder nach einer Änderung | Über alle Generationen hinweg nutzbar. Die erste Wahl für eine pragmatische Behebung |
| Per-Monitor (V1) | 8.1 | Die DPI des Monitors, auf dem sich das Fenster befindet | Nur eine Benachrichtigung an das oberste Fenster; Skalierung liegt vollständig in Eigenregie | Keine Framework-Unterstützung und wenig praktischer Nutzen. Nicht wählen |
| Per-Monitor V2 | 10 (1703) | Die DPI des Monitors, auf dem sich das Fenster befindet | Auch untergeordnete Fenster werden benachrichtigt; das Betriebssystem skaliert den Nicht-Client-Bereich automatisch | Die erste Wahl für vollständige Unterstützung. Verfügbar in .NET Framework 4.7+ / .NET |
Der Unterschied zwischen Per-Monitor V1 und V2 ist in der Praxis entscheidend. V1 ist ein blanker Win32-Mechanismus nach dem Motto „Sie erhalten eine Benachrichtigung, den Rest erledigen Sie selbst“ — es gibt keinen Grund, ihn aus WinForms zu verwenden. Bei V2 skaliert das Betriebssystem den Nicht-Client-Bereich (Titelleiste, Bildlaufleisten, Menüs usw.) automatisch, und die WinForms-seitige Framework-Unterstützung (siehe unten) ist ebenfalls um V2 herum aufgebaut. Gehen Sie den Weg über Per-Monitor, ist V2 die einzig echte Wahl.6
Es gibt außerdem einen Modus namens GDI-Skalierung (DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, Windows 10 1809+). Die App selbst bleibt Unaware, aber über GDI gezeichneter Text und Formen werden vom Betriebssystem separat auf Vektorebene hochskaliert — eine verbesserte Form der Bitmap-Streckung.6 Da dies möglicherweise Textunschärfe reduziert, ohne eine einzige Zeile Code zu ändern, ist es als Notlösung für Apps, die nicht nachgebessert werden können, im Hinterkopf zu behalten (Abschnitt 4.3).
Beachtenswert ist außerdem, dass seit Windows 10 1607 ein Mixed-Mode existiert, der es unterschiedlichen obersten Fenstern innerhalb desselben Prozesses erlaubt, gleichzeitig unter verschiedenen Bewusstseinsmodi zu laufen — zum Beispiel „der Hauptbildschirm läuft als PMv2, während ein Dialog, der noch nicht behoben werden konnte, Unaware bleibt und vom Betriebssystem gestreckt wird“. Diese stufenweise Migration wird auf Win32-Ebene unterstützt.7 Es ist nichts, das man aus WinForms heraus beiläufig nutzen würde, aber die dahinterstehende Entwurfsphilosophie — „Sie müssen nicht jeden Bildschirm auf einmal beheben“ — verbindet sich direkt mit der stufenweisen Strategie in Abschnitt 7.
4. Konfiguration je Generation — richtig gemacht
Wie Sie einen DPI-Bewusstseinsmodus erklären, hängt von der Generation Ihrer App ab. Machen Sie das falsch, verschwenden Sie Zeit mit „ich habe es konfiguriert, aber es wirkt nicht“ — deshalb gehe ich das generationsweise durch.
4.1 WinForms unter .NET (Core 3.1 bis .NET 8)
.NET stellt Application.SetHighDpiMode bereit, und das aus der Vorlage erzeugte ApplicationConfiguration.Initialize() (.NET 6 und später) ruft dies für Sie auf. Der Standardwert ist SystemAware.3 Die empfohlene Konfigurationsmethode erfolgt über die Projektdatei, die auch der Visual-Studio-Designer liest.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<!-- SystemAware (Standard) / PerMonitorV2 / DpiUnaware / DpiUnawareGdiScaled -->
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
</PropertyGroup>
</Project>
Ein Vorbehalt: ApplicationHighDpiMode in der Projektdatei und ApplicationConfiguration.Initialize() sind Mechanismen, die mit .NET 6 eingeführt wurden.3 In .NET-Core-3.1- / .NET-5-Projekten hat das Setzen dieser Eigenschaft keine Wirkung; rufen Sie stattdessen Application.SetHighDpiMode direkt im Code auf, wie unten gezeigt (dasselbe gilt, wenn Sie eine ältere Main-Variante verwenden, die ApplicationConfiguration.Initialize() nicht aufruft). In beiden Fällen müssen Sie es aufrufen, bevor auch nur ein einziges Fenster erzeugt wird.
[STAThread]
static void Main()
{
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
.NET 6 und später haben die Skalierung von Container-Steuerelementen und MDI-Kindfenstern unter PMv2 verbessert und lösen damit viele Probleme, die bis .NET 5 bestanden — etwa, dass sich Steuerelemente beim Verschieben eines Fensters von einem 200-%-Monitor auf einen 100-%-Monitor verschieben.3 Wenn Sie ernsthaft in die PMv2-Unterstützung investieren wollen, ist .NET nach meiner Erfahrung eindeutig ein leichterer Kampf als .NET Framework. Material zur Frage, ob eine Migration weg von .NET Framework sinnvoll ist, finden Sie in „Checkliste vor der Migration: .NET Framework zu .NET“.
4.2 .NET Framework 4.7 und später
.NET Framework hat die High-DPI-Unterstützung in 4.7 deutlich gestärkt — verbesserte Steuerelement-Skalierung, DPI-Änderungsereignisse (die DpiChanged-Familie), die DeviceDpi-Eigenschaft und mehr. Sie ist jedoch Opt-in: Sie greift erst, wenn Sie die folgenden zwei Dinge zusammen setzen.4
Erklären Sie zunächst die Windows-10-Kompatibilität im Manifest (ohne dies sind die High-DPI-Funktionen von 4.7 selbst nicht aktiviert).
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows-10-Kompatibilitätserklärung -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
Erklären Sie als Nächstes den DPI-Bewusstseinsmodus in System.Windows.Forms.ApplicationConfigurationSection in app.config.
<configuration>
<System.Windows.Forms.ApplicationConfigurationSection>
<add key="DpiAwareness" value="PerMonitorV2" />
<!-- Wenn einige Bildschirme ihre Skalierung bereits selbst handhaben, können Sie einzelne Funktionen abschalten -->
<!-- <add key="EnableWindowsFormsHighDpiAutoResizing" value="false" /> -->
</System.Windows.Forms.ApplicationConfigurationSection>
</configuration>
Bestätigen Sie außerdem, dass Application.EnableVisualStyles() zu Beginn von Main aufgerufen wird. Hier ist ein Vorbehalt zu beachten: Der ältere Ansatz, <dpiAware> / <dpiAwareness> im Manifest zu schreiben, überschreibt die app.config-Einstellung, weshalb die offizielle Anleitung davon abrät, beides zusammen in 4.7-WinForms zu verwenden.4 Wenn Sie eine App, die für System Aware zuvor <dpiAware>true</dpiAware> im Manifest stehen hatte, auf 4.7+PMv2 aktualisieren, räumen Sie zunächst diese Manifest-Erklärung auf. „Ich habe es in app.config gesetzt, aber es wirkt nicht“ hat fast immer diese Ursache.
4.3 .NET Framework 4.6 und früher, sowie gemischte VB6/MFC-Codebasen
WinForms in 4.6 und früher besitzt keinen Framework-Code, der PMv2 unterstützt, sodass eine erzwungene Erklärung bedeutet, jeden einzelnen Layoutbruch selbst beheben zu müssen. Die realistische Obergrenze für diese Generation ist die Erklärung von System Aware über das Manifest. Das Manifest ist der empfohlene Mechanismus für Erklärungen auf Betriebssystemebene; das gemeinsame Schreiben von <dpiAware> (ab Vista) und <dpiAwareness> (ab Windows 10 1607) stellt sicher, dass es sowohl unter älteren als auch neueren Betriebssystemversionen wie beabsichtigt interpretiert wird.2
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">system</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
Mit System Aware greift die automatische Skalierung von WinForms (Abschnitt 5) einmalig, passend zur DPI des primären Monitors beim Start, sodass die Darstellung auf dem primären Monitor scharf wirkt. Ist bei einem Formular AutoScaleMode.Font korrekt gesetzt, bringt diese Erklärung allein einen oft in einen recht vorzeigbaren Zustand. Beachten Sie, dass es auch einen Weg gibt, dies zur Laufzeit über APIs wie SetProcessDpiAwareness zu erklären, doch dies lässt sich nach der Fenstererzeugung nicht mehr ändern, und die Manifest-Erklärung ist der offiziell empfohlene Ansatz.8 Dieselbe Überlegung gilt für Apps mit gemischtem VB6- oder MFC-Code — das Manifest der exe bestimmt den Modus für den gesamten Prozess (MFC selbst hat keine automatische Skalierungsunterstützung, weshalb nach der Erklärung von System Aware eine Prüfung Bildschirm für Bildschirm unerlässlich ist; für den Umgang mit Altbeständen dieser Generation siehe auch „Was ist MFC unter Windows“).
Ich stelle außerdem eine Notlösung auf Benutzerseite vor, für den Fall, dass eine Nachbesserung selbst nicht möglich ist: Rechtsklick auf die exe → Eigenschaften → Registerkarte Kompatibilität → „Hohe DPI-Einstellungen ändern“, womit sich das DPI-Skalierungsverhalten dieser App überschreiben lässt. Das Setzen von „Verhalten bei hoher DPI-Skalierung überschreiben“ auf „System“ erzwingt DPI-Virtualisierung (bricht nicht, aber verschwimmt); „System (erweitert)“ wendet die in Abschnitt 3 erwähnte GDI-Skalierung an, wodurch nur über GDI gerenderter Text scharf wird.6 Es ist kein Allheilmittel, aber es lohnt sich, es in Ihr Support-Playbook aufzunehmen, als etwas, das Sie einem Kunden am Telefon durchgeben können, wenn er „sofort“ eine Behebung braucht.
5. AutoScaleMode und die Designer-Falle
Getrennt vom DPI-Bewusstseinsmodus besitzt WinForms einen eigenen, formularbezogenen automatischen Skalierungsmechanismus. Ohne dessen Verständnis skaliert der Wechsel zu System Aware die Dinge nicht korrekt.
So funktioniert es. Zur Entwurfszeit hält jedes Formular (ContainerControl) die Skalierungsgrundlage in AutoScaleMode fest und speichert den Basiswert der Umgebung, in der es entworfen wurde, in AutoScaleDimensions. Zur Laufzeit wird dies mit dem Wert der aktuellen Umgebung (CurrentAutoScaleDimensions) verglichen, und bei einem Unterschied werden alle Kindsteuerelemente entsprechend skaliert.9 Es handelt sich also um einen Mechanismus, der den „Unterschied zwischen Entwurfs- und Laufzeitumgebung“ ausgleicht, wobei der Basiswert im Code (Designer.cs) fest verankert wird.
Für AutoScaleMode gibt es effektiv zwei Wahlmöglichkeiten.
| AutoScaleMode | Grundlage | Eigenschaften |
|---|---|---|
| Font (empfohlen, Standard) | Die Schriftgröße des Formulars | Da sich die tatsächliche Größe der Systemschriftart mit steigender DPI ändert, dient dies zugleich der DPI-Nachverfolgung. Verfolgt auch Änderungen der Schrifteinstellung des Benutzers |
| Dpi | Bildschirm-DPI | Skaliert nur proportional zur DPI. Geeignet für grafiklastige Bildschirme |
| None | — | Automatische Skalierung deaktiviert. Apps mit fest codierten 96-DPI-Werten sind oft so eingestellt |
Im Zweifel Font verwenden. Ein Vorbehalt: Es ist offiziell dokumentiert, dass das Mischen unterschiedlicher Modi zwischen einem Basisformular und einem abgeleiteten Formular unerwartete Ergebnisse liefert.9 Für Apps, die Formularvererbung nutzen, ist der erste Schritt eine Prüfung, um sicherzustellen, dass alle Formulare denselben Modus verwenden.
Und nun zur Hauptfalle. Da AutoScaleDimensions ein Mechanismus ist, der „den Wert der Umgebung, in der entworfen wurde“, festhält, wird Designer.cs’s AutoScaleDimensions auf den 150-%-Wert umgeschrieben, wenn Sie den Visual-Studio-Designer auf einem 150-%-Monitor öffnen und das Formular speichern (bei Font-Modus wird aus 6F, 12F etwa 9F, 18F und so weiter). Koordinaten und Größen werden ebenfalls als 1,5-fache Werte gespeichert. Baut und startet ein Teammitglied in einer 100-%-Umgebung dieses Formular dann, erscheint das Ganze geschrumpft, und ein Diff-Review zeigt einen riesigen Unterschied, bei dem sich die Koordinaten jedes Steuerelements geändert haben. „Ein neues Teammitglied mit einem hochauflösenden Laptop hat ein Formular angefasst, und das Layout im gesamten Repository ist kaputtgegangen“ ist ein klassischer Vorfall in High-DPI-Beratungen.
Die Behebung ist im Prinzip eine einzige Regel: Öffnen Sie den Designer immer bei 100 % (96 DPI). Visual Studio selbst ist eine DPI-bewusste App, doch der WinForms-Designer (für .NET Framework) ist es nicht, weshalb das Öffnen auf einem High-DPI-Monitor eine gelbe Infoleiste auslöst, die auffordert, „Visual Studio mit 100-%-Skalierung neu zu starten“.5 Machen Sie es zur Teampolitik, dieser Leiste zu folgen, im DPI-unbewussten Modus neu zu starten, das Formular zu bearbeiten und danach zurück in den normalen Modus zu wechseln. Sie können dies auch über die Befehlszeile mit devenv /noScale starten. Beachten Sie, dass Visual Studio 2022 17.8 und später bei .NET-6+-Projekten das Setzen von <ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware> in der Projektdatei erlaubt, wodurch nur die Designer-Registerkarte DPI-unbewusst läuft, ohne ganz VS neu zu starten (für .NET-Framework-Projekte steht dies nicht zur Verfügung).5
Als Möglichkeit, die Prüfung zu systematisieren, ist es einfach und wirksam, CI oder einen Pre-Commit-Hook Diffs ablehnen zu lassen, bei denen AutoScaleDimensions in Designer.cs vom erwarteten Wert abweicht. Es ist billiger, einen Vorfall am Commit-Tor zu stoppen, als ihn nachträglich zu beheben.
6. Häufige Bruchmuster und wie man sie behebt
Heben Sie den DPI-Bewusstseinsmodus an (oder stehen kurz davor), fallen die Stellen, die kaputtgehen, im Allgemeinen in folgende Muster.
| Was kaputtgeht | Ursache | Behebung |
|---|---|---|
| Steuerelemente überlappen sich oder werden abgeschnitten | Layout mit festen Koordinaten, fester Größe | Durch Anchor/Dock, TableLayoutPanel / FlowLayoutPanel ersetzen. AutoSize verwenden |
| Symbole/Bilder sind klein oder grob | 96-DPI-Bitmaps direkt eingefügt | Bilder in mehreren Auflösungen vorbereiten und je nach DPI umschalten. Wo möglich, aus einem größeren Quellbild verkleinern |
| Eigenes Zeichnen (Diagramme, Grafiken, Berichtsvorschau) | Pixelwerte direkt in Graphics fest codiert | Koordinaten, Linienbreiten und Schriftarten anhand von DeviceDpi skalieren |
| DataGridView-Zeilen sind zu eng | In Pixeln angegebenes RowHeight usw. | AutoSizeRowsMode verwenden, oder DPI-skalierte Werte setzen |
| ToolStrip-Symbole sind winzig | Fest auf 16×16 | ImageScalingSize je nach DPI setzen |
| Nur ein bestimmtes Drittanbieter-/ActiveX-Steuerelement ist kaputt | Das Steuerelement selbst ist DPI-unbewusst | Auf die DPI-bewusste Version des Herstellers aktualisieren. Gibt es keine, ist das die Obergrenze des Erreichbaren |
Das Ersetzen von Layouts macht den Großteil des Aufwands aus. Umgekehrt benötigen Bildschirme, die bereits mit TableLayoutPanel und Dock gebaut wurden, auch nach dem Anheben des DPI-Bewusstseinsmodus nur sehr wenig Arbeit. Die Regel, dass neue Bildschirme künftig immer von Anfang an mit Layout-Panels gebaut werden, reduziert künftige technische Schulden.
Für die Skalierung eigenen Zeichnens besteht der Grundansatz darin, für 96 DPI entworfene Werte anhand von Control.DeviceDpi (.NET Framework 4.7+ / .NET) umzurechnen.
public partial class ChartPanel : Panel
{
// Einen für 96 DPI entworfenen Wert auf die aktuelle DPI umrechnen
private int Scale(int value96) => value96 * DeviceDpi / 96;
protected override void OnPaint(PaintEventArgs e)
{
using var pen = new Pen(Color.Navy, Scale(2));
e.Graphics.DrawRectangle(pen,
Scale(16), Scale(16), Scale(320), Scale(120));
}
// Unter PMv2 ändert sich DeviceDpi, wenn das Fenster zwischen Monitoren wechselt, daher ein Neuzeichnen auslösen
protected override void OnDpiChangedAfterParent(EventArgs e)
{
base.OnDpiChangedAfterParent(e);
Invalidate();
}
}
Bis einschließlich System Aware ist die DPI beim Start festgelegt, sodass die Einführung von Scale allein ausreicht. Unter PMv2 ändert sich DeviceDpi jedoch jedes Mal, wenn das Fenster zwischen Monitoren wechselt, sodass Sie einen Entwurf brauchen, der zwischengespeicherte Schriftarten, Bilder und Layoutwerte als Reaktion auf die DpiChanged-Ereignisfamilie neu aufbaut.4 „Wie viele Bildschirme mit eigenem Zeichnen hat diese App?“ bestimmt unmittelbar die Aufwandsschätzung für die PMv2-Unterstützung.
Drittanbieter-Steuerelemente und ActiveX/OCX sind der Faktor, der die Obergrenze dieser Nachbesserung setzt. Ist ein Steuerelement selbst nicht DPI-bewusst, wird dieser Bildschirm niemals vollständig behoben werden können, egal wie sehr sich der Host anstrengt. Prüfen Sie den Support-Status des Herstellers, und entscheiden Sie bei ActiveX-Steuerelementen, bei denen ein Update unwahrscheinlich ist, mithilfe der Entscheidungstabelle in „Behalten, Wrappen oder Ersetzen von ActiveX/OCX“, wie Sie damit umgehen, bevor Sie Ihr High-DPI-Unterstützungsziel festlegen.
7. Eine stufenweise Reaktionsstrategie — eine Entscheidungstabelle dafür, wie weit man gehen sollte
Angesichts all dessen lassen sich die Nachbesserungsstufen in drei Ebenen ordnen. Jede App vollständig PMv2-konform zu machen, ist das technische Ideal, doch angesichts von Nachbesserungsbudgets und App-Lebensdauern ist ein pragmatischer Kompromiss oft die richtige Entscheidung.
| (1) Nichts tun (Unaware) | (2) Zu System Aware wechseln | (3) Vollständige PMv2-Unterstützung | |
|---|---|---|---|
| Erscheinungsbild | In jeder Umgebung verschwommen (bricht nicht) | Scharf auf dem primären Monitor. Verschwommen auf sekundären Monitoren oder nach einer DPI-Änderung | Scharf auf jedem Monitor |
| Hauptarbeit | Keine | Über Manifest/Konfiguration erklären + AutoScaleMode prüfen + Darstellung jedes Bildschirms verifizieren | (2) + vollständige Layoutprüfung + DPI-Unterstützung für eigenes Zeichnen und Bild-Assets + DpiChanged handhaben |
| Aufwand | Null | Klein bis mittel (überwiegend Prüfarbeit proportional zur Bildschirmzahl) | Groß (überwiegend Behebung von Brüchen; Drittanbieter-Steuerelemente können das Erreichbare begrenzen) |
| Gut geeignet für | Für die Ausmusterung innerhalb weniger Jahre vorgesehen; Benutzer tolerieren es | Interne Apps, zentriert auf feste Desktops / einen einzelnen Monitor. Als erste Stufe | Gemischte Nutzung von Laptop + externem Monitor. Langlebige Flaggschiff-Apps. An Kunden verteilte Produkte |
Es gibt drei Achsen für die Entscheidung: die verbleibende Lebensdauer der App (wie viele weitere Jahre wird sie genutzt), die Einsatzumgebung (nutzen alle denselben festen Desktop-Aufbau, reicht System Aware praktisch aus; sind Kombinationen aus Laptop und externem Monitor üblich, wird die Unschärfe von System Aware bei jedem Monitorwechsel die Benutzer unzufrieden lassen) und das Nachbesserungsbudget. Der empfohlene Ansatz besteht darin, zunächst (2) als Standard für alle Apps auszurollen und dann anhand des Umfangs der dabei gefundenen Brüche sowie der durch Drittanbieter-Steuerelemente auferlegten Einschränkungen zu entscheiden, ob — und welche Bildschirme — zu (3) fortgeschritten werden sollen. (2) dreht sich hauptsächlich um „erklären + prüfen“, birgt ein geringes Ausfallrisiko und verbessert dennoch das Erlebnis des Benutzers spürbar.
Ein Wort auch zum Testen. High-DPI-Probleme lassen sich auf der eigenen Maschine eines Entwicklers oft nicht reproduzieren, richten Sie deshalb bewusst folgende Umgebungen zur Verifikation ein.1
- Schließen Sie zwei Monitore mit unterschiedlicher Skalierung an (z. B. ein 150-%-Laptop-Bildschirm plus ein 100-%-externer Monitor) und verschieben Sie Fenster hin und her
- Tauschen Sie den primären Monitor aus und melden Sie sich erneut an, bevor Sie starten (die System-DPI wird bei der Anmeldung festgelegt, sie wechselt also nicht ohne erneute Anmeldung)
- Ändern Sie die Skalierungseinstellung, während die App läuft
- Verbinden Sie sich per Remotedesktop von einem High-DPI-Client aus (RDP bringt die clientseitige DPI mit, daher ist „auf der Serverkonsole ist alles in Ordnung, aber über RDP bricht es“ ein häufiger Bericht)
Erhalten Sie einen Bericht, dass etwas „nur bei 125 % kaputtgeht“, ist die tatsächliche Reproduktion dieser Skalierungseinstellung und ein direkter Blick darauf meiner Erfahrung nach letztlich der schnellste Weg zur Diagnose. Da sich die Skalierungseinstellung auch in einer virtuellen Maschine ändern lässt, beschleunigt es die Einordnung, wenn Verifikationsumgebungen für 100 / 125 / 150 / 200 % bereitstehen.
8. Zusammenfassung
In einer High-DPI-Umgebung ist „verschwommen“ die Entlastungsmaßnahme des Betriebssystems (DPI-Virtualisierung); „kaputt“ ist halb fertige DPI-Unterstützung — haben Sie diese Zuordnung erst einmal verinnerlicht, können Sie vom Symptom rückwärts zu Ursache und Behebung arbeiten. Die Konfiguration ist generationsspezifisch: für .NET das ApplicationHighDpiMode der Projektdatei; für .NET Framework 4.7 und später app.config (nicht mit dem dpiAware des Manifests kombinieren); für 4.6 und früher immerhin bis System Aware über das Manifest. Daneben verhindern die Vereinheitlichung auf AutoScaleMode.Font und die Durchsetzung der Regel, den Designer immer bei 100 % zu öffnen, unglamourös, aber am wirkungsvollsten Vorfälle.
Von dort aus hängt es von den drei Stufen in Abschnitt 7 ab, wie weit man geht. Machen Sie zuerst über System Aware den primären Monitor scharf, und wechseln Sie dann zu PMv2, wenn Einsatzumgebung und verbleibende Lebensdauer der App das rechtfertigen — dieser zweistufige Ansatz ist das, was wir bei realen Nachbesserungsprojekten tatsächlich empfehlen. Ist der Zeitpunkt gekommen, das UI-Framework selbst zu überdenken (WPF ist von Grund auf mit starker DPI-Unterstützung entworfen), ziehen Sie auch „Wie man zwischen WinForms, WPF und WinUI wählt“ als Material für diese Entscheidung heran. Wenn Sie unsicher sind, wie weit sich Ihre eigene App realistisch beheben lässt, können wir helfen, angefangen bei einer Inventarisierung Ihrer Bildschirme und Steuerelemente.
Verwandte Artikel
- Wie man zwischen WinForms, WPF und WinUI wählt — Eine Entscheidungstabelle
- Behalten, Wrappen oder Ersetzen von ActiveX/OCX — Eine Entscheidungstabelle
- Checkliste vor der Migration: .NET Framework zu .NET
- Was ist MFC unter Windows — was es ist und wie man heute damit umgeht
Verwandte Beratungsbereiche
Die Komura Soft LLC übernimmt die High-DPI-Nachbesserung für Legacy-WinForms-/MFC-Apps (Bestandsaufnahme, Festlegung der passenden Unterstützungsstufe, Layout-Nachbesserung), die Ursachenermittlung bei Darstellungsproblemen nach PC-Austauschen sowie Beratung zur UI-Modernisierung.
- Technische Beratung & Design-Review
- Nutzung von Altbeständen & Migrationsunterstützung
- Windows-App-Entwicklung
- Kontakt
Referenzlinks
-
Microsoft Learn, High DPI Desktop Application Development on Windows. Hintergrund zur DPI-Skalierung, zum Verhalten jedes DPI-Bewusstseinsmodus, dazu, wie DPI-unbewusste Apps per Bitmap gestreckt werden, und zu Testüberlegungen für gemischte DPI-Umgebungen. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Setting the default DPI awareness for a process. Zur Erklärung über die Manifest-Elemente dpiAware / dpiAwareness, deren Vorrangbeziehung, und dazu, warum die Erklärung per API nicht empfohlen wird. ↩ ↩2
-
Microsoft Learn, What’s new in Windows Forms .NET 6. Zum Bootstrapping über ApplicationConfiguration.Initialize, zur ApplicationHighDpiMode-Einstellung der Projektdatei (Standard SystemAware), und zu den PerMonitorV2-Skalierungsverbesserungen in .NET 6. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, High DPI support - Windows Forms. Zum Inhalt der High-DPI-Erweiterungen von .NET Framework 4.7, zum app.config-Abschnitt System.Windows.Forms.ApplicationConfigurationSection (DpiAwareness=PerMonitorV2), dazu, warum die Erklärung im Manifest abgeraten wird, da sie app.config überschreibt, sowie zur DpiChanged-Ereignisfamilie plus DeviceDpi. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Fix DPI display issues in Windows Form Designer. Dazu, dass der WinForms-Designer DPI-unbewusst ist, zur Infoleiste auf High-DPI-Monitoren, die zu einem Neustart bei 100-%-Skalierung auffordert, zu devenv /noScale, und zu ForceDesignerDPIUnaware für .NET 6+. ↩ ↩2 ↩3
-
Microsoft Learn, DPI_AWARENESS_CONTEXT handle. Definitionen und Verhalten der Kontexte Unaware / System Aware / Per-Monitor / Per-Monitor V2 / UNAWARE_GDISCALED (GDI-Skalierung, Windows 10 1809+). ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Mixed-Mode DPI Scaling and DPI-aware APIs. Zum Mischen von DPI-Bewusstseinsmodi pro oberstem Fenster über SetThreadDpiAwarenessContext sowie zu DPI-bewussten APIs wie GetDpiForWindow. ↩
-
Microsoft Learn, SetProcessDpiAwareness function. Zum Setzen der Standard-DPI-Bewusstheit eines Prozesses per API, dazu, warum stattdessen die Manifest-Erklärung empfohlen wird, und dazu, dass es sich nach dem Setzen nicht mehr ändern lässt. ↩
-
Microsoft Learn, Automatic form scaling - Windows Forms. Dazu, wie sich die automatische Skalierung über AutoScaleMode / AutoScaleDimensions / CurrentAutoScaleDimensions verhält, und dazu, dass das Mischen von Font- und Dpi-Modus nicht unterstützt wird. ↩ ↩2
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
High-DPI-Unterstützung in WPF — Warum es trotz „eigentlich DPI-bewusst“ immer noch verschwimmt und durchblutet, und wie Sie es beheben
WPF layoutet die UI in DIPs (1/96 Zoll) und ist von Anfang an System DPI Aware, doch das Verschieben eines Fensters auf einen Monitor mit...
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 – ...
Warum EXCEL.EXE-Prozesse nach C#-Excel-COM-Automatisierung bestehen bleiben — Muster zur Referenzfreigabe und die Ersatzentscheidung
Ein praxisnaher Blick darauf, warum EXCEL.EXE-Prozesse nach der Automatisierung von Excel aus C# über Microsoft.Office.Interop.Excel best...
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...
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.
- Warum sieht meine WinForms-App auf einem 4K-Monitor verschwommen aus?
- Unschärfe auf einem hochauflösenden Display ist kein Bug — sie ist eine Entlastungsmaßnahme des Betriebssystems namens DPI-Virtualisierung. Hat eine App keine DPI-Bewusstheit erklärt, sagt Windows ihr, der Bildschirm habe 96 DPI, lässt sie in dieser Größe rendern und streckt anschließend das entstandene Bitmap auf die tatsächliche Skalierung, sodass das Layout nicht zerbricht, aber alles unscharf wirkt. Die Behebung bedeutet, selbst einen DPI-Bewusstseinsmodus zu erklären, und in dem Moment, in dem Sie das tun, wird jedes Layoutproblem zu Ihrer eigenen Verantwortung — deshalb ist „wir sind schnell mal auf Per-Monitor V2 umgestiegen, und es wurde noch schlimmer“ eine häufige Geschichte.
- Welchen DPI-Bewusstseinsmodus sollte eine WinForms-App erklären?
- Für eine pragmatische erste Stufe: System Aware. Die App passt sich beim Start an die DPI des primären Monitors an und wirkt dort scharf, funktioniert über alle Framework-Generationen hinweg, und in Kombination mit einer korrekten AutoScaleMode.Font-Einstellung bringt sie einen mit überwiegend Prüfarbeit oft in einen vorzeigbaren Zustand. Per-Monitor V2 ist die erste Wahl für vollständige Unterstützung — scharf auf jedem Monitor —, erfordert aber .NET Framework 4.7+ oder .NET sowie echten Nachbesserungsaufwand bei Layout, eigenem Zeichnen und Bildmaterial. Per-Monitor V1 hat keine Framework-Unterstützung und sollte überhaupt nicht gewählt werden.
- Wie aktiviere ich High-DPI-Unterstützung in .NET gegenüber .NET-Framework-WinForms?
- Das hängt von der Generation ab. Setzen Sie unter .NET 6 und später ApplicationHighDpiMode in der Projektdatei oder rufen Sie Application.SetHighDpiMode auf, bevor auch nur ein Fenster erzeugt wird (Standard ist SystemAware). Erklären Sie unter .NET Framework 4.7 und später Windows-10-Kompatibilität im Manifest und setzen Sie DpiAwareness im System.Windows.Forms.ApplicationConfigurationSection von app.config — und erklären Sie nicht zusätzlich dpiAware im Manifest, denn das überschreibt app.config und ist die übliche Ursache für „ich habe es konfiguriert, aber es wirkt nicht“. Unter 4.6 und früher ist die Erklärung von System Aware über das Manifest die realistische Obergrenze.
- Warum ist das Layout unserer Formulare kaputtgegangen, nachdem jemand sie in Visual Studio bearbeitet hat?
- Öffnet und speichert man ein Formular im WinForms-Designer auf einem über 100 % skalierten Monitor, wird AutoScaleDimensions in Designer.cs auf den skalierten Wert umgeschrieben — bei 150 % wird aus 6F,12F etwa 9F,18F —, und die Koordinaten jedes Steuerelements werden mit dem 1,5-fachen Wert gespeichert, sodass das Formular für Teammitglieder in 100-%-Umgebungen geschrumpft erscheint. Die Regel lautet, den Designer immer bei 100 % (96 DPI) zu öffnen: folgen Sie der gelben Infoleiste, die Visual Studio auf High-DPI-Monitoren anzeigt, verwenden Sie devenv /noScale, oder setzen Sie bei .NET-6+-Projekten ForceDesignerDPIUnaware in der Projektdatei. Lässt man CI Diffs ablehnen, bei denen sich AutoScaleDimensions unerwartet geändert hat, stoppt man den Vorfall bereits am Commit-Tor.
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.