Dunkler Modus und Kontrastthemen in Windows-Apps — Dunkle DWM-Titelleisten, Systemthema-Nachführung in WinForms/WPF und Zeichnen unter hohem Kontrast

· Aktualisiert am: · · Dunkler Modus, Kontrastthemen, Hoher Kontrast, DWM, WinForms, WPF, Windows 11, Barrierefreiheit, Geschäftsanwendungen, Windows

Änderungsverlauf (Erstfassung, veröffentlicht am 2. Sep 2026)
Erstveröffentlichung

„Wir haben die Büro-PCs auf Windows 11 umgestellt, und die Mitarbeiter, die den dunklen Modus nutzen, beschweren sich, dass nur bei unserer Geschäftsanwendung die Titelleiste grellweiß ist.“ „Ein Mitarbeiter mit Sehschwäche hat ein Kontrastthema eingeschaltet, und die Statusanzeige auf dem Auftragserfassungsbildschirm ist verschwunden.“ Beide Beschwerden hören wir in den letzten ein bis zwei Jahren häufiger.

Die erste kommt von der Farbeneinstellung in Windows 11, die zweite von den Einstellungen zur Barrierefreiheit, aber für Entwickler sehen sie wie dasselbe Problem aus: Die App folgt dem Thema nicht. Und tatsächlich ist die Grundlage der Korrektur gemeinsam. Farben nicht fest einprogrammieren; die Systemeinstellung lesen, Änderungen bemerken und neu zeichnen. Diese drei Punkte.

Der vorherige Artikel „Einführung in die Barrierefreiheit von Windows-Apps“ behandelte, wie Bildschirmleser eine App vorlesen (UI Automation) sowie die Grundlagen von Benennung, Tastaturbedienung und Farbe. Er berührte das Folgen von Kontrastthemen, behandelte den Hell/Dunkel-Farbmodus selbst aber nicht. Dieser Artikel ist das Schwesterstück. Er verbindet die zwei Achsen Farbmodus (Hell/Dunkel) und Kontrastthemen auf Implementierungsebene in dieser Reihenfolge: Zeichnen der Titelleiste durch DWM (Desktop Window Manager), Folgen des Systemthemas in WinForms/WPF und Zeichnen unter einem Kontrastthema. Zielgruppe sind Entwickler, die Geschäftsanwendungen in WinForms, WPF oder Win32 bauen und warten. Voraussetzungen sind Windows 11 (dunkle Titelleisten erfordern Build 22000 oder höher) und WinForms/WPF unter .NET 9/10 (unter .NET Framework 4.8 und .NET 8 müssen einige Teile von Hand implementiert werden). Der Schwierigkeitsgrad ist mittel.

Der Ablauf dieses ArtikelsDie Struktur dieses Artikels, der nacheinander die zwei Themenachsen, warum Fenster standardmäßig hell sind, dunkle DWM-Titelleisten, den Erkennungs- und Nachführungsmechanismus, die WinForms- und WPF-Implementierungen, das Zeichnen unter einem Kontrastthema, das Festlegen einer Richtlinie und die Prüfung verbindetDie zwei Themenachsen klärenWarum die Vorgabe hell istDunkle DWM-TitelleisteErkennung und NachführungWinForms/WPF-ImplementierungZeichnen unter einem KontrastthemaRichtlinie festlegen und prüfen

Abbildung 1: Dieser Artikel führt in einer Linie von der Klärung der Themen über Mechanismen, Implementierung, Kontrastthemen und Prüfung.

1. Zuerst das Fazit

  • Windows hat zwei Themenachsen. Hell/Dunkel (der Farbmodus) unter Einstellungen > Personalisierung > Farben, und Einstellungen > Barrierefreiheit > Kontrastthemen. Letzteres ist eine Palette, die auf ein Kontrastverhältnis von grob 7:1 oder höher eingeschränkt ist, und etwas anderes als Hell/Dunkel. Der dunkle Modus ist nicht verfügbar, solange ein Kontrastthema aktiv ist. Die Rangfolge der Erkennung ist „Kontrastthema, dann Hell/Dunkel“.12
  • Die Titelleiste einer bestehenden App bleibt weiß, weil das die Kompatibilitätsvorgabe ist. Windows hat keine Möglichkeit festzustellen, ob eine App den dunklen Modus unterstützt, und behandelt deshalb jedes Fenster standardmäßig als hell.3
  • Was die Titelleiste dunkel macht, ist DWMWA_USE_IMMERSIVE_DARK_MODE (Wert 20) über DwmSetWindowAttribute. Übergeben Sie ein BOOL TRUE, und der Rahmen wird dunkel gezeichnet, wenn das System dunkel ist. Die dokumentierte Unterstützung ist Windows 11 Build 22000 oder höher.43
  • Lesen Sie den aktuellen Modus mit UISettings.GetColorValue und empfangen Sie Änderungen über ColorValuesChanged. Das offizielle Verfahren von Microsoft lautet: Wenn der Vordergrund (die Standardtextfarbe) hell ist, ist der Modus dunkel. Das Ereignis ist nicht garantiert auf dem UI-Thread, also führen Sie vor dem Neuzeichnen auf die UI zurück.35
  • WinForms erhielt Application.SetColorMode in .NET 9, und in .NET 10 war es nicht mehr experimentell. Rufen Sie es mit SystemColorMode.System vor Application.Run auf. Es hat drei Einschränkungen: nur Windows 11, während eines Kontrastthemas deaktiviert, und keine Nachführung von Einstellungsänderungen, während die App läuft.672
  • WPF erhielt das Fluent-Thema und ThemeMode in .NET 9. ThemeMode="System" folgt dem System und steuert auch das Abdunkeln des Fensters. Die Manipulation aus Code ist jedoch auch in .NET 10 noch experimentell (WPF0001), und die Fluent-Stile sind „in progress“. Bleiben Sie beim klassischen Thema, tauschen Sie helle und dunkle ResourceDictionaries, die über DynamicResource referenziert werden.8910
  • Unter einem Kontrastthema bilden Sie Farben auf die richtigen Systemfarbenpaare ab, lassen Bilder hinter Text weg und zeichnen mehrfarbige Grafiken in den zwei Vordergrund- und Hintergrundfarben. Erkennen Sie mit SPI_GETHIGHCONTRAST (SystemInformation.HighContrast in WinForms, SystemParameters.HighContrast in WPF); Benachrichtigungen sind WM_SYSCOLORCHANGE / WM_THEMECHANGED (SystemEvents.UserPreferenceChanged in .NET).111213
  • Unterstützung des dunklen Modus ist kein Ersatz für Barrierefreiheitsunterstützung. Eine dunkle Palette braucht weiterhin ein Kontrastverhältnis von 4,5:1, und Informationen durch mehr als nur Farbe zu vermitteln ist unter jedem Thema erforderlich.1415

In einem Satz: Themenunterstützung bedeutet, Farben an einer Stelle zu sammeln, die Systemeinstellung zu lesen, Änderungen zu bemerken und neu zu zeichnen; unter einem Kontrastthema aber die Systemfarbenpaare vollständig zu übernehmen.

In der Abbildung kennzeichnet eine durchgezogene Linie eine stets geltende Beziehung und eine gestrichelte Linie eine bedingte (die Bedingungen stehen bei jeder Beziehung auf der Detailseite). Die vollständige Liste der Beziehungen (28 insgesamt, mit Beleg und Sicherheitsgrad) und die Definitionen der wichtigsten Konzepte sind auf der Detailseite der Wissenskarte (auf Japanisch) zusammengestellt. Daten: JSON-LD / Turtle

2. „Thema“ hat zwei Achsen — Hell/Dunkel und Kontrastthemen

2.1. Hell/Dunkel (Farbmodus)

Der Farbmodus unter Einstellungen > Personalisierung > Farben in Windows ist die Einstellung, die die Helligkeit von Vordergrund und Hintergrund über das Betriebssystem und alle Apps entscheidet. Die Microsoft-Dokumentation definiert Hell als „einen dunklen Vordergrund auf hellem Hintergrund“ und Dunkel als „einen hellen Vordergrund auf dunklem Hintergrund“ und fügt hinzu, dass Vordergrund hier „die Standardtextfarbe“ bedeutet. Im dunklen Modus ist der Vordergrund (Text) hell und der Hintergrund dunkel.3

Die Einstellung liegt in der Registrierung unter HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize als DWORD-Werte AppsUseLightTheme (der App-Modus) und SystemUsesLightTheme (der Modus von Windows selbst) und ist in Microsofts Einstellungsreferenz aufgeführt.16 Wie weiter unten beschrieben, ist der kanonische Weg, sie aus einer App zu lesen, jedoch die WinRT-Klasse UISettings.

2.2. Kontrastthemen (hoher Kontrast)

Ein Kontrastthema, gewählt unter Einstellungen > Barrierefreiheit > Kontrastthemen, verwendet eine Palette, die auf ein Kontrastverhältnis von grob 7:1 oder höher eingeschränkt ist, und existiert für Benutzer, die eine starke visuelle Trennung von Vordergrund und Hintergrund brauchen. Windows 11 hat vier mitgeliefert, Aquatic, Desert, Dusk und Night sky, und der Benutzer kann nicht nur eines davon wählen, sondern auch Hintergrund, Text, Hyperlink, deaktivierten Text, ausgewählten Text und Schaltflächenfarben einzeln bearbeiten. Linke Alt-Taste + linke Umschalttaste + Druck schaltet ein Kontrastthema schnell um, und Aquatic wird angewendet, wenn keines ausgewählt wurde.1

Die Microsoft-Dokumentation sagt klar: „verwechseln Sie Kontrastthemen nicht mit hellen und dunklen Themen“. Hell/Dunkel verwendet eine breite Palette und ist nicht auf maximalen Kontrast optimiert.1 Und der wichtige Punkt ist, dass der dunkle Modus nicht verfügbar ist, solange ein Kontrastthema aktiv ist. Application.SetColorMode in WinForms stellt während eines Kontrastthemas keinen dunklen Modus bereit, und RequestedTheme in XAML wird vom System überschrieben.217

2.3. Die Rangfolge

Die Implementierung in einer App folgt deshalb dieser Reihenfolge. Entscheiden Sie zuerst, ob ein Kontrastthema aktiv ist; wenn ja, überlassen Sie die Farben vollständig den Systemfarben. Wenn nicht, wählen Sie entweder die helle oder die dunkle Palette.

Die zwei Themenachsen und die RangfolgeDie Entscheidungsreihenfolge, in der die App die Systemfarbenpaare vollständig übernimmt, wenn ein Kontrastthema aktiv ist, und sonst den Hell/Dunkel-Farbmodus liest und die App-Palette wähltJaNeinHellDunkelKontrastthema aktiv?Den Systemfarbenpaaren überlassenFarbmodus?Helle PaletteDunkle PaletteDunkler Modus ist nicht verfügbar

Abbildung 2: Stellen Sie die Kontrastthema-Entscheidung voran, und wählen Sie die helle oder dunkle Palette nur, wenn kein Kontrastthema aktiv ist.

3. Warum bestehende Apps im dunklen Modus weiß bleiben

Ein Fenster besteht aus zwei Bereichen: dem Nichtclientbereich, gebildet aus Titelleiste, Rahmen und den Schaltflächen der Titelleiste, und dem Clientbereich, den die App zeichnet. Seit Windows Vista wird der Nichtclientbereich von DWM (Desktop Window Manager) zusammengesetzt und gezeichnet, und die App gibt Attribute der Zeichnungsweise über DwmSetWindowAttribute an.18

Die Microsoft-Dokumentation erklärt offen, warum bestehende Apps weiß bleiben. „Windows weiß nicht, ob eine Anwendung den dunklen Modus unterstützen kann, und nimmt aus Gründen der Abwärtskompatibilität an, dass sie es nicht kann.“ Frameworks wie WinUI und das Windows App SDK behandeln den dunklen Modus nativ, Win32-Apps unterstützen den dunklen Modus aber in der Regel nicht, daher gibt Windows ihnen standardmäßig eine helle Titelleiste.3

Die zwei Bereiche eines Fensters und wer sie zeichnetDer Nichtclientbereich aus Titelleiste und Rahmen wird von DWM gezeichnet, und der Clientbereich wird von der App oder dem UI-Framework gezeichnet, daher ist Unterstützung des dunklen Modus in beiden nötigFenster der obersten EbeneNichtclientbereich (Titelleiste, Rahmen)Clientbereich (der Fensterinhalt)Von DWM zusammengesetzt und gezeichnetVon der App oder dem Framework gezeichnetAngewiesen über DwmSetWindowAttributeDie eigene Palette der App

Abbildung 3: DWM zeichnet die Titelleiste und die App den Inhalt, daher braucht die Unterstützung des dunklen Modus sowohl eine Anweisung an DWM als auch die Palette der App.

Zwei Folgen ergeben sich. Erstens muss die App DWM ausdrücklich bitten, um die Titelleiste dunkel zu machen. Zweitens wird durch das Bitten nur die Titelleiste dunkel; den Clientbereich muss die App selbst neu zeichnen. Die Dokumentation sagt auch, dass „zur vollständigen Unterstützung des dunklen Modus die gesamte Oberfläche der App dem dunklen Thema folgen muss“, und merkt an, dass der offizielle Leitfaden nur Erkennung und Titelleiste abdeckt, nicht wie der Clientbereich neu gezeichnet wird.3 Eine App mit schwarzer Titelleiste und weißem Inhalt wirkt weniger natürlich als eine, die durchgängig weiß bleibt.

Wie die Vorgabe hell wirdWindows weiß nicht, ob eine App den dunklen Modus unterstützt, daher ist die Vorgabe aus Kompatibilität hell, und nur wenn die App TRUE über das DWM-Attribut übergibt, zeichnet es den Rahmen gemäß der dunklen SystemeinstellungNeinJaWindows kann nicht feststellen, ob die App es unterstütztVorgabe ist hell, aus KompatibilitätHat die App TRUE übergeben?Immer ein heller RahmenGezeichnet gemäß der Systemeinstellung

Abbildung 4: Weil Windows nicht weiß, ob die App es unterstützt, ist die Vorgabe hell, und es folgt dem System nur, wenn die App das ausdrücklich sagt.

4. Die dunkle DWM-Titelleiste — DwmSetWindowAttribute

4.1. DWMWA_USE_IMMERSIVE_DARK_MODE

Das Attribut, das die Titelleiste dunkel macht, ist DWMWA_USE_IMMERSIVE_DARK_MODE. Die Enumeration DWMWINDOWATTRIBUTE beschreibt es so: „Erlaubt, dass der Fensterrahmen dieses Fensters in Farben des dunklen Modus gezeichnet wird, wenn die Systemeinstellung für den dunklen Modus aktiviert ist. Aus Kompatibilitätsgründen sind alle Fenster unabhängig von der Systemeinstellung standardmäßig im Hellmodus. Der Parameter pvAttribute zeigt auf einen Wert vom Typ BOOL. TRUE, um den dunklen Modus für das Fenster zu respektieren, FALSE, um immer den Hellmodus zu verwenden. Dieser Wert wird ab Windows 11 Build 22000 unterstützt.“4

Mit anderen Worten: TRUE bedeutet nicht „mach es dunkel“; es ist die Erlaubnis, „du darfst es dunkel zeichnen, wenn das System dunkel ist“. Wenn die App bereit ist, ihren Clientbereich dunkel zu zeichnen, reicht das Übergeben von TRUE, damit die Titelleiste der Systemeinstellung folgt. Umgekehrt, wenn die App so entworfen ist, dass sie immer im Hellmodus angezeigt wird (die später beschriebene Richtlinie „festes Hell“), ist das Belassen der Vorgabe FALSE in Ordnung.

Der C++-Code im offiziellen Leitfaden hat die folgende Form. Er enthält sogar den Schritt, Wert 20 selbst zu definieren, für ältere SDKs, deren Header die Konstante nicht haben.3

#include <dwmapi.h>
#pragma comment(lib, "dwmapi.lib")

#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif

// Windows 11 (Build 22000) oder höher? Setzt ein Manifest voraus, das supportedOS
// für Windows 10 oder höher deklariert (ohne eines wird die Version auf Windows 8 abgerundet)
bool IsWindows11OrGreater()
{
    OSVERSIONINFOEXW osvi{ sizeof(osvi) };
    osvi.dwMajorVersion = 10;
    osvi.dwMinorVersion = 0;
    osvi.dwBuildNumber = 22000;
    DWORDLONG mask = 0;
    VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
    VER_SET_CONDITION(mask, VER_MINORVERSION, VER_GREATER_EQUAL);
    VER_SET_CONDITION(mask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
    return ::VerifyVersionInfoW(
        &osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER, mask) != FALSE;
}

// honorDarkMode = true: die Titelleiste darf dunkel gezeichnet werden, wenn das System dunkel ist
void ApplyTitleBarTheme(HWND hwnd, bool honorDarkMode)
{
    if (!IsWindows11OrGreater())
    {
        // Dokumentierte Unterstützung ist Build 22000 oder höher. Darunter nicht aufrufen und der Vorgabe (hell) folgen
        LogInfo(L"DWMWA_USE_IMMERSIVE_DARK_MODE is not documented for this OS build; keeping the default light frame");
        return;
    }
    BOOL value = honorDarkMode ? TRUE : FALSE;
    HRESULT hr = ::DwmSetWindowAttribute(
        hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
    if (FAILED(hr))
    {
        // Ein Fehler auf einem unterstützten Betriebssystem ist unnormal. Nicht still schlucken; den HRESULT aufzeichnen und sichtbar machen
        LogWarning(L"DwmSetWindowAttribute(DWMWA_USE_IMMERSIVE_DARK_MODE) failed: 0x%08X", hr);
    }
}

Ein Wort dazu, warum der Aufruf an die Betriebssystemversion gekoppelt ist. Die dokumentierte Unterstützung ist Windows 11 Build 22000 oder höher.4 Berichte, dass derselbe Wert unter Windows 10 funktioniert, sind nicht selten, aber die Anzeige einer Geschäftsanwendung sollte nicht von undokumentiertem Verhalten abhängen. Wenn das Design „den Aufruf versuchen und bei Fehler aufgeben“ lautet, wird die Titelleiste dunkel, sobald der Aufruf unter Windows 10 zufällig gelingt, und das auf undokumentiertem Verhalten. Unterhalb von Build 22000 nicht aufrufen und der dokumentierten Vorgabe einer hellen Titelleiste folgen; auf einem unterstützten Betriebssystem den HRESULT eines Fehlers protokollieren und sichtbar machen. Das ist alles. Im Internet kursiert außerdem ein älteres Verfahren mit Wert 19 und eine Technik, die Ordinale von uxtheme.dll aufruft, um die Common Controls abzudunkeln; beides sind undokumentierte APIs, und niemand garantiert sie, wenn ein Update ihr Verhalten ändert.

4.2. Wann aufrufen — solange der HWND lebt, und jedes Mal, wenn er neu erzeugt wird

DwmSetWindowAttribute wird auf einem HWND aufgerufen, muss also laufen, nachdem das Fensterhandle erzeugt wurde. Und ein WinForms-Formular kann sein Handle neu erzeugen lassen, etwa wenn sich ShowInTaskbar ändert. Der neue HWND nach der Neuerzeugung trägt kein Attribut, daher ist der Ort des Aufrufs nicht der Konstruktor, sondern die Stelle, die jedes Mal läuft, wenn ein Handle erzeugt wird: OnHandleCreated in WinForms, SourceInitialized in WPF.

Wann DwmSetWindowAttribute aufgerufen wirdDas DWM-Attribut nach dem Erzeugen des Fensterhandles setzen, es auf dem neuen Handle erneut setzen, wenn das Handle neu erzeugt wird, und es erneut setzen, wenn eine Themenwechselbenachrichtigung eintrifftHandle neu erzeugtThemenwechselbenachrichtigungHWND erzeugtDas DWM-Attribut setzenAngezeigt

Abbildung 5: Das DWM-Attribut ist an den HWND gebunden, setzen Sie es also bei jeder Erzeugung und jeder Neuerzeugung erneut.

Das P/Invoke in WinForms sieht so aus (zum sicheren Schreiben von DllImport siehe „Win32-APIs sicher aus C# aufrufen“).

using System.Runtime.InteropServices;

public partial class MainForm : Form
{
    private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;

    [DllImport("dwmapi.dll")]
    private static extern int DwmSetWindowAttribute(
        IntPtr hwnd, int attribute, ref int value, int size);

    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        ApplyTitleBarTheme();
    }

    private void ApplyTitleBarTheme()
    {
        // Dokumentierte Unterstützung ist Build 22000 oder höher. Darunter nicht aufrufen und der Vorgabe (hell) folgen.
        // Diese Prüfung funktioniert auch unter .NET Framework. Unter .NET Framework wird die Version jedoch ohne
        // ein Manifest, das supportedOS für Windows 10 oder höher deklariert, auf Windows 8 abgerundet
        // (unter .NET 5 oder höher funktioniert auch OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
        if (Environment.OSVersion.Version < new Version(10, 0, 22000))
        {
            _logger.LogInformation("Dark title bar is not documented for this OS build; keeping the default light frame");
            return;
        }
        // 1 (TRUE) = darf dunkel gezeichnet werden, wenn das System dunkel ist. 0 (FALSE) = immer hell
        int honorDarkMode = 1;
        int hr = DwmSetWindowAttribute(
            Handle, DWMWA_USE_IMMERSIVE_DARK_MODE, ref honorDarkMode, sizeof(int));
        if (hr < 0)
        {
            _logger.LogWarning("DwmSetWindowAttribute failed: 0x{Hr:X8}", hr);
        }
    }
}

Diesen Code brauchen Apps unter .NET 8 oder früher, .NET Framework und Win32/MFC. Wenn Sie Application.SetColorMode in WinForms unter .NET 9 oder höher oder ThemeMode in WPF unter .NET 9 oder höher verwenden, übernimmt das Framework das Abdunkeln des Fensters (die ThemeMode-Dokumentation stellt fest, dass es „auch die Anwendung von Hintergrundmaterial und dunklem Modus auf das Fenster steuert“).10 Zweimal aufzurufen schadet nicht, verwischt aber, wer verantwortlich ist, wählen Sie also das eine oder das andere.

In WPF steht der HWND bei SourceInitialized fest. Holen Sie das Handle von WindowInteropHelper.19

using System.Windows.Interop;

public partial class MainWindow : Window
{
    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        var hwnd = new WindowInteropHelper(this).Handle;
        TitleBarTheme.Apply(hwnd, honorDarkMode: true); // der Rumpf ist das oben gezeigte P/Invoke
    }
}

4.3. Titelleistenfarbe, Textfarbe, Rahmenfarbe und Hintergrundmaterial

Windows 11 fügte Attribute hinzu, die die Titelleistenfarbe selbst angeben, über die binäre Wahl dunkel oder hell hinaus.

Attribut Wert Bedeutung Unterstützter Build
DWMWA_USE_IMMERSIVE_DARK_MODE 20 Den Rahmen dunkel zeichnen, wenn das System dunkel ist (BOOL) 22000
DWMWA_BORDER_COLOR 34 Farbe des Fensterrahmens (COLORREF). DWMWA_COLOR_NONE entfernt den Rahmen 22000
DWMWA_CAPTION_COLOR 35 Titelleistenfarbe (COLORREF) 22000
DWMWA_TEXT_COLOR 36 Farbe des Titeltexts (COLORREF) 22000
DWMWA_SYSTEMBACKDROP_TYPE 38 Vom System gezeichnetes Hintergrundmaterial (Mica oder Acrylic) 22621

Für die drei Farbattribute stellt das Übergeben von DWMWA_COLOR_DEFAULT (0xFFFFFFFF) die Systemvorgabe wieder her. Beachten Sie, dass für die Rahmenfarbe „es in der Verantwortung der App liegt, die Farbe als Reaktion auf Zustandsänderungen wie Fensteraktivierung zu ändern“.4 Das Hintergrundmaterial wird mit der Enumeration DWM_SYSTEMBACKDROP_TYPE angegeben; unter Windows 11 entspricht DWMSBT_MAINWINDOW Mica und DWMSBT_TRANSIENTWINDOW Acrylic, aber die Dokumentation stellt fest, dass „die Wirkung des Materials sich in künftigen Windows-Versionen ändern kann“.20

Überlegen Sie sorgfältig, wo das in einer Geschäftsanwendung hingehört. Sobald Sie die Titelleiste in einer Markenfarbe lackieren, sind Sie verantwortlich dafür, den Kontrast des Titeltexts und der Schaltflächen der Titelleiste auf dieser Farbe zu garantieren. Zu den zwei Zuständen dunkel und hell multiplizieren sich die Kombinationen mit aktiv und inaktiv. Für die meisten Geschäftsanwendungen ist die richtige Antwort „der Systemvorgabe folgen (einfach Wert 20 auf TRUE setzen)“, und eine Markenfarbe ist eine Option, wenn sie wirklich gebraucht wird.

Wie die Titelleistenfarbe entschieden wirdDer Systemvorgabe zu folgen erfordert nur, Wert 20 auf TRUE zu setzen, aber eine Markenfarbe zu lackieren macht die App verantwortlich für den Kontrast von Text und Schaltflächen der Titelleiste und für die Verwaltung aktiver und inaktiver Farben, und das Wiederherstellen übergibt DWMWA_COLOR_DEFAULTDer Systemvorgabe folgenEine Markenfarbe lackierenTitelleistenfarbe?Einfach Wert 20 auf TRUE setzenDie Farbattribute angeben (34 bis 36)Kontrast von Text und Schaltflächen selbst garantierenAktiv/inaktiv selbst verwaltenMit COLOR_DEFAULT wiederherstellen

Abbildung 6: Die Wahl einer Markenfarbe verschiebt die Verantwortung für Kontrast und Zustandsverwaltung auf die App, daher ist für die meisten Geschäftsanwendungen das Folgen der Vorgabe die richtige Antwort.

5. Das Systemthema erkennen und ihm folgen — Lesen, Bemerken, Neuzeichnen

Die Aufgabe, den Clientbereich dem Thema folgen zu lassen, zerfällt in drei Teile: die aktuelle Einstellung lesen, Änderungen bemerken und neu zeichnen.

Die drei Schritte von Erkennung und NachführungDie Schleife, den aktuellen Farbmodus beim Start mit UISettings zu lesen, Änderungen über Benachrichtigungen wie ColorValuesChanged zu bemerken, auf den UI-Thread zurückzuführen und die App-Palette neu zu zeichnenLesen: UISettings.GetColorValueNeuzeichnen: die Palette erneut anwendenBemerken: ColorValuesChangedAuf den UI-Thread zurückführenDas DWM-Attribut ebenfalls erneut setzen

Abbildung 7: Beim Start lesen, über Benachrichtigungen bemerken, auf den UI-Thread zurückführen und neu zeichnen: Diese Schleife ist das Gerüst der Themennachführung.

5.1. Lesen — UISettings und „Wenn der Vordergrund hell ist, ist es dunkel“

Das offizielle Verfahren von Microsoft verwendet die WinRT-Klasse Windows.UI.ViewManagement.UISettings. Holen Sie die Vordergrundfarbe (die Standardtextfarbe) mit GetColorValue(UIColorType::Foreground), schätzen Sie ihre wahrgenommene Leuchtdichte mit Ganzzahlarithmetik, um zu entscheiden, ob sie „hell“ ist, und schließen Sie auf dunklen Modus, wenn der Vordergrund hell ist. Die Dokumentation merkt an, dass die Formel kein strenges Leuchtdichtemodell ist, nur eine Näherung, die zum Klassifizieren von Hell und Dunkel ausreicht.321

UISettings ist eine WinRT-Klasse, aber C#-WPF- und WinForms-Apps können sie direkt aufrufen, wenn das TargetFramework eine Windows-SDK-Version trägt, etwa net8.0-windows10.0.19041.0 (wie das funktioniert, siehe „WinRT ist COM“). Sie könnten AppsUseLightTheme auch direkt aus der Registrierung lesen, aber die Registrierung ist der Speicherort der Einstellung, kein API-Vertrag; wenn Sie sie lesen, behandeln Sie UISettings als Quelle der Wahrheit und behalten die Registrierung für die Diagnose.

Wege, den Farbmodus zu lesenDer kanonische Weg ist, die Vordergrundfarbe über WinRT UISettings zu holen und sie als hell oder dunkel zu klassifizieren; der Registrierungswert AppsUseLightTheme ist der Speicherort und sollte für die Diagnose behalten werdenGerade hell oder dunkel?UISettings.GetColorValueRegistrierung AppsUseLightThemeDie wahrgenommene Leuchtdichte des Vordergrunds beurteilenHell bedeutet dunkler ModusSpeicherort. Für die Diagnose behalten

Abbildung 8: Der kanonische Leseweg ist UISettings; die Registrierung ist nur der Speicherort der Einstellung.

5.2. Bemerken — ColorValuesChanged kommt nicht auf dem UI-Thread an

UISettings dient auch der Änderungserkennung. Das Ereignis ColorValuesChanged wird ausgelöst, wenn sich ein Farbwert ändert, und der offizielle Leitfaden nutzt dieses Ereignis, um Einstellungsänderungen nachzuführen.53 Hier gilt eine praktische Vorsicht. Dieses Ereignis ist nicht garantiert auf dem UI-Thread. Führen Sie mit WPFs Dispatcher, WinForms’ Control.Invoke oder dem SynchronizationContext, der in beiden funktioniert, auf den UI-Thread zurück, bevor Sie ein Steuerelement anfassen. Die Arbeit mit dem UI-Thread ist in „WPF/WinForms: async und der UI-Thread auf einem Blatt“ zusammengefasst.

Das Ereignis wird auch ausgelöst, wenn sich die Akzentfarbe ändert. Wenn Sie nur neu zeichnen wollen, wenn sich Hell/Dunkel ändert, bewerten Sie bei jedem Ereignis neu und benachrichtigen Sie nur, wenn das Ergebnis sich vom letzten Mal unterscheidet. Und gemäß der Rangfolge in Kapitel 2 schauen Sie nicht auf die Vordergrundhelligkeit, solange ein Kontrastthema aktiv ist. Ein Kontrastthema mit dunklem Hintergrund wie Aquatic hat einen hellen Vordergrund, sodass Helligkeit allein es als „dunkel“ falsch klassifizieren würde. Behandeln Sie das Kontrastthema als unabhängigen Zustand und entscheiden Sie es zuerst.

Die folgende Klasse sammelt diese Entscheidungsreihenfolge an einer Stelle. Der SynchronizationContext, der auf den UI-Thread zurückführt, und die Kontrastthema-Prüfung (SystemInformation.HighContrast in WinForms, SystemParameters.HighContrast in WPF; siehe Kapitel 8) werden vom Aufrufer übergeben. Achten Sie auf den Erzeugungszeitpunkt. In WinForms gibt es am Punkt von Program.Main noch keine Nachrichtenschleife und kein Control, daher ist SynchronizationContext.Current null. Übergeben Sie SynchronizationContext.Current, nachdem die Steuerelemente existieren, etwa aus dem Konstruktor des Formulars oder OnLoad. In WPF können Sie new DispatcherSynchronizationContext(Application.Current.Dispatcher) übergeben.

using Windows.UI.ViewManagement; // TargetFramework: net8.0-windows10.0.19041.0 oder höher

public enum ThemeState { Light, Dark, HighContrast }

public sealed class SystemThemeWatcher : IDisposable
{
    private readonly UISettings _settings = new();
    private readonly SynchronizationContext _ui;
    private readonly Func<bool> _isHighContrast;

    private bool _disposed;

    public ThemeState Current { get; private set; }
    public event EventHandler? Changed;

    // ui: der SynchronizationContext des UI-Threads. Übergeben Sie SynchronizationContext.Current
    //     nachdem die Steuerelemente existieren, oder einen DispatcherSynchronizationContext in WPF
    // isHighContrast: () => SystemInformation.HighContrast (WinForms)
    //                 () => SystemParameters.HighContrast (WPF)
    public SystemThemeWatcher(SynchronizationContext ui, Func<bool> isHighContrast)
    {
        _ui = ui ?? throw new ArgumentNullException(nameof(ui));
        _isHighContrast = isHighContrast ?? throw new ArgumentNullException(nameof(isHighContrast));
        Current = Read();
        _settings.ColorValuesChanged += OnColorValuesChanged;
    }

    private ThemeState Read()
    {
        // Entscheidungsreihenfolge wie in Kapitel 2: zuerst Kontrastthema. Ein Kontrastthema mit dunklem
        // Hintergrund hat einen hellen Vordergrund, sodass Helligkeit allein es als „dunkel“ falsch klassifizieren würde
        if (_isHighContrast()) return ThemeState.HighContrast;
        // Derselbe Test wie im offiziellen Leitfaden: dunkel, wenn der Vordergrund (Standardtextfarbe) hell ist
        var fg = _settings.GetColorValue(UIColorType.Foreground);
        bool isDark = (5 * fg.G + 2 * fg.R + fg.B) > 8 * 128;
        return isDark ? ThemeState.Dark : ThemeState.Light;
    }

    // Vom UI-Thread aufrufen. Kann auch von anderen Benachrichtigungspfaden wie UserPreferenceChanged aufgerufen werden
    public void Refresh()
    {
        if (_disposed) return;
        var next = Read();
        // Zwischen Hell und Dunkel nicht benachrichtigen, wenn der Zustand unverändert ist, um nur-Akzentfarben-Änderungen zu ignorieren.
        // Während eines Kontrastthemas ist das die Ausnahme: der Zustand bleibt HighContrast, auch wenn der Benutzer
        // die Farben des Themas bearbeitet, also auch bei gleichem Zustand benachrichtigen, damit die Systemfarben erneut gelesen werden
        if (next == Current && next != ThemeState.HighContrast) return;
        Current = next;
        Changed?.Invoke(this, EventArgs.Empty);
    }

    private void OnColorValuesChanged(UISettings sender, object args)
    {
        // Nicht garantiert auf dem UI-Thread, also vor dem Entscheiden und Benachrichtigen auf die UI zurückführen.
        // Ein Aufruf, der nach Dispose ankommt (bereits in der Warteschlange), wird durch das Flag in Refresh ignoriert
        _ui.Post(_ => Refresh(), null);
    }

    public void Dispose()
    {
        // Abmelden stoppt nur künftige Lieferungen; bereits an den UI-Thread gesendete Aufrufe bleiben.
        // Das Flag setzen, damit die verbleibenden Aufrufe sich selbst ignorieren (dies auf dem UI-Thread aufrufen)
        _disposed = true;
        _settings.ColorValuesChanged -= OnColorValuesChanged;
    }
}

Auf Win32-Ebene wird WM_SETTINGCHANGE an jedes Fenster der obersten Ebene gesendet, wenn sich eine Einstellung ändert,22 und in .NET kommt es als SystemEvents.UserPreferenceChanged an.23 Ein Wechsel des visuellen Stils (einschließlich des Aktivierens eines Kontrastthemas) bringt WM_THEMECHANGED,24 und eine Änderung der Systemfarben bringt WM_SYSCOLORCHANGE.25 Statt für jede Art von Benachrichtigung eine eigene Behandlung zu schreiben, rufen Sie dieselbe Routine „lesen und neu zeichnen“ auf, welche Benachrichtigung auch ankommt; das bricht schwerer, und es hat dieselbe Form wie die Kontrastthema-Nachführung aus dem vorherigen Artikel. Im Sinne des SystemThemeWatcher oben bedeutet das, Refresh() auch aus den Handlern von UserPreferenceChanged und StaticPropertyChanged aufzurufen.

Pfade und Threads der ThemenwechselbenachrichtigungColorValuesChanged von UISettings kann außerhalb des UI-Threads ankommen und wird zurückgeführt, WM_SETTINGCHANGE kommt als SystemEvents.UserPreferenceChanged an, und WM_THEMECHANGED und WM_SYSCOLORCHANGE kommen an der Fensterprozedur an. Alle laufen in derselben Neuzeichnungsroutine zusammenColorValuesChangedAuf den UI-Thread zurückführenUserPreferenceChangedLesen und neu zeichnenWM_THEMECHANGED usw.

Abbildung 9: Es gibt mehrere Benachrichtigungspfade, aber alle laufen in derselben Routine „lesen und neu zeichnen“ zusammen.

5.3. Neuzeichnen — Farben an einer Stelle sammeln

Die Voraussetzung, die das Neuzeichnen möglich macht, ist, dass Farben an einer Stelle gesammelt sind. Wenn Color.White und #FFFFFF über Formulare und XAML verstreut sind, können Sie die Stellen zum Neuzeichnen nicht aufzählen. In WinForms erzeugen Sie eine „Palette“-Klasse (zwei Instanzen, eine hell und eine dunkel) und lassen Steuerelemente ihre Farben beim Start und bei jeder Benachrichtigung daraus nehmen. In WPF sammeln Sie Farben in einem ResourceDictionary und referenzieren sie aus XAML mit DynamicResource. In WinUI ist diese Struktur von Anfang an als ThemeDictionaries vorhanden.

Die Struktur, die Farben an einer Stelle sammeltDie App hält eine helle Palette und eine dunkle Palette, und jeder Bildschirm nimmt seine Farben aus der für den aktuellen Modus gewählten Palette, sodass die Ziele eines Neuzeichnens aufgezählt werden könnenHellDunkelAktueller ModusWelche?Helle PaletteDunkle PaletteJeder Bildschirm und jedes SteuerelementVerstreutes Color.WhiteWas neu zu zeichnen ist, lässt sich nicht aufzählen

Abbildung 10: Mit der Palette an einer Stelle lässt sich ein Neuzeichnen aufzählen; mit verstreuten fest eingecodierten Farben nicht.

Diese Arbeit des „Farben-Sammelns“ zahlt sich direkt in der Kontrastthema-Unterstützung und in den später beschriebenen Kontrastverhältnisprüfungen aus. Die größten Kosten der Unterstützung des dunklen Modus sind nicht die API-Aufrufe, sondern diese Aufräumarbeit.

6. Implementierung in WinForms

6.1. .NET 9/10 — Application.SetColorMode

WinForms erhielt in .NET 9 eine vorläufige Unterstützung des dunklen Modus, und sie wurde in .NET 10 „vollständig integriert“. Application.SetColorMode akzeptiert drei Werte.67

  • SystemColorMode.Classic — die Vorgabe. Hell, wie bisher.
  • SystemColorMode.System — der Hell/Dunkel-Einstellung von Windows folgen.
  • SystemColorMode.Dark — dunkel.

Rufen Sie es vor Application.Run auf, bevor irgendein UI-Element erzeugt wird. Unter .NET 9 war es ein experimentelles Feature, daher war es ein Kompilierfehler, wenn WFO5001 nicht in der Projektdatei unterdrückt wurde; ab .NET 10 erscheint der Fehler nicht mehr.26

static class Program
{
    [STAThread]
    static void Main()
    {
        ApplicationConfiguration.Initialize();
        Application.SetColorMode(SystemColorMode.System); // vor dem Erzeugen jeglicher UI aufrufen
        Application.Run(new MainForm());
    }
}

Wenn sich der Farbmodus ändert, schaltet System.Drawing.SystemColors auf die passenden Farben um, und die Standardsteuerelemente werden entsprechend gezeichnet.6 Unter der Haube „lässt die experimentelle Eigenschaft SystemColors.UseAlternativeColorSet (SYSLIB5002) die System-KnownColor-Werte einen alternativen Farbsatz zurückgeben (derzeit die Version für den dunklen Modus)“; weil die Win32-Systemfarben selbst sich mit der Hell/Dunkel-Einstellung nicht ändern, führt .NET den alternativen Satz auf seiner Seite. Dieselbe Dokumentation sagt auch, dass wenn ein Kontrastthema aktiv ist, immer die aktuellen Windows-Farben zurückgegeben werden.27

Behalten Sie die drei Einschränkungen im Blick, die in der SetColorMode-Dokumentation stehen.2

  1. Der dunkle Farbmodus ist nur unter Windows 11 oder höher verfügbar.
  2. Der dunkle Modus ist nicht verfügbar, wenn ein Kontrastthema aktiv ist.
  3. Selbst mit SystemColorMode.System folgt die App einer Änderung der Windows-Einstellung während der Laufzeit nicht automatisch.

Die dritte erzeugt in Geschäftsanwendungen gern Supportfragen. Schreiben Sie in die benutzerseitige Dokumentation, dass der Modus durch die Windows-Einstellung beim Start entschieden wird und beim nächsten Start greift. Wenn Sie einem Wechsel zur Laufzeit unbedingt folgen müssen, brauchen Sie ein Design, das die Formulare neu erzeugt, und es lohnt sich fast nie.

Ablauf und Einschränkungen von SetColorModeSetColorMode wird vor Application.Run aufgerufen, SystemColors schaltet auf den alternativen Satz um, und die Standardsteuerelemente folgen. Es hat drei Einschränkungen: nur Windows 11, während eines Kontrastthemas deaktiviert, und keine Nachführung von Einstellungsänderungen zur LaufzeitSetColorMode (System)Vor Application.RunSystemColors schaltet auf den alternativen Satz umStandardsteuerelemente folgenNur Windows 11Während eines Kontrastthemas deaktiviertFolgt Änderungen zur Laufzeit nicht

Abbildung 11: SetColorMode greift einmal vor dem Start und hat drei dokumentierte Einschränkungen.

6.2. Selbstgezeichnete Steuerelemente und ApplyThemingImplicitly

Die Standardsteuerelemente folgen dem Farbmodus der App, aber die .NET-10-Dokumentation listet zwei Ausnahmefälle. Wenn ein Steuerelement, das Sie selbst zusammenbauen und zeichnen, Win32-Common-Controls wie Bildlaufleisten verwendet, bleiben diese hell, wenn sie nicht ausdrücklich opt-in. Umgekehrt, wenn Sie ein vorhandenes Steuerelement erben, das dem Thema folgt, und die Zeichnung vollständig selbst steuern wollen, opt-out.7

In beiden Fällen überschreiben Sie Control.CreateParams und rufen SetStyle(ControlStyles.ApplyThemingImplicitly, true/false) vor dem Lesen von base.CreateParams auf. Die Falle in dieser API ist, dass der Basisklassenkonstruktor CreateParams liest, sodass Ihr eigener Konstruktor zu spät kommt.7

public partial class GanttChartControl : Control
{
    protected override CreateParams CreateParams
    {
        get
        {
            // Vor dem Lesen von base.CreateParams setzen. Der Konstruktor kommt zu spät
            SetStyle(ControlStyles.ApplyThemingImplicitly, true);
            return base.CreateParams;
        }
    }
}
Wann ApplyThemingImplicitly gesetzt werden kannApplyThemingImplicitly wird an dem Punkt entschieden, an dem der Basisklassenkonstruktor CreateParams liest, daher muss SetStyle vor base.CreateParams innerhalb der CreateParams-Überschreibung aufgerufen werden, und der abgeleitete Konstruktor kommt zu spätBasisklassenkonstruktorLiest CreateParamsSetStyle muss bis hier geschehenKonstruktor der abgeleiteten KlasseHier aufzurufen kommt zu spät

Abbildung 12: ApplyThemingImplicitly muss entschieden sein, bevor der Basisklassenkonstruktor CreateParams liest.

Das Selbstzeichnen selbst (GDI+-Zeichnung in OnPaint) folgt dem alternativen Satz, solange es SystemColors / SystemBrushes / SystemPens verwendet. Jede Stelle, die mit Color.White malt, wird auch hier durch die oben beschriebene Palette ersetzt.

6.3. .NET Framework 4.8 und .NET 8 oder früher — „Festes Hell“ erklären

In Umgebungen ohne SetColorMode gibt es keine Standardunterstützung des dunklen Modus. Es gibt zwei Optionen.

  1. Festes Hell erklären. Lassen Sie das DWM-Attribut bei seiner Vorgabe FALSE (immer eine helle Titelleiste) und belassen Sie den Clientbereich wie er ist. Kontrastthema-Unterstützung (Kapitel 8) bleibt verpflichtend.
  2. Vollständige Unterstützung selbst implementieren. Konsolidieren Sie die Palette, lesen und folgen Sie mit UISettings, setzen Sie das DWM-Attribut auf TRUE und zeichnen Sie alles neu, einschließlich des Aussehens der Common Controls.

Option 2 endet gern bei „größtenteils dunkel, aber an Stellen hell“, weil die App die Zeichnung der Win32-Common-Controls (Bildlaufleisten, Header, Aufklapp-Schaltflächen von Bäumen und so weiter) nicht vollständig steuern kann. Wie der offizielle Leitfaden sagt, „muss die gesamte Oberfläche folgen“;3 ein halb fertiger dunkler Modus ist eine schlechtere Erfahrung als festes Hell. Für bestehende Assets wählen Sie Option 1, erklären Sie „diese App wird im Hellmodus angezeigt“ und wechseln Sie zu SetColorMode, wenn Sie nach .NET 10 migrieren. Das ist die realistische Richtlinie und die am leichtesten zu erklärende.

WinForms-Optionen nach LaufzeitumgebungUnter .NET 10 oder höher SetColorMode verwenden, unter .NET 9 dieselbe API mit unterdrücktem WFO5001, und unter .NET 8 oder früher oder .NET Framework zwischen festem Hell erklären und vollständiger Unterstützung selbst bis zu den Common Controls wählen.NET 10 oder höher.NET 9.NET 8 oder früher / .NET FrameworkEmpfohlenWenn Sie darauf vorbereitet sindLaufzeitumgebung?SetColorMode (System)SetColorMode + WFO5001 unterdrückenWas tun?Festes Hell erklärenVollständige Unterstützung selbstCommon Controls bleiben

Abbildung 13: Auf Laufzeitumgebungen ohne SetColorMode ist festes Hell erklären die realistische Vorgabe.

7. Implementierung in WPF

7.1. .NET 9/10 — Das Fluent-Thema und ThemeMode

WPF unter .NET 9 liefert ein neues Thema, das dem Fluent-Design von Windows 11 folgt, mit Unterstützung für Hell/Dunkel und die Akzentfarbe. Es gibt zwei Wege, es anzuwenden: die Eigenschaft ThemeMode setzen oder das Ressourcenwörterbuch PresentationFramework.Fluent zu MergedDictionaries hinzufügen.8

ThemeMode nimmt vier Werte, Light / Dark / System / None (die Vorgabe; das klassische Aero2-Thema). Auf Application gesetzt wirkt es auf die ganze App; auf einem Window gesetzt wirkt es nur auf dieses Fenster.8

<Application x:Class="OrderEntry.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml"
             ThemeMode="System">
</Application>

ThemeMode lädt nicht nur die Fluent-Themenwörterbücher in die Ressourcen; die Dokumentation stellt fest, dass es „auch die Anwendung von Hintergrundmaterial und dunklem Modus auf das Fenster steuert“. Mit anderen Worten: WPF übernimmt das DWM-Attribut aus Kapitel 4. ThemeMode und Resources sind außerdem so entworfen, dass sie synchron bleiben, was die Dokumentation damit erklärt, eine Inkonsistenz zu vermeiden, bei der das Fenster dunkel, die Steuerelemente darin aber hell sind.10

Was ThemeMode in WPF tutThemeMode auf System zu setzen lädt die Fluent-Themenwörterbücher passend zur Windows-Einstellung in die Ressourcen und steuert auch die Anwendung von dunklem Modus und Hintergrundmaterial auf das FensterThemeMode=SystemDie Windows-Einstellung lesenDie Fluent-Wörterbücher in die Ressourcen ladenDas Fenster abdunkeln und das Hintergrundmaterial anwendenMit Resources synchron bleiben, um Inkonsistenz zu vermeiden

Abbildung 14: ThemeMode steuert das Laden der Fluent-Wörterbücher und das Abdunkeln des Fensters gemeinsam.

Es gibt zwei Vorsichten, die Sie vor der Übernahme kennen sollten. Erstens ist das Lesen und Schreiben von ThemeMode aus Code ein experimentelles Feature, und der Zugriff erzeugt den Fehler WPF0001. Wenn Sie ihn unterdrücken, können Sie Application.Current.ThemeMode = ThemeMode.Dark schreiben, aber die API-Referenz trägt in .NET 10 weiterhin [Experimental("WPF0001")], mit dem Hinweis, dass es „in Zukunft entfernt werden kann“.810 Zweitens ist die Fluent-Stilabdeckung in .NET 10 noch „in progress“. .NET 10 fügte Stile für DatePicker, GridSplitter, GroupBox, TextBox und andere hinzu und behob HighContrast-bezogene Abstürze,9 was umgekehrt gelesen bedeutet, dass das Fluent-Thema in .NET 9 sie nicht hatte. Bevor Sie die Übernahme entscheiden, bestätigen Sie auf einer echten Maschine, dass die Steuerelemente, die Ihre Geschäftsanwendung verwendet (besonders DataGrid und Steuerelemente Dritter), unter Fluent nicht zerbrechen.

7.2. Dem Thema auf dem klassischen Thema folgen — ResourceDictionaries tauschen

In WPF ohne Fluent (oder unter .NET 8 oder früher oder .NET Framework) gibt es keine Standardunterstützung des dunklen Modus. Die Win32-Systemfarben ändern sich mit der Hell/Dunkel-Einstellung nicht, daher macht das Referenzieren von WPFs SystemColors nichts dunkel. Die Struktur, um dem Thema selbst zu folgen, ist wie folgt.

  1. Definieren Sie Farben und Pinsel mit denselben Schlüsseln in Themes/Light.xaml für Hell und Themes/Dark.xaml für Dunkel.
  2. Referenzieren Sie sie aus XAML mit DynamicResource, etwa {DynamicResource App.WindowBackgroundBrush} (StaticResource ist zur Ladezeit fest und folgt einem Tausch nicht).
  3. Bei einer Benachrichtigung vom SystemThemeWatcher aus Kapitel 5 tauschen Sie das entsprechende Wörterbuch in MergedDictionaries. Während eines Kontrastthemas ersetzt der Trigger aus Abschnitt 8.4 die Farben durch Systemfarben, welches Wörterbuch Sie auch einsetzen, setzen Sie also das helle ein.
public static class AppTheme
{
    private static readonly Uri Light = new("pack://application:,,,/Themes/Light.xaml");
    private static readonly Uri Dark = new("pack://application:,,,/Themes/Dark.xaml");

    public static void Apply(ThemeState state)
    {
        var merged = Application.Current.Resources.MergedDictionaries;
        var current = merged.FirstOrDefault(d => d.Source == Light || d.Source == Dark);
        // Das dunkle Wörterbuch nur bei Dunkel. Während eines Kontrastthemas den Systemfarben überlassen (Abschnitt 8.4)
        var next = new ResourceDictionary { Source = state == ThemeState.Dark ? Dark : Light };
        if (current is null)
        {
            merged.Add(next);
        }
        else
        {
            merged[merged.IndexOf(current)] = next; // an Ort und Stelle tauschen, an derselben Position
        }
    }
}
Ressourcenwörterbücher in WPF tauschenFarben mit denselben Schlüsseln in einem hellen und einem dunklen Wörterbuch definieren, sie aus XAML mit DynamicResource referenzieren und das Wörterbuch in MergedDictionaries bei einer Themenwechselbenachrichtigung tauschen, sodass die Referenzen aktualisiert werdenThemenwechselbenachrichtigungDas Wörterbuch in MergedDictionaries tauschenLight.xaml (dieselben Schlüssel)Dark.xaml (dieselben Schlüssel)DynamicResource-Referenzen aktualisieren sichStaticResource-ReferenzenBehalten den Wert von der Ladezeit

Abbildung 15: Tauschen Sie Wörterbücher mit denselben Schlüsseln, und nur DynamicResource-Referenzen folgen.

Die Vorlagen der Standardsteuerelemente (Schaltflächenhintergründe, Farben der Bildlaufleisten) tragen die Farben des klassischen Themas, daher gibt es auch hier Stellen, an denen „die eigenen Oberflächen der App dunkel, die Standardsteuerelemente aber hell sind“. Schätzen Sie die Arbeit, den Stil jedes benötigten Steuerelements zu überschreiben, und vergleichen Sie sie dann mit der Übernahme von Fluent oder festem Hell.

7.3. Die Titelleiste

Wenn Sie ThemeMode verwenden, übernimmt WPF sie. Wenn Sie dem Thema selbst auf dem klassischen Thema folgen, verwenden Sie die DWM-Attributsetzung aus OnSourceInitialized aus Abschnitt 4.2 und setzen Sie sie bei Benachrichtigungen von SystemThemeWatcher erneut.

8. Zeichnen unter einem Kontrastthema — Die Systemfarbenpaare halten

8.1. Erkennung und Benachrichtigung

In Win32 übergeben Sie SPI_GETHIGHCONTRAST an SystemParametersInfo, um eine Struktur HIGHCONTRAST zu empfangen, und prüfen das Bit HCF_HIGHCONTRASTON von dwFlags. cbSize muss vor dem Aufruf gesetzt sein.1128 Microsoft positioniert das als „den einzigen unterstützten Weg zu prüfen, ob hoher Kontrast eingeschaltet ist“.12

bool IsContrastThemeActive()
{
    HIGHCONTRASTW hc{};
    hc.cbSize = sizeof(hc);
    if (!::SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0))
    {
        // Einen Fehler nicht hinter einem Vorgabewert verstecken. Mit dem Fehlercode sichtbar machen, damit die Ursache gefunden werden kann
        throw std::system_error(::GetLastError(), std::system_category(),
                                "SystemParametersInfo(SPI_GETHIGHCONTRAST)");
    }
    return (hc.dwFlags & HCF_HIGHCONTRASTON) != 0;
}

Jedes Framework hat eine Eigenschaft, die diesen Aufruf umhüllt.

Umgebung Erkennung Änderungsbenachrichtigung
Win32 / MFC SPI_GETHIGHCONTRAST + HCF_HIGHCONTRASTON WM_SYSCOLORCHANGE, WM_THEMECHANGED
WinForms SystemInformation.HighContrast SystemEvents.UserPreferenceChanged
WPF SystemParameters.HighContrast (bildet auf SPI_GETHIGHCONTRAST ab) SystemParameters.StaticPropertyChanged
WinUI 3 ThemeSettings.HighContrast (Microsoft.UI.System) ThemeSettings.Changed

Der WinForms-Barrierefreiheits-Walkthrough verlangt, HighContrast beim Start zu prüfen und auf Änderungen über UserPreferenceChanged zu reagieren.13 WPFs SystemParameters.HighContrast bildet auf SPI_GETHIGHCONTRAST und HCF_HIGHCONTRASTON ab,29 und Änderungen der statischen Eigenschaften werden über StaticPropertyChanged gemeldet.30 WinUI 3s ThemeSettings wird mit CreateForWindowId an ein Fenster gebunden erzeugt, und Sie abonnieren sein Ereignis Changed, aber beachten Sie, dass die Ereignisse aufhören, wenn Sie keine Referenz auf das Objekt halten.31

Erkennungspfade für KontrastthemenWin32 SPI_GETHIGHCONTRAST ist die einzige unterstützte Erkennungsmethode, und SystemInformation.HighContrast in WinForms, SystemParameters.HighContrast in WPF und ThemeSettings.HighContrast in WinUI 3 werden von jedem Framework als Hüllen darum bereitgestelltSPI_GETHIGHCONTRAST (die einzige Erkennungsmethode)WinForms SystemInformationWPF SystemParametersWinUI 3 ThemeSettingsWin32: direkt aufrufen

Abbildung 16: Die Wurzel der Erkennung ist eine einzelne Win32-API, und jedes Framework hat eine Eigenschaft, die sie umhüllt.

8.2. Zeichengrundsätze — Vordergrund- und Hintergrundpaare

Microsofts „High contrast parameter“ listet drei Dinge, die eine App tun soll, wenn hoher Kontrast eingeschaltet ist.11

  1. Jede Farbe auf ein Paar aus Vordergrund- und Hintergrundfarbe abbilden. Verwenden Sie GetSysColor mit dem Paar COLOR_WINDOWTEXT und COLOR_WINDOW oder dem Paar COLOR_BTNTEXT und COLOR_BTNFACE.
  2. Bitmap-Bilder hinter Text weglassen. Sie sind ein visuelles Hindernis für Benutzer, die hohen Kontrast brauchen.
  3. Mehrfarbige Bilder in den für Text verwendeten Vordergrund- und Hintergrundfarben zeichnen.

Das „Paar“ ist der Kern. Der Leitfaden für Windows 8 und höher erklärt, dass COLOR_HIGHLIGHTTEXT dazu entworfen ist, mit dem Hintergrund COLOR_HIGHLIGHT kombiniert zu werden und COLOR_WINDOWTEXT mit dem Hintergrund COLOR_WINDOW, und verlangt, Textfarben nicht fest einzucodieren und, weil Benutzer die Farben anpassen, eine UI zu bauen, die nicht vom geltenden Thema abhängt.12 Das Beispiel desselben Leitfadens, „in Aero ist Text immer schwarz und die Auswahlfarbe hellblau, aber in High Contrast Black ist die Auswahlfarbe schwarz. Wenn Sie schwarzen Text annehmen und die Systemauswahlfarbe verwenden, erhalten Sie schwarzen Text auf Schwarz“, ist genau die Beschwerde „die Statusanzeige ist verschwunden“ aus der Eröffnung.

Die Windows-11-Leitlinie zu Kontrastthemen tabelliert die Paarungen.1

Verwendung Vordergrund Hintergrund
Überschriften, Fließtext, Listen, Rahmen, nicht interaktive UI SystemColorWindowText SystemColorWindow
Hyperlinks SystemColorHotlight SystemColorWindow
Deaktivierte oder inaktive UI SystemColorGrayText SystemColorWindow
Ausgewählt, Hover, gedrückt, in Bearbeitung SystemColorHighlightText SystemColorHighlight
Interaktive UI wie Schaltflächen SystemColorButtonText SystemColorButtonFace

Was nicht zu tun ist, steht ebenfalls. Verwenden Sie GrayText nicht für ergänzenden oder Hinweistext (es ist nur für den deaktivierten Zustand); verwenden Sie Hotlight für nichts außer Hyperlinks; mischen Sie unverträgliche Vordergründe und Hintergründe nicht; wählen Sie Farben nicht nach Aussehen (Benutzer ändern sie wirklich). Es gibt auch eine Gestaltungsleitlinie, dass die Hintergründe von Seiten, Bereichen und Popups auf SystemColorWindow basieren, sodass benachbarte Oberflächen dieselbe Hintergrundfarbe haben, und nur die Grenzen, die zählen, mit einem Rahmen getrennt werden, der nur unter Kontrastthemen verwendet wird (2px wird für Flyouts und Dialoge empfohlen).1

Wie das Aufbrechen des Paars Text unleserlich machtSchwarzen Text anzunehmen und die Systemauswahlfarbe nur für den Auswahlhintergrund zu verwenden ergibt Schwarz auf Schwarz in High Contrast Black, wo die Auswahlfarbe schwarz ist. Vordergrund und Hintergrund als Paar zu nehmen hält Text lesbar, auch wenn der Benutzer die Farben bearbeitetAnnehmen, Text sei schwarzNur der Auswahlhintergrund verwendet die SystemauswahlfarbeIn High Contrast Black ist die Auswahlfarbe schwarzSchwarzer Text auf SchwarzVordergrund und Hintergrund als Paar nehmenLesbar, auch wenn der Benutzer die Farben bearbeitetBenutzer bearbeitet die Farben

Abbildung 17: Eine Systemfarbe nur für eine Seite zu verwenden kann Schwarz auf Schwarz ergeben, aber das Paar zu nehmen bleibt lesbar, auch wenn die Farben bearbeitet werden.

Zeichenentscheidungen unter einem KontrastthemaWenn ein Kontrastthema aktiv ist, Farben auf die Systemfarbenpaare abbilden, Bilder hinter Text weglassen, mehrfarbige Bilder in den zwei Vordergrund- und Hintergrundfarben zeichnen und keine fest eingecodierten Farben verwendenKontrastthema aktivFarben auf Paare abbildenBilder hinter Text weglassenMehrfarbige Grafiken in zwei Farben zeichnenKeine fest eingecodierten Farben

Abbildung 18: Das Zeichnen unter einem aktiven Kontrastthema läuft auf vier Punkte hinaus: Abbilden, Weglassen, zwei Farben und kein festes Einprogrammieren.

8.3. Implementierung in WinForms

Die Standard-WinForms-Steuerelemente folgen den Systemfarben, solange ForeColor / BackColor bei ihren Vorgaben bleiben. Nur die Stellen mit benutzerdefinierten Farben und das Selbstzeichnen werden gemäß der Prüfung umgeschaltet. Das Beispiel des Walkthroughs nimmt ein Label, das normalerweise gelb auf blau ist, und setzt es unter hohem Kontrast auf SystemColors.Window / SystemColors.WindowText zurück.13 Es läuft darauf hinaus, der Palettenstruktur von weiter oben einen Kontrastthema-Zweig hinzuzufügen.

using Microsoft.Win32;

public partial class OrderForm : Form
{
    private readonly SynchronizationContext _ui;

    public OrderForm()
    {
        InitializeComponent();
        // Die Steuerelemente existieren jetzt, also ist ein WindowsFormsSynchronizationContext vorhanden
        _ui = SynchronizationContext.Current
              ?? throw new InvalidOperationException("Create this form on the UI thread.");
        ApplyColorScheme();
        SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
    }

    private void ApplyColorScheme()
    {
        if (SystemInformation.HighContrast)
        {
            // Das Paar halten, vollständig den Systemfarben überlassen und das Bild hinter dem Text entfernen
            statusLabel.BackColor = SystemColors.Window;
            statusLabel.ForeColor = SystemColors.WindowText;
            headerPanel.BackgroundImage = null;
        }
        else
        {
            var p = AppPalette.Current; // die Hell/Dunkel-Palette (Kapitel 5)
            statusLabel.BackColor = p.PanelBackground;
            statusLabel.ForeColor = p.PanelForeground;
            headerPanel.BackgroundImage = Properties.Resources.HeaderPattern;
        }
    }

    private void OnUserPreferenceChanged(object? sender, UserPreferenceChangedEventArgs e)
    {
        // Auch dieses Ereignis ist nicht garantiert auf dem UI-Thread. Auf die UI zurückführen, dann ohne Filterung nach Kategorie neu bewerten
        _ui.Post(_ =>
        {
            if (IsDisposed) return;
            ApplyColorScheme();
        }, null);
    }

    // Ein statisches Ereignis, daher leckt das Formular, wenn es nicht abgetrennt wird. In Dispose(bool) abtrennen, das auch
    // auf den Pfaden läuft, auf denen das Formular ohne Schließen entsorgt wird (wenn das designerzeugte Dispose(bool) existiert, dort einfügen)
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
        }
        base.Dispose(disposing);
    }
}

Selbstzeichnen in OnPaint verwendet Systempinsel, die das Paar halten, etwa SystemBrushes.Window / SystemPens.WindowText, und mehrfarbige Grafiken wie ein farbiger Punkt, der einen Status darstellt, werden durch einen Rahmen und Text („Running“, „Stopped“) in der Vordergrundfarbe ersetzt. Informationen durch mehr als nur Farbe zu vermitteln ist derselbe Punkt wie Erfolgskriterium 1.4.1 aus dem vorherigen Artikel.

Verzweigung des WinForms-FarbschemasApplyColorScheme wird beim Start und bei jedem UserPreferenceChanged aufgerufen; wenn SystemInformation.HighContrast wahr ist, überlässt es den Systemfarbenpaaren und entfernt das Hintergrundbild, und wenn falsch, nimmt es Farben aus der Hell/Dunkel-PaletteWahrFalschStart / UserPreferenceChangedApplyColorSchemeSystemInformation.HighContrast?Den SystemColors-Paaren überlassenDas Hintergrundbild entfernenFarben aus der Hell/Dunkel-Palette nehmen

Abbildung 19: In WinForms wird dieselbe Routine beim Start und bei jeder Benachrichtigung aufgerufen, und unter einem Kontrastthema überlässt sie den Systemfarben.

8.4. Implementierung in WPF

WPFs SystemColors aktualisieren sich automatisch, wenn sich ein Pinsel ändert, wenn Sie einen Ressourcenschlüssel wie WindowBrushKey über DynamicResource referenzieren (eine statische Referenz, die WindowBrush direkt verwendet, aktualisiert sich nicht).32 Um das Aussehen nur während eines Kontrastthemas zu ändern, referenzieren Sie den Wert von SystemParameters.HighContrast aus einem DataTrigger. SystemParameters.HighContrast ist jedoch eine statische Eigenschaft, daher ist sie für sich keine lebendige Bindungsquelle. Bereiten Sie einen kleinen Proxy vor, der StaticPropertyChanged abonniert, den Wert hält und über INotifyPropertyChanged benachrichtigt, und binden Sie mit dieser Instanz als Source.30

public sealed class ThemeSettings : INotifyPropertyChanged
{
    public static ThemeSettings Instance { get; } = new();

    public bool IsHighContrast { get; private set; } = SystemParameters.HighContrast;
    public event PropertyChangedEventHandler? PropertyChanged;

    private ThemeSettings()
    {
        // Wird ausgelöst, wenn sich eine statische Eigenschaft von SystemParameters ändert (SPI_GETHIGHCONTRAST wird erneut gelesen)
        SystemParameters.StaticPropertyChanged += (_, e) =>
        {
            if (!string.IsNullOrEmpty(e.PropertyName)
                && e.PropertyName != nameof(SystemParameters.HighContrast)) return;
            IsHighContrast = SystemParameters.HighContrast;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsHighContrast)));
        };
    }
}
<!-- xmlns:local="clr-namespace:OrderEntry" vorher deklarieren -->
<Style x:Key="CardStyle" TargetType="Border">
    <Setter Property="Background" Value="{DynamicResource App.CardBackgroundBrush}"/>
    <Setter Property="BorderBrush" Value="{DynamicResource App.CardBorderBrush}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Style.Triggers>
        <DataTrigger Binding="{Binding Source={x:Static local:ThemeSettings.Instance}, Path=IsHighContrast}"
                     Value="True">
            <!-- Das Paar halten: Hintergrund ist Window, Rahmen und Text sind WindowText. Die Grenze dicker machen -->
            <Setter Property="Background"
                    Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
            <Setter Property="BorderBrush"
                    Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
            <!-- Den Text darin ebenfalls erben lassen. Beachten Sie, dass ein Kind, das Foreground ausdrücklich setzt, die Vererbung abschneidet -->
            <Setter Property="TextElement.Foreground"
                    Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
            <Setter Property="BorderThickness" Value="2"/>
        </DataTrigger>
    </Style.Triggers>
</Style>
WPF-Systemfarbenreferenzen und der Kontrastthema-TriggerDas Referenzieren von SystemColors-Ressourcenschlüsseln über DynamicResource folgt Pinseländerungen automatisch, und ein Trigger, der an IsHighContrast auf einem Proxy gebunden ist, der StaticPropertyChanged abonniert, reagiert auf einen Wechsel zur Laufzeit und schaltet auf Farben um, die das Paar halten. Eine direkte Referenz auf WindowBrush aktualisiert sich nichtWindowBrushKey über DynamicResource referenzierenFolgt Pinseländerungen automatischStaticPropertyChangedIsHighContrast auf dem ProxyDataTrigger reagiertAuf Farben umschalten, die das Paar haltenWindowBrush direkt referenzierenAktualisiert sich nicht

Abbildung 20: WPF folgt einem Wechsel zur Laufzeit über dynamische Referenzen auf Ressourcenschlüssel und eine Bindung an einen Proxy, der Änderungen der statischen Eigenschaft weiterleitet.

Wenn Sie das Fluent-Thema verwenden, behalten Sie im Blick, dass .NET 10 HighContrast-bezogene Absturzbehebungen enthielt,9 und machen Sie die Prüfung unter einem Kontrastthema zu einer Bedingung der Übernahme.

8.5. Implementierung in WinUI 3

In WinUI 3 folgen die Standardsteuerelemente hellen, dunklen und Kontrastthemen von Anfang an, und die eigenen Farben der App werden in ResourceDictionary.ThemeDictionaries unter den Schlüsseln Default (dunkel), Light und HighContrast definiert. Unter HighContrast Farben nicht fest einprogrammieren; dynamische Systemfarben wie SystemColorWindowColor über ThemeResource referenzieren. Ein benutzerdefiniertes Steuerelement, das Light/Dark hat, muss immer auch HighContrast haben, und HighContrast ist der Rückfallschlüssel, der verwendet wird, wenn kein anderes benanntes High-Contrast-Thema gefunden wird.331

<ResourceDictionary.ThemeDictionaries>
    <ResourceDictionary x:Key="Default">
        <SolidColorBrush x:Key="App.CardBackgroundBrush" Color="#2B2B2B"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="Light">
        <SolidColorBrush x:Key="App.CardBackgroundBrush" Color="#F3F3F3"/>
    </ResourceDictionary>
    <ResourceDictionary x:Key="HighContrast">
        <SolidColorBrush x:Key="App.CardBackgroundBrush"
                         Color="{ThemeResource SystemColorWindowColor}"/>
    </ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>

Noch eines: WinUI hat einen Mechanismus namens HighContrastAdjustment, der standardmäßig aktiviert ist. Er erzwingt weißen Text und einen schwarzen Hervorhebungshintergrund, um den Kontrast zu erhalten, und die Leitlinie empfiehlt, sobald Sie Themenwörterbücher vorbereitet haben, die die Systemfarben korrekt verwenden, ihn auf None zu setzen, damit Ihre eigenen Stile greifen.1

Wie WinUI ThemeDictionaries auflöstJe nach aktuellem Thema wird eines der Wörterbücher Default (dunkel), Light und HighContrast gewählt, und unter HighContrast werden dynamische Systemfarben über ThemeResource referenziert. HighContrast ist der Rückfallschlüssel, wenn es kein benanntes High-Contrast-Thema gibtDunkelHellKontrastthemaAktuelles Thema?Default-WörterbuchLight-WörterbuchHighContrast-WörterbuchSystemColor-Ressourcen über ThemeResource referenzierenRückfall, wenn es kein benanntes Thema gibt

Abbildung 21: In WinUI wird das Wörterbuch für jedes Thema automatisch gewählt, und das HighContrast-Wörterbuch referenziert die Systemfarben.

9. Unterstützung des dunklen Modus ist kein Ersatz für Barrierefreiheitsunterstützung

Die Unterstützung des dunklen Modus als „wir haben die Barrierefreiheitsarbeit erledigt“ zu berichten ist ein Fehler. Die Beziehung der beiden lässt sich wie folgt legen.

  • Der Kontrastverhältnis-Standard gilt für die dunkle Palette genauso. WCAG-Erfolgskriterium 1.4.3 verlangt 4,5:1 für Text und 3:1 für großen Text, und das ändert sich nicht, wenn der Hintergrund dunkel ist.14 Ein dunkles Design, das mittelgrauen Text auf dunkelgrauem Hintergrund setzt, hat dasselbe Problem wie „hellgrau auf weiß“ im Hellmodus.
  • Reines Schwarz und reines Weiß zu vermeiden ist das Design von Windows 11. Microsofts Best Practices erklären, dass Windows 11 von reinem Weiß und reinem Schwarz zu Tönen wechselte, die den Augen leichter fallen.34 Umgekehrt führt ein Hintergrund #000000 im dunklen Modus bei manchen Menschen zu Klagen über Halation, einen Bloom-Effekt durch übermäßigen Kontrast gegen hellen Text.
  • Die Berücksichtigung von Farbsehvielfalt ist unabhängig vom Thema nötig. Microsofts Farbleitlinie verlangt, Farbe als visuelle Verstärkung statt als primäres Kommunikationsmittel zu verwenden und die Kombination aus Rot und Grün niemals zur einzigen Unterscheidung zu machen.3515
  • Kontrastthemen sind eine Anforderung unabhängig vom dunklen Modus. Wie Kapitel 2 erklärt hat, ist der dunkle Modus nicht verfügbar, solange ein Kontrastthema aktiv ist, daher erreicht selbst eine perfekte Unterstützung des dunklen Modus Benutzer von Kontrastthemen nie.
Die Beziehung zwischen dunklem Modus und BarrierefreiheitUnterstützung des dunklen Modus ist eine Frage visueller Vorliebe und Umgebung, während die Barrierefreiheitsanforderungen Kontrastverhältnis, Informationen durch mehr als nur Farbe und Kontrastthema-Unterstützung unabhängig vom Thema gesondert erfüllt werden müssenKein Ersatz fürUnterstützung des dunklen ModusVorliebe und UmgebungBarrierefreiheitKontrastverhältnis 4,5:1Nicht Farbe alleinKontrastthema-Unterstützung

Abbildung 22: Unterstützung des dunklen Modus adressiert Vorliebe und Umgebung; die Barrierefreiheitsanforderungen müssen gesondert erfüllt werden.

Andererseits ist die Arbeit des „Farben an einer Stelle Sammelns“ aus Kapitel 5 die Grundlage von beidem. Mit der Palette an einer Stelle können Sie aufzählen, was für das Kontrastverhältnis in Hell und Dunkel zu messen ist, und der Kontrastthema-Zweig kann an derselben Stelle geschrieben werden. Nutzen Sie die Unterstützung des dunklen Modus als Anlass, die Palette zu konsolidieren, und prüfen Sie Kontrastverhältnisse und Kontrastthemen nebenbei. Das ist die Reihenfolge mit der besten Rendite der Investition.

10. Eine Richtlinie festlegen — Empfehlungen nach App-Typ

App-Typ Empfohlene Richtlinie
Neues WinForms (.NET 10) SetColorMode(System) verwenden. Selbstzeichnen auf SystemColors stützen und benutzerdefinierte Steuerelemente, die Common Controls enthalten, mit ApplyThemingImplicitly opt-in
Neues WPF (.NET 9/10) ThemeMode="System" übernehmen, nachdem Sie geprüft haben, wie die von Ihnen verwendeten Steuerelemente dargestellt werden und wie die App unter Kontrastthemen verhält. Wenn das schwierig ist, klassisches Thema plus Wörterbuchtausch
Bestehendes WinForms/WPF (.NET Framework 4.8, .NET 8 oder früher) „Festes Hell“ erklären und das DWM-Attribut bei seiner Vorgabe (FALSE) belassen. Kontrastthema-Unterstützung ist verpflichtend; zur Unterstützung des dunklen Modus bei der Migration nach .NET 10 wechseln
WinUI 3 Folgt dem System standardmäßig. Die eigenen Farben der App in ThemeDictionaries einschließlich HighContrast definieren und HighContrastAdjustment auf None setzen
Win32 / MFC DWM-Attribut + eigene Palette + Neuberechnung bei WM_THEMECHANGED / WM_SYSCOLORCHANGE. Der offizielle Leitfaden deckt nur Erkennung und Titelleiste ab; das Neuzeichnen der Common Controls liegt außerhalb seines Umfangs
Der Weg von bestehenden Assets zur Unterstützung des dunklen ModusBestehende Assets erklären zuerst festes Hell, schließen Kontrastthema-Unterstützung unbedingt ab, konsolidieren die Palette als Vorbereitung, migrieren dann nach .NET 10 und wechseln zu SetColorMode oder ThemeMode. Ein halb fertiger dunkler Modus ist eine schlechtere Erfahrung als festes Hell, daher wird dieser Weg nicht genommenNicht genommenFestes Hell erklären (jetzt)Kontrastthema-Unterstützung (verpflichtend)Die Palette konsolidieren (Vorbereitung)Nach .NET 10 migrierenZu SetColorMode / ThemeMode wechselnHalb fertiger dunkler ModusSchlechtere Erfahrung als festes Hell

Abbildung 23: Bestehende Assets starten bei festem Hell, gehen über Kontrastthema-Unterstützung und Palettenkonsolidierung und wechseln bei der Migration nach .NET 10 zur Unterstützung des dunklen Modus.

„Festes Hell“ ist keine Niederlage. Es ist das Standardverhalten von Windows selbst und dokumentiertes Verhalten. Eine App, die durchgängig hell ist, ist für Benutzer weit besser als ein halb fertiger dunkler Modus, der mit „nur die Titelleiste schwarz“ oder „nur die Bildlaufleisten weiß“ ausgeliefert wird. Kontrastthema-Unterstützung ist jedoch das eine, das nicht „fest“ gesetzt werden kann. Sie fällt unter die angemessenen Vorkehrungen aus dem vorherigen Artikel: Es geht darum, ob die App nutzbar ist, nicht um Themenvorliebe.

11. Prüfliste

Wenn die Arbeit erledigt ist, prüfen Sie auf einer echten Maschine in der folgenden Reihenfolge. Jeder Schritt lässt sich in der Einstellungen-App in Sekunden umschalten.

  1. Hell/Dunkel umschalten, während die App läuft. Ändern Sie den Modus unter Einstellungen > Personalisierung > Farben und bestätigen Sie, dass sowohl Titelleiste als auch Clientbereich folgen, oder dass die App sich wie spezifiziert mit „greift beim nächsten Start“ verhält (SetColorMode in WinForms folgt nicht).
  2. Eine Handle-Neuerzeugung auslösen. In WinForms ShowInTaskbar zur Laufzeit umschalten und bestätigen, dass das Titelleistenattribut erhalten bleibt.
  3. Alle vier Kontrastthemen ausprobieren. Mit linker Alt-Taste + linker Umschalttaste + Druck umschalten und in jedem von Aquatic, Desert, Dusk und Night sky bestätigen, dass Text, Rahmen, ausgewählte Zeilen, deaktivierte Elemente und Links lesbar sind.1
  4. Die Farben eines Kontrastthemas bearbeiten. Benutzer ändern die Farben wirklich. Bearbeiten Sie den Hintergrund zu einer extremen Farbe, um verbliebene fest eingecodierte Farben herauszuspülen.
  5. Die Protokolle prüfen. Bestätigen Sie, dass ein Fehler von DwmSetWindowAttribute oder SystemParametersInfo auf einem unterstützten Betriebssystem aufgezeichnet wird und dass die App unter Windows 10 unterhalb von Build 22000 hell startet, ohne das DWM-Attribut aufzurufen.
  6. Die Kontrastverhältnisse messen. Prüfen Sie in Hell und Dunkel jede Kombination aus Text- und Hintergrundfarbe in der Palette gegen 4,5:1.14
Prüfschritte für ThemenunterstützungAuf einer echten Maschine in dieser Reihenfolge prüfen: Hell/Dunkel zur Laufzeit umschalten, Handle-Neuerzeugung, die vier Kontrastthemen, die Themenfarben bearbeiten, Fehlerprotokolle prüfen und Kontrastverhältnisse messenHell/Dunkel zur Laufzeit umschaltenHandle-NeuerzeugungDie vier KontrastthemenDie Themenfarben bearbeitenDie Fehlerprotokolle prüfenDie Kontrastverhältnisse messen

Abbildung 24: Die Prüfung beginnt mit dem Umschalten von Einstellungen und schließt mit dem Prüfen der Protokolle und Kontrastverhältnisse.

12. Zusammenfassung

  • Windows-Themen haben zwei Achsen, Hell/Dunkel und Kontrastthemen, und der dunkle Modus ist nicht verfügbar, solange letzteres aktiv ist. Erkennen Sie das Kontrastthema zuerst.
  • Die Titelleiste einer bestehenden App ist weiß, weil das die Kompatibilitätsvorgabe ist; übergeben Sie TRUE für DWMWA_USE_IMMERSIVE_DARK_MODE (Wert 20, Windows 11 Build 22000 oder höher) über DwmSetWindowAttribute, und sie wird dunkel gezeichnet, wenn das System dunkel ist. Setzen Sie es jedes Mal, wenn der HWND erzeugt wird, und protokollieren Sie Fehler.
  • Entscheiden Sie den aktuellen Modus aus der Helligkeit der Vordergrundfarbe von UISettings.GetColorValue, bemerken Sie Änderungen mit ColorValuesChanged, führen Sie auf den UI-Thread zurück und zeichnen Sie neu. Halten Sie die Farben an einer Stelle gesammelt.
  • WinForms: Application.SetColorMode(SystemColorMode.System) unter .NET 9/10. Kennen Sie die drei Einschränkungen (nur Windows 11, während eines Kontrastthemas deaktiviert, keine Nachführung von Änderungen zur Laufzeit) und ApplyThemingImplicitly für benutzerdefinierte Steuerelemente.
  • WPF: ThemeMode="System" unter .NET 9/10. Manipulation aus Code ist experimentell und Fluent ist in Arbeit, also vor der Übernahme bewerten. Auf dem klassischen Thema Wörterbuchtausch plus DynamicResource.
  • Unter einem Kontrastthema mit der Familie SPI_GETHIGHCONTRAST erkennen, Farben auf die Systemfarbenpaare abbilden, Bilder hinter Text weglassen und mehrfarbige Grafiken in zwei Farben zeichnen. GrayText ist für den deaktivierten Zustand, Hotlight nur für Links.
  • Unterstützung des dunklen Modus ist kein Ersatz für Barrierefreiheitsunterstützung. 4,5:1 gilt auch im dunklen Modus, Informationen dürfen nicht allein auf Farbe beruhen, und Kontrastthema-Unterstützung ist gesondert erforderlich.
  • Für bestehende Assets ist „festes Hell“ erklären die realistische Antwort, und Kontrastthema-Unterstützung ist das eine, das nicht festgesetzt werden kann.

Als empfohlener erster Schritt nehmen Sie einen Hauptbildschirm, aktivieren Sie zuerst ein Kontrastthema mit linker Alt-Taste + linker Umschalttaste + Druck und schauen Sie hin, schalten Sie mit denselben Tasten zurück und schalten Sie dann die Windows-Farbeneinstellung auf dunkel (der dunkle Modus ist nicht verfügbar, solange ein Kontrastthema aktiv ist, also die beiden getrennt ausprobieren). Innerhalb weniger Minuten sehen Sie, „wo Ihre App ihre Farben hält“.

Verwandte Artikel

Verwandte Beratungsbereiche

KomuraSoft LLC übernimmt die Unterstützung des dunklen Modus für WinForms/WPF-Geschäftsanwendungen (Konsolidieren der Palette, Bewerten einer Migration zu SetColorMode / ThemeMode unter .NET 9/10, Einbinden des DWM-Attributs), Diagnose und Korrektur von Anzeigebrüchen unter Kontrastthemen sowie Beratungen zur Themennachführung in Win32/MFC-Assets. Vom Stadium „Mitarbeiter haben sich beschwert, sobald sie auf den dunklen Modus umgestellt haben“ zu starten, ist in Ordnung.

Quellen

  1. Microsoft Learn, Contrast themes. Dazu, dass Kontrastthemen eine eingeschränkte Palette von grob 7:1 oder höher verwenden und nicht mit hellen und dunklen Themen verwechselt werden dürfen; zu den vier Themen Aquatic, Desert, Dusk und Night sky und dem Bearbeiten ihrer Farben; zum Umschalten mit linker Alt-Taste + linker Umschalttaste + Druck; zu den Vordergrund/Hintergrund-Paaren und Verwendungen der SystemColor-Ressourcen; zur Verwendung von GrayText nur für den deaktivierten Zustand und Hotlight nur für Links; zu Brüchen durch fest eingecodierte Farben; zu Grenzrahmen; zu HighContrast in ThemeDictionaries; zum Setzen von HighContrastAdjustment auf None; und zur Erkennung mit Microsoft.UI.System.ThemeSettings.  2 3 4 5 6 7 8

  2. Microsoft Learn, Application.SetColorMode(SystemColorMode) Method. Zum Aufruf, bevor UI-Elemente erzeugt werden, dazu, dass die App sich auch mit System nicht automatisch anpasst, wenn sich die Systemeinstellung ändert, und dazu, dass der dunkle Farbmodus nur unter Windows 11 oder höher verfügbar und im High-Contrast-Modus nicht verfügbar ist.  2 3 4

  3. Microsoft Learn, Support Dark and Light themes in Win32 apps. Zur Definition von Vordergrund und Hintergrund in den Farbmodi; dazu, dass Windows aus Kompatibilität eine helle Titelleiste vorgibt, weil es nicht wissen kann, ob eine App den dunklen Modus unterstützt; zum Verfahren, die Vordergrundfarbe mit UISettings.GetColorValue zu holen und Hell oder Dunkel anhand der wahrgenommenen Leuchtdichte zu klassifizieren, um den dunklen Modus zu erkennen; zur Nachführung mit ColorValuesChanged; zum Aktivieren der dunklen Titelleiste mit DwmSetWindowAttribute und DWMWA_USE_IMMERSIVE_DARK_MODE (Wert 20); und dazu, dass die gesamte Oberfläche dem dunklen Modus folgen muss.  2 3 4 5 6 7 8 9 10

  4. Microsoft Learn, DWMWINDOWATTRIBUTE enumeration (dwmapi.h). Dazu, dass DWMWA_USE_IMMERSIVE_DARK_MODE erlaubt, den Rahmen dunkel zu zeichnen, wenn die dunkle Systemeinstellung aktiviert ist, und dass alle Fenster standardmäßig hell sind; zu den COLORREF-Werten von DWMWA_BORDER_COLOR, DWMWA_CAPTION_COLOR und DWMWA_TEXT_COLOR und dem Wiederherstellen der Vorgabe mit DWMWA_COLOR_DEFAULT; zur Unterstützung ab Windows 11 Build 22000; und zur Unterstützung von DWMWA_SYSTEMBACKDROP_TYPE ab Build 22621.  2 3 4

  5. Microsoft Learn, UISettings.ColorValuesChanged Event. Zum Ereignis, das ausgelöst wird, wenn sich ein Farbwert ändert.  2

  6. Microsoft Learn, What’s new in Windows Forms for .NET 9. Zur experimentellen vorläufigen Unterstützung des dunklen Modus, zum entsprechenden Wechsel von SystemColors, wenn sich der Farbmodus ändert, zu den drei SystemColorMode-Werten Classic, System und Dark, zum Aufruf von Application.SetColorMode im Startcode und zum Unterdrücken von WFO5001.  2 3

  7. Microsoft Learn, What’s new in Windows Forms for .NET 10. Zur vollständigen Integration des dunklen Modus und dazu, dass SetColorMode nicht mehr experimentell ist, dass Win32-Common-Controls in selbstgezeichneten Steuerelementen hell bleiben, wenn sie nicht opt-in, und zur Notwendigkeit, SetStyle(ControlStyles.ApplyThemingImplicitly) vor base.CreateParams innerhalb der CreateParams-Überschreibung aufzurufen, weil der Konstruktor zu spät kommt.  2 3 4

  8. Microsoft Learn, What’s new in WPF for .NET 9. Zum Fluent-Thema mit Unterstützung für Hell/Dunkel und die Akzentfarbe, zu den vier ThemeMode-Werten Light, Dark, System und None und dem Setzen auf Application oder Window, zum Anwenden über Ressourcenwörterbücher und dazu, dass das Setzen von ThemeMode aus Code experimentell ist und WPF0001 unterdrückt werden muss.  2 3 4

  9. Microsoft Learn, What’s new in WPF for .NET 10. Dazu, dass die Fluent-UI-Stilunterstützung noch in Arbeit ist, zu den hinzugefügten Fluent-Stilen für DatePicker, GridSplitter, GridView, GroupBox, Hyperlink, Label, NavigationWindow, RichTextBox und TextBox und zu den HighContrast-bezogenen Absturzbehebungen.  2 3

  10. Microsoft Learn, Application.ThemeMode Property. Dazu, dass es steuert, ob das Fluent-Thema im hellen, dunklen oder Systemmodus geladen wird und auch die Anwendung von Hintergrundmaterial und dunklem Modus auf das Fenster steuert, dass ThemeMode und Resources so entworfen sind, synchron zu bleiben, um Inkonsistenz zu vermeiden, und dass es das Attribut Experimental(“WPF0001”) trägt und in Zukunft entfernt werden kann.  2 3 4

  11. Microsoft Learn, High contrast parameter. Zum Holen der HIGHCONTRAST-Struktur mit SPI_GETHIGHCONTRAST bei der Initialisierung und beim Behandeln von WM_SYSCOLORCHANGE und zum Prüfen von HCF_HIGHCONTRASTON, und, wenn es eingeschaltet ist, zum Abbilden jeder Farbe auf ein Paar aus COLOR_WINDOWTEXT und COLOR_WINDOW oder COLOR_BTNTEXT und COLOR_BTNFACE, zum Weglassen von Bitmap-Bildern hinter Text und zum Zeichnen mehrfarbiger Bilder in den Vordergrund- und Hintergrundfarben.  2 3

  12. Microsoft Learn, High-contrast mode. Dazu, dass Aero schwarzen Text und eine hellblaue Auswahlfarbe hat, High Contrast Black aber eine schwarze Auswahlfarbe, was schwarzen Text auf Schwarz erzeugen kann; dazu, dass COLOR_HIGHLIGHTTEXT mit COLOR_HIGHLIGHT und COLOR_WINDOWTEXT mit COLOR_WINDOW gepaart werden soll; zum Nicht-fest-Einprogrammieren von Textfarben; zum Bauen einer UI, die nicht vom Thema abhängt, weil Benutzer die Farben anpassen; zum Neuberechnen der Farben bei WM_THEMECHANGED; und dazu, dass SPI_GETHIGHCONTRAST der einzige unterstützte Prüfungsweg ist.  2 3

  13. Microsoft Learn, Walkthrough: Creating an Accessible Windows-based Application. Zur Erkennung mit SystemInformation.HighContrast; zur Verwendung des Systemfarbschemas, wenn es eingeschaltet ist, zum Hinzufügen visueller Hinweise zu durch Farbe vermittelten Informationen und zum Weglassen von Bildern hinter Text; zur Prüfung beim Start und zum Folgen des Ereignisses UserPreferenceChanged; und zum Beispiel des Umschaltens der Farben eines Labels mit SystemColors.  2 3

  14. W3C / japanische Übersetzung durch das Web Accessibility Infrastructure Committee (WAIC), Web Content Accessibility Guidelines (WCAG) 2.1, Japanese translation. Zu Erfolgskriterium 1.4.3 (Contrast (Minimum)) mit 4,5:1 für Text und 3:1 für großen Text und zu Erfolgskriterium 1.4.1 (Use of Color).  2 3

  15. Microsoft Learn, Color in Windows. Dazu, dass Windows zwei Farbmodi hat, hell und dunkel, dass die Wahl von Akzentfarbe und Thema sich über die gesamte Benutzererfahrung widerspiegelt, und zur Sicherstellung von Kontrast und zur Berücksichtigung von Farbsehvielfalt.  2

  16. Microsoft Learn, Reference for Windows 11 and Windows 10 settings. Dazu, dass AppsUseLightTheme und SystemUsesLightTheme unter HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize DWORD-Werte sind, die den Hell/Dunkel-Modus von Apps und von Windows darstellen. 

  17. Microsoft Learn, Theming in Windows apps. Dazu, dass das Entfernen von RequestedTheme die App der Systemeinstellung folgen lässt, dass das System RequestedTheme überschreibt, wenn der Benutzer ein High-Contrast-Thema wählt, und dass benutzerdefinierte Vorlagen Themenpinsel statt fest eingecodierter Farben verwenden sollten. 

  18. Microsoft Learn, DwmSetWindowAttribute function (dwmapi.h). Zur Funktion, die die DWM-Darstellungsattribute des Nichtclientbereichs eines Fensters setzt, und ihrer Verfügbarkeit ab Windows Vista. 

  19. Microsoft Learn, Retrieve a window handle (HWND). Zum Holen des Handle von WindowInteropHelper in WPF. 

  20. Microsoft Learn, DWM_SYSTEMBACKDROP_TYPE enumeration (dwmapi.h). Dazu, dass DWMSBT_MAINWINDOW unter Windows 11 Mica und DWMSBT_TRANSIENTWINDOW Acrylic entspricht, dass sich die Wirkung des Materials in künftigen Windows-Versionen ändern kann, und zur Unterstützung ab Windows 11 Build 22621. 

  21. Microsoft Learn, UISettings.GetColorValue(UIColorType) Method. Zur Methode, die den Farbwert des angegebenen UIColorType zurückgibt. 

  22. Microsoft Learn, WM_SETTINGCHANGE message. Zur Nachricht, die an alle Fenster der obersten Ebene gesendet wird, wenn SystemParametersInfo eine systemweite Einstellung ändert oder wenn sich eine Richtlinieneinstellung ändert. 

  23. Microsoft Learn, SystemEvents.UserPreferenceChanged Event. Zum statischen Ereignis, das ausgelöst wird, wenn sich eine Benutzereinstellung ändert, und zum Speicherleck, das entsteht, wenn der Handler nicht abgetrennt wird. 

  24. Microsoft Learn, WM_THEMECHANGED message. Dazu, dass sie nach dem Aktivieren, Deaktivieren oder Wechseln eines Themas an alle Fenster gesendet wird und dass vorhandene Themenhandles ungültig werden und erneut geöffnet werden müssen. 

  25. Microsoft Learn, WM_SYSCOLORCHANGE message. Dazu, dass sie an alle Fenster der obersten Ebene gesendet wird, wenn sich eine Systemfarbeneinstellung ändert, dass Pinsel, die Systemfarben verwenden, neu erzeugt werden müssen, und dass sie an die Common Controls weitergeleitet werden muss. 

  26. Microsoft Learn, Compiler Error WFO5001. Dazu, dass SetColorMode und SystemColorMode in .NET 9 als experimentelle Features zur Bewertung geschützt waren und dass der Fehler ab .NET 10 nicht gilt. 

  27. Microsoft Learn, SystemColors.UseAlternativeColorSet Property. Dazu, dass das Setzen auf true die System-KnownColor-Werte einen alternativen Farbsatz zurückgeben lässt (derzeit die Version für den dunklen Modus), dass es ein experimentelles Feature unter SYSLIB5002 ist und dass die System-KnownColor-Werte immer die aktuellen Windows-Farben zurückgeben, wenn in Windows ein High-Contrast-Thema aktiv ist. 

  28. Microsoft Learn, HIGHCONTRASTW structure (winuser.h). Zu HCF_HIGHCONTRASTON (0x00000001) in dwFlags und zur Notwendigkeit, cbSize anzugeben, wenn es mit SPI_GETHIGHCONTRAST verwendet wird. 

  29. Microsoft Learn, SystemParameters.HighContrast Property. Zur statischen WPF-Eigenschaft, die auf SPI_GETHIGHCONTRAST und HCF_HIGHCONTRASTON abbildet. 

  30. Microsoft Learn, SystemParameters.StaticPropertyChanged Event. Zum statischen Ereignis, das ausgelöst wird, wenn sich irgendeine Eigenschaft von SystemParameters ändert.  2

  31. Microsoft Learn, ThemeSettings Class (Microsoft.UI.System). Zum Erzeugen gebunden an ein Fenster mit CreateForWindowId und zum Empfangen von High-Contrast-Änderungen über das Ereignis Changed, und dazu, dass das Freigeben der Referenz das Objekt zerstört und das Ereignis nicht mehr auslöst. 

  32. Microsoft Learn, SystemColors.WindowBrushKey Property. Dazu, dass eine dynamische Referenz mit dem Ressourcenschlüssel sich automatisch aktualisiert, wenn sich der Pinsel ändert, und dass eine statische Referenz über WindowBrush das nicht tut. 

  33. Microsoft Learn, ResourceDictionary.ThemeDictionaries Property (Microsoft.UI.Xaml). Dazu, dass ein benutzerdefiniertes Steuerelement mit Light- und Dark-Themenwörterbüchern auch ein HighContrast-Wörterbuch bereitstellen sollte, dass HighContrast der Rückfallschlüssel ist, wenn kein anderes High-Contrast-Thema existiert, dass Default verwendet wird, wenn kein ResourceDictionary für das Thema gefunden wird, und dass Systemfarbenressourcen wie SystemColorButtonFaceColor in HighContrast verwendet werden können. 

  34. Microsoft Learn, Windows app development best practices. Dazu, dass Windows 11 von reinem Weiß und reinem Schwarz zu Tönen wechselte, die den Augen leichter fallen, und dass die dunklen und hellen Themen ein Mittel sind, sich an die visuelle Vorliebe des Benutzers anzupassen. 

  35. Microsoft Learn, Color (Windows UX guidelines). Zur Verwendung von Farbe als visuelle Verstärkung statt als primäres Kommunikationsmittel, zur Wahl von Themenfarben und Systemfarben nach Zweck und zur Verwendung von Vordergrund und Hintergrund in passenden Paaren, zur Behandlung von Themenwechseln mit WM_THEMECHANGED und dazu, dass High Contrast Black unter Windows 11 Aquatic und High Contrast White Desert entspricht. 

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Ich habe Windows auf den dunklen Modus umgestellt, aber die Titelleiste unserer internen WinForms-App bleibt weiß. Warum?
Weil Windows keine Möglichkeit hat festzustellen, ob eine App den dunklen Modus unterstützt, und deshalb aus Kompatibilitätsgründen jedes Fenster standardmäßig als Hellmodus behandelt. Den Nichtclientbereich einschließlich der Titelleiste zeichnet der Desktop Window Manager (DWM), und der Rahmen wird nur dann dunkel gezeichnet, wenn das System dunkel ist, nachdem die App über DwmSetWindowAttribute TRUE für DWMWA_USE_IMMERSIVE_DARK_MODE (Wert 20) übergeben hat. Die Unterstützung dieses Attributs ist für Windows 11 Build 22000 und höher dokumentiert. Wenn Sie Application.SetColorMode in WinForms unter .NET 9 oder höher oder ThemeMode in WPF unter .NET 9 oder höher verwenden, übernimmt das Framework diesen Aufruf, sodass Sie ihn selbst nur in Apps unter .NET 8 oder früher, .NET Framework oder Win32/MFC brauchen. Beachten Sie, dass eine dunkle Titelleiste über einem Clientbereich, der weiß bleibt, schlechter wirkt, nicht besser. Aktivieren Sie dieses Attribut erst, wenn Sie bereit sind, die gesamte App dunkel neu zu zeichnen.
Sind dunkler Modus und Kontrastthemen (hoher Kontrast) dasselbe?
Nein. Hell/Dunkel ist der Farbmodus unter Einstellungen > Personalisierung > Farben und verwendet eine breite Palette, die die Helligkeit von Vordergrund und Hintergrund vertauscht. Ein Kontrastthema wird unter Einstellungen > Barrierefreiheit > Kontrastthemen gewählt und verwendet eine eingeschränkte Palette mit einem Kontrastverhältnis von grob 7:1 oder höher (die vier mitgelieferten Themen Aquatic, Desert, Dusk und Night sky sowie alle Farben, die der Benutzer bearbeitet hat). Die Microsoft-Dokumentation sagt ausdrücklich, die beiden nicht zu verwechseln, und der dunkle Modus ist nicht verfügbar, solange ein Kontrastthema aktiv ist (SetColorMode in WinForms stellt während eines Kontrastthemas keinen dunklen Modus bereit, und RequestedTheme in XAML wird vom System überschrieben). In der Implementierung prüfen Sie zuerst, ob ein Kontrastthema aktiv ist, und überlassen in dem Fall die Farben vollständig den Systemfarben; nur sonst wählen Sie die helle oder dunkle Palette. Das ist die Rangfolge.
Was ist der kürzeste Weg, eine WinForms-App für den dunklen Modus vorzubereiten?
Unter .NET 9 oder höher ist der kürzeste Weg, Application.SetColorMode(SystemColorMode.System) vor Application.Run in Program.cs aufzurufen. Unter .NET 9 war das ein experimentelles Feature, daher musste WFO5001 in der Projektdatei unterdrückt werden; ab .NET 10 funktioniert es ohne Unterdrückung. Der Aufruf von SetColorMode schaltet SystemColors auf einen alternativen Satz für den dunklen Modus um, und die Standardsteuerelemente werden entsprechend gezeichnet. Es gibt drei Vorbehalte. Erstens ist der dunkle Modus nur unter Windows 11 oder höher verfügbar und während eines Kontrastthemas deaktiviert. Zweitens folgt die App selbst mit SystemColorMode.System einer Änderung der Windows-Einstellung während der Laufzeit nicht (die Änderung greift beim nächsten Start). Drittens müssen Sie, wenn ein selbstgezeichnetes Steuerelement Win32-Common-Controls wie Bildlaufleisten verwendet, CreateParams überschreiben und SetStyle(ControlStyles.ApplyThemingImplicitly, true) vor base.CreateParams aufrufen (der Konstruktor kommt zu spät).
Was braucht eine WPF-App, um dem dunklen Modus zu folgen?
WPF unter .NET 9 oder höher liefert ein neues Thema, das dem Fluent-Design von Windows 11 folgt, und ThemeMode="System" am Application-Element in App.xaml reicht, um das Fluent-Thema zu laden, das zur Hell/Dunkel-Einstellung von Windows passt. ThemeMode steuert außerdem das Abdunkeln des Fensters (die Titelleiste) und das Anwenden des Hintergrundmaterials. Das Lesen und Schreiben der ThemeMode-Eigenschaft aus Code ist jedoch auch in .NET 10 noch experimentell (WPF0001), und die Fluent-Stile selbst werden in der .NET-10-Dokumentation als "still in progress" beschrieben. Bevor Sie es in einer Geschäftsanwendung übernehmen, bewerten Sie, ob die von Ihnen verwendeten Steuerelemente unter Fluent korrekt dargestellt werden. Bleiben Sie beim klassischen Thema (dasselbe gilt für .NET 8 und früher sowie für .NET Framework), bereiten Sie ein helles und ein dunkles ResourceDictionary vor, tauschen Sie sie in MergedDictionaries, referenzieren Sie sie aus XAML mit DynamicResource und nutzen Sie UISettings.ColorValuesChanged, um den Wechsel zu erkennen. Für die Titelleiste holen Sie den HWND in SourceInitialized über WindowInteropHelper und rufen DwmSetWindowAttribute auf.
Warum verschwindet Text unter einem Kontrastthema (hoher Kontrast) oder wird unleserlich?
Typische Ursachen sind fest eingecodierte Farben oder das Aufbrechen der Paarung von Vordergrund- und Hintergrund-Systemfarben. Unter einem Kontrastthema kann der Benutzer Hintergrund, Text, Link und andere Farben frei bearbeiten, sodass jede Annahme wie "Text ist schwarz" oder "die ausgewählte Zeile ist hellblau" zerfällt. Wenn etwa nur der Hintergrund auf #E6E6E6 festgesetzt ist, liefern manche Themen einen weißen Vordergrund, und weißer Text auf hellgrau wird unleserlich. Es gibt drei Grundsätze. Ermitteln Sie den Zustand mit SPI_GETHIGHCONTRAST (SystemInformation.HighContrast in WinForms, SystemParameters.HighContrast in WPF); ersetzen Sie jede Farbe durch das richtige Systemfarbenpaar (WindowText mit Window, ButtonText mit ButtonFace, HighlightText mit Highlight); und lassen Sie Bilder hinter Text und mehrfarbige Grafiken weg und zeichnen Sie nur mit Vordergrund- und Hintergrundfarbe. GrayText darf für nichts außer dem deaktivierten Zustand verwendet werden, Hotlight für nichts außer Hyperlinks. Änderungen werden durch WM_SYSCOLORCHANGE und WM_THEMECHANGED (SystemEvents.UserPreferenceChanged in .NET) gemeldet, dort berechnen Sie die Farben neu und zeichnen neu.

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.

Zurück zum Blog