Dark-Mode für Win32

Etwa gleichzeitig mit der Netflix-Serie Dark hat Microsoft in Windows 10 das Dunkle Design als Dark-Mode schrittweise eingeführt und mit jedem halbjährigen Update weiter ausgebaut.

Doch leider nur für UWP Apps und das pisst mich offen gesagt sehr an. Was bitte ist so schwer, dieses Design auch in die klassische WinAPI zu übernehmen?


Seit Windows 3.1 kenne ich Windows so, dass man die Farben aller Elemente der UI einstellen kann. Und obwohl bereits Windows XP und seine Nachfolger mit “Themes” in dieses klassische Feature reingepfuscht haben, war es bis Windows 7 einigermaßen möglich, die UI-Farben nach Wunsch anzupassen und alle “guten” Programme konnten diese Einstellungen auslesen und anzeigen.

Und dann kam die unseelige Trennung von Win32 und UWP mit Windows 8 und die beiden Darstellungen drifteten auseinander. Das mag an einigen Stellen auch gut gewesen sein und die Reduktion von “Man kann alles einstellen” hin zu “man kann zwischen ein paar Designs wählen” ist eigentlich ein guter Gedanke.

Nun sind wir in Windows 10 mit zwei Designs angekommen, nämlich “Hell” (Light) und “Dunkel” (Dark). Und offen gesagt finde ich das wirklich gut. Keine selbst fabrizierten “grünen” oder “roten” Fensterrahmen, sondern nur noch zwei gut abgestimmte Farbklänge.

Und dann vergisst man diese auf Win32 zu übertragen? Warum ??

Explorer Theme ist ein “bisschen schwarz”

SetWindowTheme(hwnd, L"Explorer", NULL); hätte eigentlich “die Lösung” sein können. Bzw. manchmal findet man auch den Token DarkMode_Explorer im Netz. Denn wenn man diese Funktion für jedes Fenster und jedes Control aufruft, dann müssten doch alle so aussehen, wie der Explorer, und der passt sich ja neuerdings brav an den Dark-Mode an.

Leider kommt damit nur an einigen Stellen plötzlich ein bisschen schwarze Farbe zum Vorschein. So werden z.B.: die Scrollbalken in diversen Controls plötzlich dunkel, während der restliche Hintergrund hell bleibt.

Undokumentierte uxtheme.dll APIs

Im GitHub Project “win32-darkmode” werden für die Verdunkelung einige nicht benannte Funktionen aus uxtheme.dll über ihre fixe ID importiert. Man nutzt also
FARPROC proc = GetProcAddress(hmodule, MAKEINTRESOURCEA(ordinal)); zum Laden.

Ordinal Prototype
49 HTHEME OpenNcThemeData(HWND hWnd, LPCWSTR pszClassList)
94 DWORD GetImmersiveColorSetCount(void)
95 DWORD GetImmersiveColorFromColorSetEx(UINT dwImmersiveColorSet, UINT dwImmersiveColorType, bool bIgnoreHighContrast, UINT dwHighContrastCacheMode)
96 DWORD GetImmersiveColorTypeFromName(const WCHAR *name)
98 DWORD GetImmersiveUserColorSetPreference(bool bForceCheckRegistry, bool bSkipCheckOnFail)
100 WCHAR** GetImmersiveColorNamedTypeByIndex(UINT dwImmersiveColorType)
104 void RefreshImmersiveColorPolicyState(void)
106 BOOL GetIsImmersiveColorUsingHighContrast(IMMERSIVE_HC_CACHE_MODE mode)
132 BYTE ShouldAppsUseDarkMode(void)
133 BOOL AllowDarkModeForWindow(HWND hWnd, BOOL allow)
135 BOOL AllowDarkModeForApp(BOOL allow)
135 PREFERREDAPPMODE SetPreferredAppMode(PREFERREDAPPMODE appMode)
136 VOID FlushMenuThemes(void)
137 BOOL IsDarkModeAllowedForWindow(HWND hWnd)
138 BOOL ShouldSystemUseDarkMode(void)
139 BOOL IsDarkModeAllowedForApp(void)

Und dann schalten APIs wie SetPreferredAppMode() und AllowDarkModeForWindow() (theoretisch) die App oder das Fenster in den Dark-Mode um, falls ShouldSystemUseDarkMode() diesen auch als aktiviert meldet.
Ordinal 135 hat dabei schon einen Wandel erlebt, weil es vor Windows 10/19H1 (Build 18362) als AllowDarkModeForApp() nur einen Schalter umlegte und ab 19H1 mit SetPreferredAppMode() per Enumeration mehr Optionen anbot.

Das findet man aber alles unter github.com/ysc3839/win32-darkmode.

Doch leider reicht das immer noch nicht. Denn nun hat man zwar dunkle Buttons und dunkle Menüeinträge, doch die meisten Controls und die Menüzeile selbst bleiben weiß bzw. klassisch.

Eigene Farbanpassungen tun den Rest

Meine vorläufige Lösung sieht daher so aus (wie auch oft im Netz beschrieben), dass man per IsDarkModeAllowedForWindow() fragt, ob das Fenster in den Dark-Mode versetzt wurde und dann wird in der WNDPROC beim Zeichnen eine andere Text- und Hintergrundfarbe genutzt.

Windows hat das schon seit seinen Anfängen vorgesehen und mit Messages wie WM_CTLCOLORBTN, WM_CTLCOLOREDIT, WM_CTLCOLORLISTBOX, WM_CTLCOLORSCROLLBAR WM_CTLCOLORMSGBOX, WM_CTLCOLORDLG und WM_CTLCOLORSTATIC kann man die Standardfarben von Controls in seinem Elternfenster abändern.
Man setzt also quasi im Fensterrahmen-Code die Farben der Kindelemente.

Wenn man das nun auch noch mit den “richtigen” Farben des Dark-Mode tut (siehe GetImmersiveColorFromColorSetEx()), dann kommt man den Farben des dunklen Windows Explorers schon recht nahe. Nur die fu**ing Menüzeile bleibt leider weiter weiß.
Welche Farben jetzt aber die “richtigen” sind, habe ich noch nicht klar herausbekommen, denn die Text-Token sind alles andere als eindeutig. In einem Forum-Artikel liegt zwar ein schöner Demo-Code um alle aufzulisten, aber was daraus jetzt eine “richtige” Listbox darstellt ist nicht dokumentiert.

Dark-Mode vTxtEdit

Andere Programme definieren das offenbar auch selbst und arbeiten generell nur mit der öffentlichen Theme-API. So können sie den Dark-Mode auch in Windows 7 selbst implementieren und per Programm-Option ein und ausschalten.
Das ist eigentlich auch ein interessanter Ansatz, aber es sieht dann zwangsläufig auch etwas anders aus im Vergleich zum System-Standard.

Fazit

Wie schon gesagt … ich bin angepisst.

Denn eben wegen solchen fehlenden APIs bringt man Entwickler dazu, ihre eigene Variante nachzubauen … und die wird dann immer schlecht gewartet und produziert im Nachgang Fehler.

Es wäre für MS leicht gewesen, den Dark-Mode auch auf die Standard-Controls anzuwenden, am besten mit dem bekannten “Explorer” Theme. Aber nein.

Und warum sind APIs wie AllowDarkModeForWindow() undokumentiert und nicht Teil der offiziellen API?
Ich verstehe es einfach nicht.

📧 📋 🐘 | 🔔
 

Meine Dokus über:
 
Weitere externe Links zu:
Alle extern verlinkten Webseiten stehen nicht in Zusammenhang mit opengate.at.
Für deren Inhalt wird keine Haftung übernommen.



Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!