Mode sombre et thèmes de contraste dans les applications Windows — barres de titre DWM sombres, suivi du thème système dans WinForms/WPF et dessin en contraste élevé
· Mis à jour le: · Go Komura · Mode sombre, Thèmes de contraste, Contraste élevé, DWM, WinForms, WPF, Windows 11, Accessibilité, Applications métier, Windows
Historique des révisions (première version, publiée le 2 Sep 2026)
- Première publication
« Nous avons remplacé les PC du bureau par Windows 11, et les salariés qui utilisent le mode sombre se plaignent que seule notre application métier a une barre de titre d’un blanc éblouissant. » « Un salarié malvoyant a activé un thème de contraste, et l’affichage d’état de l’écran de saisie de commande a disparu. » Ce sont deux plaintes que nous entendons plus souvent depuis un an ou deux.
La première vient du paramètre Couleurs de Windows 11, la seconde des paramètres d’accessibilité, mais pour un développeur elles ressemblent au même problème : l’application ne suit pas le thème. Et de fait, le fondement de la correction est commun. Ne pas coder les couleurs en dur ; lire le paramètre système, remarquer les changements, et repeindre. Ces trois points.
L’article précédent, « Introduction à l’accessibilité des applications Windows », traitait de la façon dont les lecteurs d’écran lisent une application (UI Automation) et des bases du nommage, du fonctionnement au clavier et de la couleur. Il abordait le suivi des thèmes de contraste, mais pas le mode de couleur clair/sombre lui-même. Cet article en est le compagnon. Il relie les deux axes du mode de couleur (clair/sombre) et des thèmes de contraste au niveau de l’implémentation, dans cet ordre : dessin de la barre de titre par DWM (Gestionnaire de fenêtres du Bureau), suivi du thème système dans WinForms/WPF, et dessin sous un thème de contraste. Le public visé est les développeurs qui construisent et maintiennent des applications métier en WinForms, WPF ou Win32. Les prérequis sont Windows 11 (les barres de titre sombres exigent la build 22000 ou une version ultérieure) et WinForms/WPF sous .NET 9/10 (sous .NET Framework 4.8 et .NET 8, certaines parties doivent être implémentées à la main). Le niveau de difficulté est intermédiaire.
flowchart TB
accTitle: Le déroulement de cet article
accDescr: La structure de cet article, qui relie dans l'ordre les deux axes de thème, pourquoi les fenêtres sont claires par défaut, les barres de titre DWM sombres, le mécanisme de détection et de suivi, les implémentations WinForms et WPF, le dessin sous un thème de contraste, le choix d'une politique, et la vérification
axes["Clarifier les deux axes de thème"] --> why["Pourquoi le défaut est clair"]
why --> dwm["Barre de titre DWM sombre"]
dwm --> detect["Détection et suivi"]
detect --> impl["Implémentation WinForms/WPF"]
impl --> hc["Dessin sous un thème de contraste"]
hc --> policy["Choisir une politique et vérifier"]
Figure 1 : Cet article va en une ligne du tri des thèmes jusqu’aux mécanismes, à l’implémentation, aux thèmes de contraste et à la vérification.
1. D’abord la conclusion
- Windows a deux axes de thème. Clair/sombre (le mode de couleur) sous Paramètres > Personnalisation > Couleurs, et Paramètres > Accessibilité > Thèmes de contraste. Le second est une palette contrainte à un ratio de contraste d’environ 7:1 ou plus, et c’est autre chose que clair/sombre. Le mode sombre n’est pas disponible tant qu’un thème de contraste est actif. L’ordre de priorité de la détection est « thème de contraste, puis clair/sombre ».12
- La barre de titre d’une application existante reste blanche parce que c’est le défaut de compatibilité. Windows n’a aucun moyen de savoir si une application prend en charge le mode sombre, donc il traite chaque fenêtre comme claire par défaut.3
- Ce qui rend la barre de titre sombre, c’est
DWMWA_USE_IMMERSIVE_DARK_MODE(valeur 20) viaDwmSetWindowAttribute. Passez un BOOL TRUE et le cadre est dessiné en sombre lorsque le système est sombre. La prise en charge documentée est Windows 11 build 22000 ou une version ultérieure.43 - Lisez le mode actuel avec
UISettings.GetColorValueet recevez les changements viaColorValuesChanged. La procédure officielle de Microsoft est : si le premier plan (la couleur de texte par défaut) est clair, le mode est sombre. L’événement n’est pas garanti d’arriver sur le thread UI, donc remontez vers l’UI avant de repeindre.35 - WinForms a reçu
Application.SetColorModedans .NET 9, et cela a cessé d’être expérimental dans .NET 10. Appelez-le avecSystemColorMode.SystemavantApplication.Run. Il a trois contraintes : Windows 11 uniquement, désactivé tant qu’un thème de contraste est actif, et pas de suivi des changements de paramètre pendant l’exécution.672 - WPF a reçu le thème Fluent et
ThemeModedans .NET 9.ThemeMode="System"suit le système et contrôle aussi l’assombrissement de la fenêtre. Cependant, la manipulation depuis le code reste expérimentale dans .NET 10 (WPF0001), et les styles Fluent sont « in progress ». Si vous restez sur le thème classique, échangez des ResourceDictionary clairs et sombres référencés via DynamicResource.8910 - Sous un thème de contraste, mappez les couleurs vers les paires de couleurs système correctes, omettez les images derrière le texte, et dessinez les graphismes multicolores dans les deux couleurs de premier plan et d’arrière-plan. Détectez avec
SPI_GETHIGHCONTRAST(SystemInformation.HighContrastdans WinForms,SystemParameters.HighContrastdans WPF) ; les notifications sontWM_SYSCOLORCHANGE/WM_THEMECHANGED(SystemEvents.UserPreferenceChangeddans .NET).111213 - La prise en charge du mode sombre n’est pas un substitut à la prise en charge de l’accessibilité. Une palette sombre a toujours besoin d’un ratio de contraste de 4,5:1, et transmettre l’information par plus que la couleur seule est requis sous tout thème.1415
En une phrase : la prise en charge des thèmes, c’est rassembler les couleurs en un seul endroit, lire le paramètre système, remarquer les changements et repeindre ; mais sous un thème de contraste, se remettre entièrement aux paires de couleurs système.
Dans le diagramme, un trait continu marque une relation qui vaut toujours et un trait pointillé une relation conditionnelle (les conditions figurent dans l’explication de chaque relation sur la page de détail). La liste complète des relations (28 au total, avec preuve et niveau de certitude) et les définitions des concepts principaux sont rassemblées sur la page de détail de la carte des connaissances (en japonais). Données : JSON-LD / Turtle
2. Le « thème » a deux axes — clair/sombre et thèmes de contraste
2.1. Clair/sombre (mode de couleur)
Le mode de couleur sous Paramètres > Personnalisation > Couleurs dans Windows est le paramètre qui décide de la luminosité du premier plan et de l’arrière-plan à travers le système d’exploitation et toutes les applications. La documentation Microsoft définit le clair comme « un premier plan sombre sur un arrière-plan clair » et le sombre comme « un premier plan clair sur un arrière-plan sombre », en ajoutant que premier plan signifie ici « la couleur de texte par défaut ». En mode sombre, le premier plan (le texte) est clair et l’arrière-plan est sombre.3
Le paramètre est stocké dans le registre sous HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize comme les valeurs DWORD AppsUseLightTheme (le mode de l’application) et SystemUsesLightTheme (le mode de Windows lui-même), et il figure dans la référence des paramètres de Microsoft.16 Comme décrit plus bas, cependant, le moyen canonique de le lire depuis une application est la classe WinRT UISettings.
2.2. Thèmes de contraste (contraste élevé)
Un thème de contraste, choisi sous Paramètres > Accessibilité > Thèmes de contraste, utilise une palette contrainte à un ratio de contraste d’environ 7:1 ou plus, et existe pour les utilisateurs qui ont besoin d’une séparation visuelle forte du premier plan et de l’arrière-plan. Windows 11 en a quatre intégrés, Aquatic, Desert, Dusk et Night sky, et l’utilisateur peut non seulement en choisir un, mais aussi éditer individuellement les couleurs d’arrière-plan, de texte, d’hyperlien, de texte désactivé, de texte sélectionné et de bouton. Alt gauche + Maj gauche + Impr. écran bascule rapidement un thème de contraste, et Aquatic est appliqué si aucun n’a été sélectionné.1
La documentation Microsoft dit clairement : « ne confondez pas les thèmes de contraste avec les thèmes clairs et sombres ». Clair/sombre utilise une palette large et n’est pas optimisé pour un contraste maximal.1 Et le point important est que le mode sombre n’est pas disponible tant qu’un thème de contraste est actif. Application.SetColorMode de WinForms ne fournit pas le mode sombre pendant un thème de contraste, et RequestedTheme de XAML est remplacé par le système.217
2.3. L’ordre de priorité
L’implémentation dans une application suit donc cet ordre. Décidez d’abord si un thème de contraste est actif ; si c’est le cas, remettez-vous entièrement aux couleurs système. Sinon, choisissez soit la palette claire, soit la palette sombre.
flowchart TB
accTitle: Les deux axes de thème et l'ordre de priorité
accDescr: L'ordre de décision dans lequel l'application se remet entièrement aux paires de couleurs système si un thème de contraste est actif, et sinon lit le mode de couleur clair/sombre et choisit la palette de l'application
q1{"Thème de contraste actif ?"}
q1 -->|Oui| sys["Se remettre aux paires de couleurs système"]
q1 -->|Non| q2{"Mode de couleur ?"}
q2 -->|Clair| light["Palette claire"]
q2 -->|Sombre| dark["Palette sombre"]
sys -.-> note["Le mode sombre n'est pas disponible"]
Figure 2 : Placez la décision de thème de contraste en premier, et ne choisissez la palette claire ou sombre que lorsqu’aucun thème de contraste n’est actif.
3. Pourquoi les applications existantes restent blanches en mode sombre
Une fenêtre se compose de deux zones : la zone non cliente, formée de la barre de titre, du cadre et des boutons de légende, et la zone cliente, que l’application dessine. Depuis Windows Vista, la zone non cliente est composée et dessinée par DWM (Gestionnaire de fenêtres du Bureau), et l’application spécifie des attributs de la façon dont elle est dessinée via DwmSetWindowAttribute.18
La documentation Microsoft explique franchement pourquoi les applications existantes restent blanches. « Windows ne sait pas si une application peut prendre en charge le mode sombre, donc elle suppose qu’elle ne le peut pas, pour des raisons de compatibilité descendante. » Des frameworks comme WinUI et le Windows App SDK gèrent le mode sombre nativement, mais les applications Win32 ne prennent généralement pas en charge le mode sombre, donc Windows leur donne une barre de titre claire par défaut.3
flowchart TB
accTitle: Les deux zones d'une fenêtre et qui les dessine
accDescr: La zone non cliente formée de la barre de titre et du cadre est dessinée par DWM, et la zone cliente est dessinée par l'application ou le framework UI, donc la prise en charge du mode sombre est nécessaire dans les deux
win["Fenêtre de premier niveau"] --> nc["Zone non cliente (barre de titre, cadre)"]
win --> client["Zone cliente (le contenu de la fenêtre)"]
nc --> dwm["Composée et dessinée par DWM"]
client --> app["Dessinée par l'application ou le framework"]
dwm -.-> attr["Indiquée via DwmSetWindowAttribute"]
app -.-> palette["La palette propre de l'application"]
Figure 3 : DWM dessine la barre de titre et l’application dessine le contenu, donc la prise en charge du mode sombre a besoin à la fois d’une instruction à DWM et de la palette de l’application.
Deux conséquences s’ensuivent. Premièrement, pour rendre la barre de titre sombre, l’application doit le demander explicitement à DWM. Deuxièmement, ce qui devient sombre du fait de la demande n’est que la barre de titre ; la zone cliente doit être repeinte par l’application elle-même. La documentation dit aussi que « pour prendre pleinement en charge le mode sombre, toute la surface de l’application doit suivre le thème sombre », et note que le guide officiel ne couvre que la détection et la barre de titre, pas comment repeindre la zone cliente.3 Une application avec une barre de titre noire et un contenu blanc paraît moins naturelle qu’une qui reste blanche de bout en bout.
flowchart TB
accTitle: Comment le défaut devient clair
accDescr: Windows ne sait pas si une application prend en charge le mode sombre, donc pour la compatibilité le défaut est clair, et seulement lorsque l'application passe TRUE via l'attribut DWM il dessine le cadre selon le paramètre sombre du système
unknown["Windows ne peut pas dire si l'application le prend en charge"] --> def["Le défaut est clair, pour la compatibilité"]
def --> q{"L'application a-t-elle passé TRUE ?"}
q -->|Non| light["Toujours un cadre clair"]
q -->|Oui| follow["Dessiné selon le paramètre système"]
Figure 4 : Ne sachant pas si l’application le prend en charge, Windows met le défaut au clair et ne suit le système que lorsque l’application le dit explicitement.
4. La barre de titre DWM sombre — DwmSetWindowAttribute
4.1. DWMWA_USE_IMMERSIVE_DARK_MODE
L’attribut qui rend la barre de titre sombre est DWMWA_USE_IMMERSIVE_DARK_MODE. L’énumération DWMWINDOWATTRIBUTE le décrit ainsi : « Permet au cadre de cette fenêtre d’être dessiné dans les couleurs du mode sombre lorsque le paramètre système du mode sombre est activé. Pour des raisons de compatibilité, toutes les fenêtres sont par défaut en mode clair indépendamment du paramètre système. Le paramètre pvAttribute pointe vers une valeur de type BOOL. TRUE pour honorer le mode sombre pour la fenêtre, FALSE pour toujours utiliser le mode clair. Cette valeur est prise en charge à partir de Windows 11 Build 22000. »4
Autrement dit, TRUE ne signifie pas « rends-le sombre » ; c’est une permission qui dit « tu peux le dessiner sombre si le système est sombre ». Si l’application est prête à peindre sa zone cliente en sombre, passer TRUE suffit pour que la barre de titre suive le paramètre système. Inversement, si l’application est conçue pour toujours s’afficher en mode clair (la politique « clair fixe » décrite plus loin), laisser le défaut FALSE convient.
Le code C++ du guide officiel a la forme suivante. Il inclut même l’étape de définir soi-même la valeur 20 pour les SDK plus anciens dont les en-têtes n’ont pas la constante.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) ou version ultérieure ? Suppose un manifeste qui déclare supportedOS
// pour Windows 10 ou une version ultérieure (sans un, la version est arrondie vers le bas à Windows 8)
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 : la barre de titre peut être dessinée sombre lorsque le système est sombre
void ApplyTitleBarTheme(HWND hwnd, bool honorDarkMode)
{
if (!IsWindows11OrGreater())
{
// La prise en charge documentée est la build 22000 ou une version ultérieure. En dessous, ne pas l'appeler et suivre le défaut (clair)
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))
{
// Un échec sur un OS pris en charge est anormal. Ne pas l'avaler en silence ; enregistrer le HRESULT et le faire apparaître
LogWarning(L"DwmSetWindowAttribute(DWMWA_USE_IMMERSIVE_DARK_MODE) failed: 0x%08X", hr);
}
}
Un mot sur la raison pour laquelle l’appel est conditionné à la version de l’OS. La prise en charge documentée est Windows 11 build 22000 ou une version ultérieure.4 Les rapports selon lesquels la même valeur fonctionne sous Windows 10 ne sont pas rares, mais l’affichage d’une application métier ne devrait pas dépendre d’un comportement non documenté. Si la conception est « essayer l’appel et abandonner en cas d’échec », alors lorsque l’appel réussit par hasard sous Windows 10, la barre de titre devient sombre par-dessus un comportement non documenté. En dessous de la build 22000, ne pas l’appeler et suivre le défaut documenté d’une barre de titre claire ; sur un OS pris en charge, consigner le HRESULT d’un échec et le faire apparaître. C’est tout. Internet fait aussi circuler une procédure plus ancienne qui utilise la valeur 19, et une technique qui appelle des exportations ordinales de uxtheme.dll pour assombrir les contrôles communs, mais les deux sont des API non documentées, et personne ne les garantit lorsqu’une mise à jour change leur comportement.
4.2. Quand l’appeler — tant que le HWND est vivant, et chaque fois qu’il est recréé
DwmSetWindowAttribute s’appelle sur un HWND, donc il doit s’exécuter après que le handle de fenêtre a été créé. Et un formulaire WinForms peut avoir son handle recréé, par exemple lorsque ShowInTaskbar change. Le nouveau HWND après recréation ne porte aucun attribut, donc l’endroit où l’appeler n’est pas le constructeur mais l’endroit qui s’exécute chaque fois qu’un handle est créé : OnHandleCreated dans WinForms, SourceInitialized dans WPF.
flowchart TB
accTitle: Quand appeler DwmSetWindowAttribute
accDescr: Définir l'attribut DWM après la création du handle de fenêtre, le définir à nouveau sur le nouveau handle lorsque le handle est recréé, et le définir à nouveau lorsqu'une notification de changement de thème arrive
create["HWND créé"] --> apply["Définir l'attribut DWM"]
apply --> run["Affiché"]
run -->|Handle recréé| create
run -->|Notification de changement de thème| apply
Figure 5 : L’attribut DWM est lié au HWND, donc définissez-le à nouveau à chaque création et à chaque recréation.
Le P/Invoke dans WinForms ressemble à ceci (pour la façon sûre d’écrire DllImport, voir « Appeler les API Win32 en toute sécurité depuis C# »).
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()
{
// La prise en charge documentée est la build 22000 ou une version ultérieure. En dessous, ne pas l'appeler et suivre le défaut (clair).
// Cette vérification fonctionne aussi sous .NET Framework. Sous .NET Framework, cependant, sans un manifeste qui
// déclare supportedOS pour Windows 10 ou une version ultérieure, la version est arrondie vers le bas à Windows 8
// (sous .NET 5 ou une version ultérieure, OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) fonctionne aussi)
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) = peut être dessiné sombre lorsque le système est sombre. 0 (FALSE) = toujours clair
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);
}
}
}
Ce code est nécessaire aux applications sous .NET 8 ou antérieur, .NET Framework, et Win32/MFC. Lorsque vous utilisez Application.SetColorMode dans WinForms sous .NET 9 ou une version ultérieure, ou ThemeMode dans WPF sous .NET 9 ou une version ultérieure, le framework prend en charge l’assombrissement de la fenêtre (la documentation de ThemeMode indique qu’il « contrôle aussi l’application du matériau d’arrière-plan et du mode sombre à la fenêtre »).10 L’appeler deux fois ne nuit pas, mais cela brouille qui est responsable, donc choisissez l’un ou l’autre.
Dans WPF, le HWND est fixé à SourceInitialized. Obtenez le handle depuis 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); // le corps est le P/Invoke montré plus haut
}
}
4.3. Couleur de barre de titre, couleur de texte, couleur de bordure et matériau d’arrière-plan
Windows 11 a ajouté des attributs qui spécifient la couleur de la barre de titre elle-même, au-delà du choix binaire sombre ou clair.
| Attribut | Valeur | Signification | Build prise en charge |
|---|---|---|---|
DWMWA_USE_IMMERSIVE_DARK_MODE |
20 | Dessiner le cadre en sombre lorsque le système est sombre (BOOL) | 22000 |
DWMWA_BORDER_COLOR |
34 | Couleur de la bordure de fenêtre (COLORREF). DWMWA_COLOR_NONE retire la bordure |
22000 |
DWMWA_CAPTION_COLOR |
35 | Couleur de la barre de titre (COLORREF) | 22000 |
DWMWA_TEXT_COLOR |
36 | Couleur du texte de titre (COLORREF) | 22000 |
DWMWA_SYSTEMBACKDROP_TYPE |
38 | Matériau d’arrière-plan dessiné par le système (Mica ou Acrylic) | 22621 |
Pour les trois attributs de couleur, passer DWMWA_COLOR_DEFAULT (0xFFFFFFFF) rétablit le défaut système. Notez que pour la couleur de bordure, « il incombe à l’application de changer la couleur en réponse aux changements d’état tels que l’activation de la fenêtre ».4 Le matériau d’arrière-plan se spécifie avec l’énumération DWM_SYSTEMBACKDROP_TYPE ; sous Windows 11, DWMSBT_MAINWINDOW correspond à Mica et DWMSBT_TRANSIENTWINDOW à Acrylic, mais la documentation indique que « l’effet du matériau peut changer dans les versions futures de Windows ».20
Réfléchissez soigneusement à la place que cela occupe dans une application métier. Une fois que vous peignez la barre de titre dans une couleur de marque, vous devenez responsable de garantir le contraste du texte de titre et des boutons de légende sur cette couleur. Aux deux états sombre et clair s’ajoutent les combinaisons avec actif et inactif. Pour la plupart des applications métier, la bonne réponse est « suivre le défaut système (se contenter de mettre la valeur 20 à TRUE) », et une couleur de marque est une option pour lorsqu’elle est vraiment nécessaire.
flowchart TB
accTitle: Comment décider la couleur de la barre de titre
accDescr: Suivre le défaut système n'exige que de mettre la valeur 20 à TRUE, mais peindre une couleur de marque rend l'application responsable de garantir le contraste du texte et des boutons de légende et de gérer les couleurs actives et inactives, et rétablir passe DWMWA_COLOR_DEFAULT
q{"Couleur de la barre de titre ?"}
q -->|Suivre le défaut système| dark["Se contenter de mettre la valeur 20 à TRUE"]
q -->|Peindre une couleur de marque| brand["Spécifier les attributs de couleur (34 à 36)"]
brand --> resp["Garantir soi-même le contraste du texte et des boutons"]
brand --> states["Gérer soi-même actif/inactif"]
brand -.-> reset["Rétablir avec COLOR_DEFAULT"]
Figure 6 : Choisir une couleur de marque déplace la responsabilité du contraste et de la gestion d’état vers l’application, donc pour la plupart des applications métier suivre le défaut est la bonne réponse.
5. Détecter et suivre le thème système — lire, remarquer, repeindre
Le travail de faire suivre le thème à la zone cliente se décompose en trois parties : lire le paramètre actuel, remarquer les changements, et repeindre.
flowchart TB
accTitle: Les trois étapes de la détection et du suivi
accDescr: La boucle de lecture du mode de couleur actuel avec UISettings au démarrage, de remarque des changements via des notifications telles que ColorValuesChanged, de remontée vers le thread UI, et de repeinture de la palette de l'application
read["Lire : UISettings.GetColorValue"] --> paint["Repeindre : réappliquer la palette"]
notice["Remarquer : ColorValuesChanged"] --> ui["Remonter vers le thread UI"]
ui --> read
paint -.-> dwm["Redéfinir aussi l'attribut DWM"]
Figure 7 : Lire au démarrage, remarquer via les notifications, remonter vers le thread UI, et repeindre : cette boucle est le squelette du suivi de thème.
5.1. Lire — UISettings et « si le premier plan est clair, c’est sombre »
La procédure officielle de Microsoft utilise la classe WinRT Windows.UI.ViewManagement.UISettings. Obtenez la couleur de premier plan (la couleur de texte par défaut) avec GetColorValue(UIColorType::Foreground), estimez sa luminance perçue par une arithmétique entière pour décider si elle est « claire », et concluez mode sombre si le premier plan est clair. La documentation note que la formule n’est pas un modèle de luminance rigoureux, seulement une approximation suffisante pour classer clair et sombre.321
UISettings est une classe WinRT, mais les applications C# WPF et WinForms peuvent l’appeler directement si le TargetFramework porte une version de SDK Windows, telle que net8.0-windows10.0.19041.0 (pour le fonctionnement, voir « WinRT est COM »). Vous pourriez aussi lire AppsUseLightTheme directement dans le registre, mais le registre est l’endroit où le paramètre est stocké, pas un contrat d’API ; si vous le lisez, traitez UISettings comme la source de vérité et gardez le registre pour le diagnostic.
flowchart TB
accTitle: Moyens de lire le mode de couleur
accDescr: Le chemin canonique est d'obtenir la couleur de premier plan via WinRT UISettings et de la classer comme claire ou sombre ; la valeur de registre AppsUseLightTheme est l'emplacement de stockage et devrait être gardée pour le diagnostic
q["Clair ou sombre en ce moment ?"] --> uis["UISettings.GetColorValue"]
q -.-> reg["Registre AppsUseLightTheme"]
uis --> fg["Juger la luminance perçue du premier plan"]
fg --> ans["Clair signifie mode sombre"]
reg -.-> diag["Emplacement de stockage. Garder pour le diagnostic"]
Figure 8 : Le chemin de lecture canonique est UISettings ; le registre n’est que l’endroit où le paramètre est stocké.
5.2. Remarquer — ColorValuesChanged n’arrive pas sur le thread UI
UISettings sert aussi à détecter les changements. L’événement ColorValuesChanged se déclenche lorsqu’une valeur de couleur change, et le guide officiel utilise cet événement pour suivre les changements de paramètre.53 Une précaution pratique s’applique ici. Cet événement n’est pas garanti d’arriver sur le thread UI. Remontez vers le thread UI avec le Dispatcher de WPF, Control.Invoke de WinForms, ou le SynchronizationContext qui fonctionne dans les deux, avant de toucher un contrôle. Le travail avec le thread UI est résumé dans « WPF/WinForms : async et le thread UI récapitulés en une fiche ».
L’événement se déclenche aussi lorsque la couleur d’accent change. Si vous voulez ne repeindre que lorsque clair/sombre change, réévaluez à chaque événement et notifiez seulement lorsque le résultat diffère de la dernière fois. Et, selon l’ordre de priorité du chapitre 2, ne regardez pas la luminosité du premier plan tant qu’un thème de contraste est actif. Un thème de contraste avec un arrière-plan sombre tel qu’Aquatic a un premier plan clair, donc la luminosité seule le classerait à tort comme « sombre ». Traitez le thème de contraste comme un état indépendant et décidez-le d’abord.
La classe suivante rassemble cet ordre de décision en un seul endroit. Le SynchronizationContext utilisé pour remonter vers le thread UI et la vérification de thème de contraste (SystemInformation.HighContrast dans WinForms, SystemParameters.HighContrast dans WPF ; voir le chapitre 8) sont passés par l’appelant. Attention au moment de la création. Dans WinForms, au point de Program.Main il n’y a pas encore de boucle de messages ni de Control, donc SynchronizationContext.Current est null. Passez SynchronizationContext.Current après que les contrôles existent, par exemple depuis le constructeur du formulaire ou OnLoad. Dans WPF vous pouvez passer new DispatcherSynchronizationContext(Application.Current.Dispatcher).
using Windows.UI.ViewManagement; // TargetFramework : net8.0-windows10.0.19041.0 ou version ultérieure
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 : le SynchronizationContext du thread UI. Passez SynchronizationContext.Current
// après que les contrôles existent, ou un DispatcherSynchronizationContext dans 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()
{
// Ordre de décision comme au chapitre 2 : thème de contraste d'abord. Un thème de contraste avec un
// arrière-plan sombre a un premier plan clair, donc la luminosité seule le classerait à tort comme « sombre »
if (_isHighContrast()) return ThemeState.HighContrast;
// Même test que le guide officiel : sombre si le premier plan (couleur de texte par défaut) est clair
var fg = _settings.GetColorValue(UIColorType.Foreground);
bool isDark = (5 * fg.G + 2 * fg.R + fg.B) > 8 * 128;
return isDark ? ThemeState.Dark : ThemeState.Light;
}
// Appeler depuis le thread UI. Peut aussi être appelé depuis d'autres chemins de notification tels que UserPreferenceChanged
public void Refresh()
{
if (_disposed) return;
var next = Read();
// Entre clair et sombre, ne pas notifier lorsque l'état est inchangé, pour ignorer les changements de seule couleur d'accent.
// Pendant un thème de contraste c'est l'exception : l'état reste HighContrast même lorsque l'utilisateur édite
// les couleurs du thème, donc notifier même pour le même état afin que les couleurs système soient relues
if (next == Current && next != ThemeState.HighContrast) return;
Current = next;
Changed?.Invoke(this, EventArgs.Empty);
}
private void OnColorValuesChanged(UISettings sender, object args)
{
// Non garanti d'arriver sur le thread UI, donc remonter vers l'UI avant de décider et de notifier.
// Un appel qui arrive après Dispose (déjà en file) est ignoré par le drapeau dans Refresh
_ui.Post(_ => Refresh(), null);
}
public void Dispose()
{
// Se désabonner n'arrête que les livraisons futures ; les appels déjà postés au thread UI restent.
// Mettre le drapeau pour que les appels restants s'ignorent eux-mêmes (appeler ceci sur le thread UI)
_disposed = true;
_settings.ColorValuesChanged -= OnColorValuesChanged;
}
}
Au niveau Win32, WM_SETTINGCHANGE est envoyé à chaque fenêtre de premier niveau lorsqu’un paramètre change,22 et dans .NET il arrive comme SystemEvents.UserPreferenceChanged.23 Un basculement de style visuel (y compris l’activation d’un thème de contraste) apporte WM_THEMECHANGED,24 et un changement de couleur système apporte WM_SYSCOLORCHANGE.25 Plutôt que d’écrire un traitement distinct pour chaque type de notification, appelez la même routine « lire et repeindre » quelle que soit la notification qui arrive ; c’est plus difficile à casser, et cela a la même forme que le suivi de thème de contraste présenté dans l’article précédent. Du point de vue du SystemThemeWatcher ci-dessus, cela signifie d’appeler aussi Refresh() depuis les gestionnaires UserPreferenceChanged et StaticPropertyChanged.
flowchart TB
accTitle: Chemins et threads de notification de changement de thème
accDescr: ColorValuesChanged de UISettings peut arriver hors du thread UI et est remonté, WM_SETTINGCHANGE arrive comme SystemEvents.UserPreferenceChanged, et WM_THEMECHANGED et WM_SYSCOLORCHANGE arrivent à la procédure de fenêtre. Tous convergent vers la même routine de réapplication
cvc["ColorValuesChanged"] --> marshal["Remonter vers le thread UI"]
upc["UserPreferenceChanged"] --> reapply["Lire et repeindre"]
wm["WM_THEMECHANGED etc."] --> reapply
marshal --> reapply
Figure 9 : Il y a plusieurs chemins de notification, mais tous convergent vers la même routine « lire et repeindre ».
5.3. Repeindre — rassembler les couleurs en un seul endroit
La condition préalable qui rend la repeinture possible est que les couleurs sont rassemblées en un seul endroit. Si Color.White et #FFFFFF sont dispersés dans les formulaires et le XAML, vous ne pouvez pas énumérer les endroits à repeindre. Dans WinForms, créez une classe « palette » (deux instances, une claire et une sombre) et faites prendre aux contrôles leurs couleurs depuis elle au démarrage et à chaque notification. Dans WPF, rassemblez les couleurs dans un ResourceDictionary et référencez-les depuis XAML avec DynamicResource. Dans WinUI, cette structure est fournie dès le départ comme ThemeDictionaries.
flowchart TB
accTitle: La structure qui rassemble les couleurs en un seul endroit
accDescr: L'application tient une palette claire et une palette sombre, et chaque écran prend ses couleurs dans la palette choisie pour le mode actuel, donc les cibles d'une repeinture peuvent être énumérées
mode["Mode actuel"] --> sel{"Laquelle ?"}
sel -->|Clair| pl["Palette claire"]
sel -->|Sombre| pd["Palette sombre"]
pl --> screens["Chaque écran et chaque contrôle"]
pd --> screens
hard["Color.White dispersé"] -.-> cannot["Impossible d'énumérer ce qu'il faut repeindre"]
Figure 10 : Avec la palette en un seul endroit, une repeinture peut être énumérée ; avec des couleurs codées en dur dispersées, non.
Ce travail de « rassembler les couleurs » se paie directement dans la prise en charge des thèmes de contraste et dans les vérifications de ratio de contraste décrites plus loin. Le plus gros coût de la prise en charge du mode sombre n’est pas les appels d’API, mais ce nettoyage.
6. Implémentation dans WinForms
6.1. .NET 9/10 — Application.SetColorMode
WinForms a reçu une prise en charge préliminaire du mode sombre dans .NET 9, et elle a été « pleinement intégrée » dans .NET 10. Application.SetColorMode accepte trois valeurs.67
SystemColorMode.Classic— le défaut. Clair, comme avant.SystemColorMode.System— suivre le paramètre clair/sombre de Windows.SystemColorMode.Dark— sombre.
Appelez-le avant Application.Run, avant qu’un quelconque élément d’interface soit créé. Sous .NET 9 c’était une fonctionnalité expérimentale, donc c’était une erreur de compilation sauf si WFO5001 était masqué dans le fichier projet ; à partir de .NET 10 l’erreur n’apparaît plus.26
static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
Application.SetColorMode(SystemColorMode.System); // appeler avant de créer toute UI
Application.Run(new MainForm());
}
}
Lorsque le mode de couleur change, System.Drawing.SystemColors bascule vers les couleurs correspondantes, et les contrôles standard sont dessinés en conséquence.6 Sous le capot, la propriété expérimentale SystemColors.UseAlternativeColorSet (SYSLIB5002) « fait que les valeurs KnownColor système renvoient un jeu de couleurs alternatif (actuellement la version du mode sombre) » ; parce que les couleurs système Win32 elles-mêmes ne changent pas avec le paramètre clair/sombre, .NET porte le jeu alternatif de son côté. La même documentation dit aussi que lorsqu’un thème de contraste est actif, les couleurs Windows actuelles sont toujours renvoyées.27
Gardez à l’esprit les trois contraintes écrites dans la documentation de SetColorMode.2
- Le mode de couleur sombre n’est disponible que sous Windows 11 ou une version ultérieure.
- Le mode sombre n’est pas disponible lorsqu’un thème de contraste est actif.
- Même avec
SystemColorMode.System, l’application ne suit pas automatiquement un changement du paramètre Windows pendant qu’elle s’exécute.
La troisième tend à générer des questions de support dans les applications métier. Écrivez dans la documentation destinée à l’utilisateur que le mode est décidé par le paramètre Windows au démarrage et prend effet au lancement suivant. Si vous devez absolument suivre un basculement à l’exécution, vous avez besoin d’une conception qui recrée les formulaires, et cela n’en vaut presque jamais la peine.
flowchart TB
accTitle: Le flux et les contraintes de SetColorMode
accDescr: SetColorMode est appelé avant Application.Run, SystemColors bascule vers le jeu alternatif, et les contrôles standard suivent. Il a trois contraintes : Windows 11 uniquement, désactivé pendant un thème de contraste, et pas de suivi des changements de paramètre à l'exécution
sc["SetColorMode (System)"] --> before["Avant Application.Run"]
before --> colors["SystemColors bascule vers le jeu alternatif"]
colors --> ctrls["Les contrôles standard suivent"]
sc -.-> c1["Windows 11 uniquement"]
sc -.-> c2["Désactivé pendant un thème de contraste"]
sc -.-> c3["Ne suit pas les changements à l'exécution"]
Figure 11 : SetColorMode prend effet une fois avant le démarrage et a trois contraintes documentées.
6.2. Contrôles dessinés par le propriétaire et ApplyThemingImplicitly
Les contrôles standard suivent le mode de couleur de l’application, mais la documentation de .NET 10 liste deux cas exceptionnels. Si un contrôle que vous composez et dessinez vous-même utilise des contrôles communs Win32 tels que les barres de défilement, ceux-ci restent clairs s’ils n’optent pas explicitement. Inversement, si vous héritez d’un contrôle existant qui suit le thème et voulez le contrôle total de son dessin vous-même, vous optez pour la sortie.7
Dans les deux cas, remplacez Control.CreateParams et appelez SetStyle(ControlStyles.ApplyThemingImplicitly, true/false) avant de lire base.CreateParams. Le piège de cette API est que le constructeur de la classe de base lit CreateParams, donc votre propre constructeur arrive trop tard.7
public partial class GanttChartControl : Control
{
protected override CreateParams CreateParams
{
get
{
// Le définir avant de lire base.CreateParams. Le constructeur arrive trop tard
SetStyle(ControlStyles.ApplyThemingImplicitly, true);
return base.CreateParams;
}
}
}
flowchart TB
accTitle: Quand ApplyThemingImplicitly peut être défini
accDescr: ApplyThemingImplicitly se décide au point où le constructeur de la classe de base lit CreateParams, donc SetStyle doit être appelé avant base.CreateParams à l'intérieur du remplacement de CreateParams, et le constructeur de la classe dérivée arrive trop tard
basector["Constructeur de la classe de base"] --> cp["Lit CreateParams"]
cp --> st["SetStyle doit se produire d'ici"]
st --> derived["Constructeur de la classe dérivée"]
derived -.-> late["L'appeler ici arrive trop tard"]
Figure 12 : ApplyThemingImplicitly doit être décidé avant que le constructeur de la classe de base lise CreateParams.
Le dessin par le propriétaire lui-même (dessin GDI+ dans OnPaint) suit le jeu alternatif tant qu’il utilise SystemColors / SystemBrushes / SystemPens. Tout endroit qui peint avec Color.White est remplacé par la palette décrite plus haut, ici aussi.
6.3. .NET Framework 4.8 et .NET 8 ou antérieur — déclarer « clair fixe »
Dans les environnements sans SetColorMode, il n’y a pas de prise en charge standard du mode sombre. Il y a deux options.
- Déclarer le clair fixe. Laissez l’attribut DWM à son défaut FALSE (toujours une barre de titre claire) et gardez la zone cliente telle quelle. La prise en charge des thèmes de contraste (chapitre 8) reste obligatoire.
- Implémenter la prise en charge complète vous-même. Consolidez la palette, lisez et suivez avec
UISettings, mettez l’attribut DWM à TRUE, et repeignez tout y compris l’apparence des contrôles communs.
L’option 2 tend à finir « surtout sombre, mais clair par endroits », parce que l’application ne peut pas pleinement contrôler le dessin des contrôles communs Win32 (barres de défilement, en-têtes, boutons d’expansion d’arbre, et ainsi de suite). Comme le dit le guide officiel, « toute la surface doit suivre » ;3 un mode sombre à moitié fait est une pire expérience que le clair fixe. Pour les actifs existants, choisissez l’option 1, déclarez « cette application s’affiche en mode clair », et passez à SetColorMode lorsque vous migrez vers .NET 10. C’est la politique réaliste, et la plus facile à expliquer.
flowchart TB
accTitle: Options WinForms selon l'environnement d'exécution
accDescr: Sous .NET 10 ou une version ultérieure utiliser SetColorMode, sous .NET 9 la même API avec WFO5001 masqué, et sous .NET 8 ou antérieur ou .NET Framework choisir entre déclarer le clair fixe et implémenter la prise en charge complète vous-même jusqu'aux contrôles communs
q{"Environnement d'exécution ?"}
q -->|.NET 10 ou version ultérieure| n10["SetColorMode (System)"]
q -->|.NET 9| n9["SetColorMode + masquer WFO5001"]
q -->|.NET 8 ou antérieur / .NET Framework| legacy{"Que faire ?"}
legacy -->|Recommandé| fixed["Déclarer le clair fixe"]
legacy -->|Si vous y êtes préparé| full["Prise en charge complète vous-même"]
full -.-> partial["Les contrôles communs restent"]
Figure 13 : Sur les environnements d’exécution sans SetColorMode, déclarer le clair fixe est le défaut réaliste.
7. Implémentation dans WPF
7.1. .NET 9/10 — Le thème Fluent et ThemeMode
WPF sous .NET 9 livre un nouveau thème qui suit le design Fluent de Windows 11, avec prise en charge du clair/sombre et de la couleur d’accent. Il y a deux façons de l’appliquer : définir la propriété ThemeMode, ou ajouter le dictionnaire de ressources PresentationFramework.Fluent à MergedDictionaries.8
ThemeMode prend quatre valeurs, Light / Dark / System / None (le défaut ; le thème Aero2 classique). Défini sur Application il affecte toute l’application ; défini sur une Window il n’affecte que cette fenêtre.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 ne charge pas seulement les dictionnaires de thème Fluent dans les ressources ; la documentation indique qu’il « contrôle aussi l’application du matériau d’arrière-plan et du mode sombre à la fenêtre ». Autrement dit, WPF s’occupe de l’attribut DWM du chapitre 4. ThemeMode et Resources sont aussi conçus pour rester en synchronisation, ce que la documentation explique pour éviter une incohérence où la fenêtre est sombre mais les contrôles à l’intérieur sont clairs.10
flowchart TB
accTitle: Ce que ThemeMode fait dans WPF
accDescr: Définir ThemeMode à System charge les dictionnaires de thème Fluent correspondant au paramètre Windows dans les ressources et contrôle aussi l'application du mode sombre et du matériau d'arrière-plan à la fenêtre
tm["ThemeMode=System"] --> read["Lire le paramètre Windows"]
read --> dict["Charger les dictionnaires Fluent dans les ressources"]
read --> win["Assombrir la fenêtre et appliquer l'arrière-plan"]
dict --> sync["Rester en synchronisation avec Resources pour éviter l'incohérence"]
Figure 14 : ThemeMode contrôle ensemble le chargement des dictionnaires Fluent et l’assombrissement de la fenêtre.
Il y a deux précautions à connaître avant de l’adopter. Premièrement, la lecture et l’écriture de ThemeMode depuis le code sont une fonctionnalité expérimentale, et y accéder produit l’erreur WPF0001. Si vous la masquez vous pouvez écrire Application.Current.ThemeMode = ThemeMode.Dark, mais la référence d’API porte encore [Experimental("WPF0001")] dans .NET 10, avec la note qu’il « peut être retiré à l’avenir ».810 Deuxièmement, la couverture des styles Fluent est encore « in progress » dans .NET 10. .NET 10 a ajouté des styles pour DatePicker, GridSplitter, GroupBox, TextBox et d’autres, et a corrigé des plantages liés à HighContrast,9 ce qui, lu à l’envers, signifie que le thème Fluent de .NET 9 en manquait. Avant de décider de l’adopter, confirmez sur une machine réelle que les contrôles que votre application métier utilise (DataGrid et les contrôles tiers en particulier) ne se cassent pas sous Fluent.
7.2. Suivre le thème sur le thème classique — échanger des ResourceDictionary
Dans WPF sans Fluent (ou sous .NET 8 ou antérieur, ou .NET Framework), il n’y a pas de prise en charge standard du mode sombre. Les couleurs système Win32 ne changent pas avec le paramètre clair/sombre, donc référencer les SystemColors de WPF ne rendra rien sombre. La structure pour suivre le thème vous-même est la suivante.
- Définissez les couleurs et les pinceaux avec les mêmes clés dans
Themes/Light.xamlpour le clair etThemes/Dark.xamlpour le sombre. - Référencez-les depuis XAML avec DynamicResource, comme dans
{DynamicResource App.WindowBackgroundBrush}(StaticResourceest fixé au chargement et ne suit pas un échange). - Sur une notification du
SystemThemeWatcherdu chapitre 5, échangez le dictionnaire correspondant dansMergedDictionaries. Pendant un thème de contraste, quel que soit le dictionnaire que vous mettez, le déclencheur de la section 8.4 remplace les couleurs par des couleurs système, donc mettez le clair.
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);
// Le dictionnaire sombre seulement lorsque sombre. Pendant un thème de contraste, se remettre aux couleurs système (section 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; // échanger sur place, à la même position
}
}
}
flowchart TB
accTitle: Échanger des dictionnaires de ressources dans WPF
accDescr: Définir les couleurs avec les mêmes clés dans un dictionnaire clair et un sombre, les référencer depuis XAML avec DynamicResource, et échanger le dictionnaire dans MergedDictionaries sur une notification de changement de thème pour que les références se mettent à jour
notify["Notification de changement de thème"] --> swap["Échanger le dictionnaire dans MergedDictionaries"]
light["Light.xaml (mêmes clés)"] --> swap
dark["Dark.xaml (mêmes clés)"] --> swap
swap --> dyn["Les références DynamicResource se mettent à jour"]
static["Références StaticResource"] -.-> stale["Gardent la valeur du chargement"]
Figure 15 : Échangez des dictionnaires avec les mêmes clés, et seules les références DynamicResource suivent.
Les modèles des contrôles standard (arrière-plans de boutons, couleurs de barres de défilement) portent les couleurs du thème classique, donc ici aussi il y aura des endroits où « les surfaces propres de l’application sont sombres mais les contrôles standard sont clairs ». Estimez le travail de remplacer le style de chaque contrôle dont vous avez besoin, puis comparez-le à l’adoption de Fluent ou au clair fixe.
7.3. La barre de titre
Si vous utilisez ThemeMode, WPF s’en occupe. Si vous suivez le thème vous-même sur le thème classique, utilisez la définition de l’attribut DWM depuis OnSourceInitialized montrée à la section 4.2, et redéfinissez-le sur les notifications de SystemThemeWatcher.
8. Dessiner sous un thème de contraste — garder les paires de couleurs système
8.1. Détection et notification
Dans Win32, passez SPI_GETHIGHCONTRAST à SystemParametersInfo pour recevoir une structure HIGHCONTRAST, et testez le bit HCF_HIGHCONTRASTON de dwFlags. cbSize doit être défini avant l’appel.1128 Microsoft positionne cela comme « le seul moyen pris en charge de vérifier si le contraste élevé est activé ».12
bool IsContrastThemeActive()
{
HIGHCONTRASTW hc{};
hc.cbSize = sizeof(hc);
if (!::SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(hc), &hc, 0))
{
// Ne pas cacher un échec derrière une valeur par défaut. Le faire apparaître avec le code d'erreur pour que la cause puisse être trouvée
throw std::system_error(::GetLastError(), std::system_category(),
"SystemParametersInfo(SPI_GETHIGHCONTRAST)");
}
return (hc.dwFlags & HCF_HIGHCONTRASTON) != 0;
}
Chaque framework a une propriété qui enveloppe cet appel.
| Environnement | Détection | Notification de changement |
|---|---|---|
| Win32 / MFC | SPI_GETHIGHCONTRAST + HCF_HIGHCONTRASTON |
WM_SYSCOLORCHANGE, WM_THEMECHANGED |
| WinForms | SystemInformation.HighContrast |
SystemEvents.UserPreferenceChanged |
| WPF | SystemParameters.HighContrast (correspond à SPI_GETHIGHCONTRAST) |
SystemParameters.StaticPropertyChanged |
| WinUI 3 | ThemeSettings.HighContrast (Microsoft.UI.System) |
ThemeSettings.Changed |
Le didacticiel d’accessibilité WinForms vous demande de vérifier HighContrast au démarrage et de répondre aux changements via UserPreferenceChanged.13 SystemParameters.HighContrast de WPF correspond à SPI_GETHIGHCONTRAST et HCF_HIGHCONTRASTON,29 et les changements des propriétés statiques sont annoncés via StaticPropertyChanged.30 ThemeSettings de WinUI 3 se crée lié à une fenêtre avec CreateForWindowId et vous vous abonnez à son événement Changed, mais notez que les événements s’arrêtent si vous ne continuez pas à tenir une référence à l’objet.31
flowchart TB
accTitle: Chemins de détection des thèmes de contraste
accDescr: Win32 SPI_GETHIGHCONTRAST est la seule méthode de détection prise en charge, et SystemInformation.HighContrast dans WinForms, SystemParameters.HighContrast dans WPF et ThemeSettings.HighContrast dans WinUI 3 sont fournis par chaque framework comme enveloppes autour
spi["SPI_GETHIGHCONTRAST (la seule méthode de détection)"] --> wf["WinForms SystemInformation"]
spi --> wpf["WPF SystemParameters"]
spi --> winui["WinUI 3 ThemeSettings"]
spi --> win32["Win32 : l'appeler directement"]
Figure 16 : La racine de la détection est une seule API Win32, et chaque framework a une propriété qui l’enveloppe.
8.2. Principes de dessin — paires de premier plan et d’arrière-plan
Le « High contrast parameter » de Microsoft liste trois choses qu’une application devrait faire lorsque le contraste élevé est activé.11
- Mapper chaque couleur vers une paire de couleurs de premier plan et d’arrière-plan. Utilisez
GetSysColoravec la paireCOLOR_WINDOWTEXTetCOLOR_WINDOW, ou la paireCOLOR_BTNTEXTetCOLOR_BTNFACE. - Omettre les images bitmap affichées derrière le texte. Elles sont un obstacle visuel pour les utilisateurs qui ont besoin d’un contraste élevé.
- Dessiner les images multicolores dans les couleurs de premier plan et d’arrière-plan utilisées pour le texte.
La « paire » est le nœud. Le guide de Windows 8 et versions ultérieures explique que COLOR_HIGHLIGHTTEXT est conçu pour être combiné avec l’arrière-plan COLOR_HIGHLIGHT et COLOR_WINDOWTEXT avec l’arrière-plan COLOR_WINDOW, et vous demande de ne pas coder en dur les couleurs de texte et, parce que les utilisateurs personnalisent les couleurs, de construire une interface qui ne dépend pas du thème en vigueur.12 L’exemple du même guide, « dans Aero, le texte est toujours noir et la couleur de sélection est bleu clair, mais dans High Contrast Black la couleur de sélection est noire. Si vous supposez un texte noir et utilisez la couleur de sélection système, vous obtenez du texte noir sur noir », est exactement la plainte « l’affichage d’état a disparu » de l’ouverture.
Le guide des thèmes de contraste de Windows 11 tabule les appariements.1
| Usage | Premier plan | Arrière-plan |
|---|---|---|
| Titres, corps de texte, listes, bordures, interface non interactive | SystemColorWindowText |
SystemColorWindow |
| Hyperliens | SystemColorHotlight |
SystemColorWindow |
| Interface désactivée ou inactive | SystemColorGrayText |
SystemColorWindow |
| Sélectionné, survol, enfoncé, en cours | SystemColorHighlightText |
SystemColorHighlight |
| Interface interactive telle que les boutons | SystemColorButtonText |
SystemColorButtonFace |
Ce qu’il ne faut pas faire est aussi énoncé. N’utilisez pas GrayText pour du texte supplémentaire ou d’indication (c’est pour l’état désactivé uniquement) ; n’utilisez pas Hotlight pour autre chose que les hyperliens ; ne mélangez pas des premiers plans et des arrière-plans incompatibles ; ne choisissez pas les couleurs d’après l’apparence (les utilisateurs les changent vraiment). Il y a aussi une ligne directrice de conception selon laquelle les arrière-plans des pages, des volets et des popups sont basés sur SystemColorWindow, donc les surfaces adjacentes finissent avec la même couleur d’arrière-plan, et seules les frontières qui comptent sont séparées par une bordure utilisée uniquement sous les thèmes de contraste (2 px est recommandé pour les menus volants et les boîtes de dialogue).1
flowchart TB
accTitle: Comment rompre la paire rend le texte illisible
accDescr: Supposer que le texte est noir et n'utiliser la couleur de sélection système que pour l'arrière-plan de sélection donne du noir sur noir dans High Contrast Black, où la couleur de sélection est noire. Prendre premier plan et arrière-plan comme une paire garde le texte lisible même lorsque l'utilisateur édite les couleurs
assume["Supposer que le texte est noir"] --> hl["Seul l'arrière-plan de sélection utilise la couleur de sélection système"]
hl --> black["Dans High Contrast Black la couleur de sélection est noire"]
black --> broken["Texte noir sur noir"]
pair["Prendre premier plan et arrière-plan comme une paire"] --> ok["Lisible même lorsque l'utilisateur édite les couleurs"]
edit["L'utilisateur édite les couleurs"] -.-> assume
Figure 17 : Utiliser une couleur système pour un seul côté peut donner du noir sur noir, mais prendre la paire reste lisible même lorsque les couleurs sont éditées.
flowchart TB
accTitle: Décisions de dessin sous un thème de contraste
accDescr: Lorsqu'un thème de contraste est actif, mapper les couleurs vers les paires de couleurs système, omettre les images derrière le texte, dessiner les images multicolores dans les deux couleurs de premier plan et d'arrière-plan, et ne pas utiliser de couleurs codées en dur
on["Thème de contraste actif"] --> map["Mapper les couleurs vers des paires"]
on --> img["Omettre les images derrière le texte"]
on --> multi["Dessiner les graphismes multicolores en deux couleurs"]
on --> nohard["Pas de couleurs codées en dur"]
Figure 18 : Dessiner sous un thème de contraste actif se résume à quatre points : mapper, omettre, deux couleurs, et pas de codage en dur.
8.3. Implémentation dans WinForms
Les contrôles WinForms standard suivent les couleurs système tant que ForeColor / BackColor sont laissés à leurs défauts. Seuls les endroits avec des couleurs personnalisées et le dessin par le propriétaire sont basculés selon la vérification. L’exemple du didacticiel prend une étiquette jaune sur bleu en temps normal et la rétablit à SystemColors.Window / SystemColors.WindowText sous contraste élevé.13 Cela revient à ajouter une branche de thème de contraste à la structure de palette vue plus haut.
using Microsoft.Win32;
public partial class OrderForm : Form
{
private readonly SynchronizationContext _ui;
public OrderForm()
{
InitializeComponent();
// Les contrôles existent maintenant, donc un WindowsFormsSynchronizationContext est en place
_ui = SynchronizationContext.Current
?? throw new InvalidOperationException("Create this form on the UI thread.");
ApplyColorScheme();
SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
}
private void ApplyColorScheme()
{
if (SystemInformation.HighContrast)
{
// Garder la paire, se remettre entièrement aux couleurs système, et retirer l'image derrière le texte
statusLabel.BackColor = SystemColors.Window;
statusLabel.ForeColor = SystemColors.WindowText;
headerPanel.BackgroundImage = null;
}
else
{
var p = AppPalette.Current; // la palette clair/sombre (chapitre 5)
statusLabel.BackColor = p.PanelBackground;
statusLabel.ForeColor = p.PanelForeground;
headerPanel.BackgroundImage = Properties.Resources.HeaderPattern;
}
}
private void OnUserPreferenceChanged(object? sender, UserPreferenceChangedEventArgs e)
{
// Cet événement n'est pas non plus garanti d'arriver sur le thread UI. Remonter vers l'UI, puis réévaluer sans filtrer par catégorie
_ui.Post(_ =>
{
if (IsDisposed) return;
ApplyColorScheme();
}, null);
}
// Un événement statique, donc le formulaire fuit s'il n'est pas détaché. Détacher dans Dispose(bool), qui s'exécute aussi
// sur les chemins où le formulaire est disposé sans être fermé (si le Dispose(bool) généré par le concepteur existe, le mettre là)
protected override void Dispose(bool disposing)
{
if (disposing)
{
SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
}
base.Dispose(disposing);
}
}
Le dessin par le propriétaire dans OnPaint utilise des pinceaux système qui gardent la paire, tels que SystemBrushes.Window / SystemPens.WindowText, et les graphismes multicolores tels qu’un point coloré qui représente un état sont remplacés par une bordure et du texte (« Running », « Stopped ») dans la couleur de premier plan. Transmettre l’information par plus que la couleur seule est le même point que le critère de succès 1.4.1 traité dans l’article précédent.
flowchart TB
accTitle: Branchement du schéma de couleurs WinForms
accDescr: ApplyColorScheme est appelé au démarrage et à chaque UserPreferenceChanged ; si SystemInformation.HighContrast est vrai il se remet aux paires de couleurs système et retire l'image d'arrière-plan, et s'il est faux il prend les couleurs dans la palette clair/sombre
start["Démarrage / UserPreferenceChanged"] --> apply["ApplyColorScheme"]
apply --> q{"SystemInformation.HighContrast ?"}
q -->|Vrai| sys["Se remettre aux paires SystemColors"]
sys --> noimg["Retirer l'image d'arrière-plan"]
q -->|Faux| pal["Prendre les couleurs dans la palette clair/sombre"]
Figure 19 : Dans WinForms la même routine est appelée au démarrage et à chaque notification, et sous un thème de contraste elle se remet aux couleurs système.
8.4. Implémentation dans WPF
Les SystemColors de WPF se mettent à jour automatiquement lorsqu’un pinceau change si vous référencez une clé de ressource telle que WindowBrushKey via DynamicResource (une référence statique qui utilise WindowBrush directement ne se met pas à jour).32 Pour changer l’apparence seulement pendant un thème de contraste, référencez la valeur de SystemParameters.HighContrast depuis un DataTrigger. Cependant, SystemParameters.HighContrast est une propriété statique, donc à elle seule ce n’est pas une source de liaison vivante. Préparez un petit proxy qui s’abonne à StaticPropertyChanged, tient la valeur, et notifie via INotifyPropertyChanged, et liez avec cette instance comme 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()
{
// Déclenché lorsqu'une propriété statique de SystemParameters change (SPI_GETHIGHCONTRAST est relu)
SystemParameters.StaticPropertyChanged += (_, e) =>
{
if (!string.IsNullOrEmpty(e.PropertyName)
&& e.PropertyName != nameof(SystemParameters.HighContrast)) return;
IsHighContrast = SystemParameters.HighContrast;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsHighContrast)));
};
}
}
<!-- Déclarer xmlns:local="clr-namespace:OrderEntry" au préalable -->
<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">
<!-- Garder la paire : l'arrière-plan est Window, la bordure et le texte sont WindowText. Épaissir la frontière -->
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="BorderBrush"
Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
<!-- Laisser le texte à l'intérieur l'hériter aussi. Notez qu'un enfant qui définit Foreground explicitement coupe l'héritage -->
<Setter Property="TextElement.Foreground"
Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="2"/>
</DataTrigger>
</Style.Triggers>
</Style>
flowchart TB
accTitle: Références de couleurs système WPF et le déclencheur de thème de contraste
accDescr: Référencer les clés de ressource SystemColors via DynamicResource suit les changements de pinceau automatiquement, et un déclencheur lié à IsHighContrast sur un proxy qui s'abonne à StaticPropertyChanged réagit à un basculement à l'exécution et bascule vers des couleurs qui gardent la paire. Une référence directe à WindowBrush ne se met pas à jour
key["Référencer WindowBrushKey via DynamicResource"] --> auto["Suit les changements de pinceau automatiquement"]
spc["StaticPropertyChanged"] --> proxy["IsHighContrast sur le proxy"]
proxy --> trig["DataTrigger réagit"]
trig --> pair["Basculer vers des couleurs qui gardent la paire"]
direct["Référencer WindowBrush directement"] -.-> stale["Ne se met pas à jour"]
Figure 20 : WPF suit un basculement à l’exécution via des références dynamiques aux clés de ressource et une liaison à un proxy qui relaie les changements de la propriété statique.
Si vous utilisez le thème Fluent, gardez à l’esprit que .NET 10 incluait des corrections de plantage liées à HighContrast,9 et faites de la vérification sous un thème de contraste une condition d’adoption.
8.5. Implémentation dans WinUI 3
Dans WinUI 3, les contrôles standard suivent les thèmes clairs, sombres et de contraste dès le départ, et les couleurs propres de l’application se définissent dans ResourceDictionary.ThemeDictionaries sous les clés Default (sombre), Light et HighContrast. Sous HighContrast, ne codez pas les couleurs en dur ; référencez des couleurs système dynamiques telles que SystemColorWindowColor via ThemeResource. Un contrôle personnalisé qui a Light/Dark doit toujours avoir aussi HighContrast, et HighContrast est la clé de repli utilisée lorsqu’aucun autre thème de contraste nommé n’est trouvé.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>
Encore une chose : WinUI a un mécanisme appelé HighContrastAdjustment, activé par défaut. Il force un texte blanc et un arrière-plan de surbrillance noir pour préserver le contraste, et le guide recommande qu’une fois que vous avez préparé des dictionnaires de thème qui utilisent correctement les couleurs système, vous le mettiez à None pour que vos propres styles s’appliquent.1
flowchart TB
accTitle: Comment WinUI résout ThemeDictionaries
accDescr: Selon le thème actuel, l'un des dictionnaires Default (sombre), Light et HighContrast est choisi, et sous HighContrast des couleurs système dynamiques sont référencées via ThemeResource. HighContrast est la clé de repli lorsqu'il n'y a pas de thème de contraste nommé
theme{"Thème actuel ?"}
theme -->|Sombre| def["Dictionnaire Default"]
theme -->|Clair| light["Dictionnaire Light"]
theme -->|Thème de contraste| hc["Dictionnaire HighContrast"]
hc --> sysc["Référencer les ressources SystemColor via ThemeResource"]
hc -.-> fb["Repli lorsqu'il n'y a pas de thème nommé"]
Figure 21 : Dans WinUI le dictionnaire de chaque thème est choisi automatiquement, et le dictionnaire HighContrast référence les couleurs système.
9. La prise en charge du mode sombre n’est pas un substitut à la prise en charge de l’accessibilité
Rapporter la prise en charge du mode sombre comme « nous avons fait le travail d’accessibilité » est une erreur. La relation entre les deux peut se poser ainsi.
- La norme de ratio de contraste s’applique à la palette sombre tout autant. Le critère de succès WCAG 1.4.3 exige 4,5:1 pour le texte et 3:1 pour le grand texte, et cela ne change pas lorsque l’arrière-plan est sombre.14 Un design sombre qui pose du texte gris moyen sur un arrière-plan gris foncé a le même problème que « gris clair sur blanc » en mode clair.
- Éviter le noir pur et le blanc pur est le design de Windows 11. Les bonnes pratiques de Microsoft expliquent que Windows 11 s’est éloigné du blanc pur et du noir pur vers des tons plus faciles pour les yeux.34 Inversement, un arrière-plan
#000000en mode sombre conduit certaines personnes à se plaindre d’halation, un effet de blooming causé par un contraste excessif contre un texte clair. - L’accommodement de la diversité de la vision des couleurs est nécessaire quel que soit le thème. Le guide des couleurs de Microsoft demande d’utiliser la couleur comme renforcement visuel plutôt que comme moyen premier de communication, et de ne jamais faire de la combinaison du rouge et du vert la seule distinction.3515
- Les thèmes de contraste sont une exigence indépendante du mode sombre. Comme le chapitre 2 l’a expliqué, le mode sombre n’est pas disponible tant qu’un thème de contraste est actif, donc aussi parfaite que soit votre prise en charge du mode sombre, elle n’atteint jamais les utilisateurs de thèmes de contraste.
flowchart TB
accTitle: La relation entre le mode sombre et l'accessibilité
accDescr: La prise en charge du mode sombre est une affaire de préférence visuelle et d'environnement, tandis que les exigences d'accessibilité de ratio de contraste, de transmission de l'information par plus que la couleur seule, et de prise en charge des thèmes de contraste doivent être satisfaites séparément quel que soit le thème
dark["Prise en charge du mode sombre"] --> pref["Préférence et environnement"]
a11y["Accessibilité"] --> ratio["Ratio de contraste 4,5:1"]
a11y --> sole["Pas la couleur seule"]
a11y --> ct["Prise en charge des thèmes de contraste"]
dark -.->|Pas un substitut à| a11y
Figure 22 : La prise en charge du mode sombre adresse la préférence et l’environnement ; les exigences d’accessibilité doivent être satisfaites séparément.
D’un autre côté, le travail de « rassembler les couleurs en un seul endroit » du chapitre 5 est le fondement des deux. Avec la palette en un seul endroit, vous pouvez énumérer ce qu’il faut mesurer pour le ratio de contraste en clair et en sombre, et la branche de thème de contraste peut s’écrire au même endroit. Servez-vous de la prise en charge du mode sombre comme occasion de consolider la palette, et inspectez les ratios de contraste et les thèmes de contraste en même temps. C’est l’ordre avec le meilleur retour sur l’investissement.
10. Choisir une politique — recommandations par type d’application
| Type d’application | Politique recommandée |
|---|---|
| Nouveau WinForms (.NET 10) | Utiliser SetColorMode(System). Fonder le dessin par le propriétaire sur SystemColors, et faire opter les contrôles personnalisés qui contiennent des contrôles communs avec ApplyThemingImplicitly |
| Nouveau WPF (.NET 9/10) | Adopter ThemeMode="System" après avoir vérifié comment les contrôles que vous utilisez s’affichent et comment l’application se comporte sous les thèmes de contraste. Si c’est difficile, thème classique plus échange de dictionnaires |
| WinForms/WPF existant (.NET Framework 4.8, .NET 8 ou antérieur) | Déclarer « clair fixe » et laisser l’attribut DWM à son défaut (FALSE). La prise en charge des thèmes de contraste est obligatoire ; passer à la prise en charge du mode sombre lors de la migration vers .NET 10 |
| WinUI 3 | Suit le système par défaut. Définir les couleurs propres de l’application dans ThemeDictionaries y compris HighContrast, et mettre HighContrastAdjustment à None |
| Win32 / MFC | Attribut DWM + votre propre palette + recalcul sur WM_THEMECHANGED / WM_SYSCOLORCHANGE. Le guide officiel ne couvre que la détection et la barre de titre ; repeindre les contrôles communs est hors de sa portée |
flowchart TB
accTitle: Le chemin des actifs existants vers la prise en charge du mode sombre
accDescr: Les actifs existants déclarent d'abord le clair fixe, achèvent sans faute la prise en charge des thèmes de contraste, consolident la palette comme préparation, puis migrent vers .NET 10 et passent à SetColorMode ou ThemeMode. Un mode sombre à moitié fait est une pire expérience que le clair fixe, donc ce chemin n'est pas pris
now["Déclarer le clair fixe (maintenant)"] --> ct["Prise en charge des thèmes de contraste (obligatoire)"]
ct --> pal["Consolider la palette (préparation)"]
pal --> mig["Migrer vers .NET 10"]
mig --> dark["Passer à SetColorMode / ThemeMode"]
now -.->|Non pris| half["Mode sombre à moitié fait"]
half -.-> worse["Pire expérience que le clair fixe"]
Figure 23 : Les actifs existants partent du clair fixe, passent par la prise en charge des thèmes de contraste et la consolidation de la palette, et vont vers la prise en charge du mode sombre lors de la migration vers .NET 10.
Le « clair fixe » n’est pas une défaite. C’est le comportement par défaut de Windows lui-même, et un comportement documenté. Une application qui est constamment claire est bien meilleure pour les utilisateurs qu’un mode sombre à moitié fait livré avec « seulement la barre de titre noire » ou « seulement les barres de défilement blanches ». Cependant, la prise en charge des thèmes de contraste est la seule chose qui ne peut pas être « fixée ». Elle relève de l’aménagement raisonnable traité dans l’article précédent : c’est une question de savoir si l’application est utilisable, pas une question de préférence de thème.
11. Liste de vérification
Une fois le travail fait, vérifiez sur une machine réelle dans l’ordre suivant. Chaque étape peut se basculer depuis l’application Paramètres en quelques secondes.
- Basculer clair/sombre pendant que l’application s’exécute. Changez le mode sous Paramètres > Personnalisation > Couleurs, et confirmez que la barre de titre et la zone cliente suivent, ou que l’application se comporte comme spécifié avec « prend effet au lancement suivant » (
SetColorModedans WinForms ne suit pas). - Déclencher une recréation de handle. Dans WinForms, basculez
ShowInTaskbarà l’exécution et confirmez que l’attribut de barre de titre est conservé. - Essayer les quatre thèmes de contraste. Basculez avec Alt gauche + Maj gauche + Impr. écran, et confirmez dans chacun d’Aquatic, Desert, Dusk et Night sky que le texte, les bordures, les lignes sélectionnées, les éléments désactivés et les liens sont lisibles.1
- Éditer les couleurs d’un thème de contraste. Les utilisateurs changent vraiment les couleurs. Éditez l’arrière-plan vers une couleur extrême pour faire sortir les couleurs encore codées en dur.
- Vérifier les journaux. Confirmez qu’un échec de
DwmSetWindowAttributeouSystemParametersInfosur un OS pris en charge est enregistré, et que sous Windows 10 en dessous de la build 22000 l’application démarre claire sans appeler l’attribut DWM. - Mesurer les ratios de contraste. En clair et en sombre, vérifiez chaque combinaison de couleur de texte et d’arrière-plan dans la palette contre 4,5:1.14
flowchart TB
accTitle: Étapes de vérification de la prise en charge des thèmes
accDescr: Vérifier sur une machine réelle dans cet ordre : basculer clair/sombre à l'exécution, recréation de handle, les quatre thèmes de contraste, éditer les couleurs du thème, vérifier les journaux d'échec, et mesurer les ratios de contraste
s1["Basculer clair/sombre à l'exécution"] --> s2["Recréation de handle"]
s2 --> s3["Les quatre thèmes de contraste"]
s3 --> s4["Éditer les couleurs du thème"]
s4 --> s5["Vérifier les journaux d'échec"]
s5 --> s6["Mesurer les ratios de contraste"]
Figure 24 : La vérification commence par le basculement des paramètres et se clôt par la vérification des journaux et des ratios de contraste.
12. Synthèse
- Les thèmes Windows ont deux axes, clair/sombre et thèmes de contraste, et le mode sombre n’est pas disponible tant que le second est actif. Détectez d’abord le thème de contraste.
- La barre de titre d’une application existante est blanche parce que c’est le défaut de compatibilité ; passez TRUE pour
DWMWA_USE_IMMERSIVE_DARK_MODE(valeur 20, Windows 11 build 22000 ou une version ultérieure) viaDwmSetWindowAttribute, et elle est dessinée sombre lorsque le système est sombre. Définissez-le chaque fois que le HWND est créé, et consignez les échecs. - Décidez le mode actuel d’après la luminosité de la couleur de premier plan de
UISettings.GetColorValue, remarquez les changements avecColorValuesChanged, remontez vers le thread UI, et repeignez. Gardez les couleurs rassemblées en un seul endroit. - WinForms :
Application.SetColorMode(SystemColorMode.System)sous .NET 9/10. Connaissez les trois contraintes (Windows 11 uniquement, désactivé pendant un thème de contraste, pas de suivi des changements à l’exécution) etApplyThemingImplicitlypour les contrôles personnalisés. - WPF :
ThemeMode="System"sous .NET 9/10. La manipulation depuis le code est expérimentale et Fluent est en cours, donc évaluez avant d’adopter. Sur le thème classique, échange de dictionnaires plus DynamicResource. - Sous un thème de contraste, détectez avec la famille
SPI_GETHIGHCONTRAST, mappez les couleurs vers les paires de couleurs système, omettez les images derrière le texte, et dessinez les graphismes multicolores en deux couleurs. GrayText est pour l’état désactivé, Hotlight pour les liens uniquement. - La prise en charge du mode sombre n’est pas un substitut à la prise en charge de l’accessibilité. 4,5:1 s’applique encore en mode sombre, l’information ne doit pas reposer sur la couleur seule, et la prise en charge des thèmes de contraste est requise séparément.
- Pour les actifs existants, déclarer « clair fixe » est la réponse réaliste, et la prise en charge des thèmes de contraste est la seule chose qui ne peut pas être fixée.
Comme premier pas recommandé, prenez un écran principal, activez d’abord un thème de contraste avec Alt gauche + Maj gauche + Impr. écran et regardez, revenez avec les mêmes touches, puis basculez le paramètre de couleur Windows vers sombre (le mode sombre n’est pas disponible tant qu’un thème de contraste est actif, donc essayez les deux séparément). En quelques minutes vous verrez « où votre application tient ses couleurs ».
Articles associés
- Introduction à l’accessibilité des applications Windows — se préparer à UI Automation et aux exigences d’aménagement raisonnable
- WinRT est COM — IInspectable, .winmd, projections de langage, et pourquoi WinUI repose encore sur un contrat binaire
- Appeler les API Win32 en toute sécurité depuis C# — Guide pratique de P/Invoke (DllImport / LibraryImport / CsWin32)
- WPF/WinForms : async et le thread UI récapitulés en une fiche
- La prise en charge du haut DPI dans WinForms — pourquoi l’interface devient floue ou se casse sur les moniteurs 4K, et solutions concrètes
- Le support du haut DPI dans WPF — pourquoi l’affichage reste flou et baveux malgré une application « censée résister au DPI », et comment y remédier
- Comment choisir entre WinForms, WPF et WinUI - Tableau de décision pratique
Domaines de conseil associés
KomuraSoft LLC prend en charge le mode sombre des applications métier WinForms/WPF (consolider la palette, évaluer une migration vers SetColorMode / ThemeMode sous .NET 9/10, intégrer l’attribut DWM), le diagnostic et la correction des cassures d’affichage sous les thèmes de contraste, et les consultations sur le suivi de thème dans les actifs Win32/MFC. Commencer dès le stade de « les salariés se sont plaints une fois passés en mode sombre » convient.
Références
-
Microsoft Learn, Contrast themes. Sur le fait que les thèmes de contraste utilisent une palette contrainte d’environ 7:1 ou plus et ne doivent pas être confondus avec les thèmes clairs et sombres ; les quatre thèmes Aquatic, Desert, Dusk et Night sky et l’édition de leurs couleurs ; le basculement avec Alt gauche + Maj gauche + Impr. écran ; les paires premier plan/arrière-plan et les usages des ressources SystemColor ; l’utilisation de GrayText pour l’état désactivé uniquement et de Hotlight pour les liens uniquement ; les cassures dues aux couleurs codées en dur ; les bordures de frontière ; HighContrast dans ThemeDictionaries ; le réglage de HighContrastAdjustment à None ; et la détection avec Microsoft.UI.System.ThemeSettings. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Application.SetColorMode(SystemColorMode) Method. Sur l’appel avant que les éléments d’interface soient créés, sur le fait que l’application ne s’adapte pas automatiquement lorsque le paramètre système change même avec System, et sur le fait que le mode de couleur sombre n’est disponible que sous Windows 11 ou une version ultérieure et n’est pas disponible en mode contraste élevé. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Support Dark and Light themes in Win32 apps. Sur la définition du premier plan et de l’arrière-plan dans les modes de couleur ; sur le fait que Windows donne une barre de titre claire par défaut pour la compatibilité parce qu’il ne peut pas savoir si une application prend en charge le mode sombre ; sur la procédure d’obtenir la couleur de premier plan avec UISettings.GetColorValue et de classer clair ou sombre par luminance perçue pour détecter le mode sombre ; sur le suivi avec ColorValuesChanged ; sur l’activation de la barre de titre sombre avec DwmSetWindowAttribute et DWMWA_USE_IMMERSIVE_DARK_MODE (valeur 20) ; et sur le fait que toute la surface doit suivre le mode sombre. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10
-
Microsoft Learn, DWMWINDOWATTRIBUTE enumeration (dwmapi.h). Sur le fait que DWMWA_USE_IMMERSIVE_DARK_MODE permet de dessiner le cadre en sombre lorsque le paramètre sombre du système est activé et que toutes les fenêtres sont claires par défaut ; sur les valeurs COLORREF de DWMWA_BORDER_COLOR, DWMWA_CAPTION_COLOR et DWMWA_TEXT_COLOR et le rétablissement du défaut avec DWMWA_COLOR_DEFAULT ; sur la prise en charge à partir de Windows 11 build 22000 ; et sur la prise en charge de DWMWA_SYSTEMBACKDROP_TYPE à partir de la build 22621. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, UISettings.ColorValuesChanged Event. Sur l’événement déclenché lorsqu’une valeur de couleur change. ↩ ↩2
-
Microsoft Learn, What’s new in Windows Forms for .NET 9. Sur la prise en charge préliminaire expérimentale du mode sombre, le changement correspondant de SystemColors lorsque le mode de couleur change, les trois valeurs SystemColorMode Classic, System et Dark, l’appel à Application.SetColorMode dans le code de démarrage, et le masquage de WFO5001. ↩ ↩2 ↩3
-
Microsoft Learn, What’s new in Windows Forms for .NET 10. Sur l’intégration complète du mode sombre et le fait que SetColorMode n’est plus expérimental, sur le fait que les contrôles communs Win32 à l’intérieur des contrôles dessinés par le propriétaire restent clairs s’ils n’optent pas, et sur la nécessité d’appeler SetStyle(ControlStyles.ApplyThemingImplicitly) avant base.CreateParams à l’intérieur du remplacement de CreateParams parce que le constructeur arrive trop tard. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, What’s new in WPF for .NET 9. Sur le thème Fluent prenant en charge le clair/sombre et la couleur d’accent, les quatre valeurs ThemeMode Light, Dark, System et None et le définir sur Application ou Window, l’appliquer via des dictionnaires de ressources, et le fait que définir ThemeMode depuis le code est expérimental et exige le masquage de WPF0001. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, What’s new in WPF for .NET 10. Sur le fait que la prise en charge des styles d’interface Fluent est encore en cours, les styles Fluent ajoutés pour DatePicker, GridSplitter, GridView, GroupBox, Hyperlink, Label, NavigationWindow, RichTextBox et TextBox, et les corrections de plantage liées à HighContrast. ↩ ↩2 ↩3
-
Microsoft Learn, Application.ThemeMode Property. Sur le fait qu’il contrôle si le thème Fluent est chargé en mode clair, sombre ou système et contrôle aussi l’application du matériau d’arrière-plan et du mode sombre à la fenêtre, que ThemeMode et Resources sont conçus pour rester en synchronisation afin d’éviter l’incohérence, et qu’il porte l’attribut Experimental(“WPF0001”) et peut être retiré à l’avenir. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, High contrast parameter. Sur l’obtention de la structure HIGHCONTRAST avec SPI_GETHIGHCONTRAST à l’initialisation et lors du traitement de WM_SYSCOLORCHANGE et la vérification de HCF_HIGHCONTRASTON, et, lorsqu’il est activé, le mapping de chaque couleur vers une paire de COLOR_WINDOWTEXT et COLOR_WINDOW ou COLOR_BTNTEXT et COLOR_BTNFACE, l’omission des images bitmap derrière le texte, et le dessin des images multicolores dans les couleurs de premier plan et d’arrière-plan. ↩ ↩2 ↩3
-
Microsoft Learn, High-contrast mode. Sur le fait qu’Aero a un texte noir et une couleur de sélection bleu clair mais que High Contrast Black a une couleur de sélection noire, ce qui peut produire du texte noir sur noir ; que COLOR_HIGHLIGHTTEXT est conçu pour être apparié avec COLOR_HIGHLIGHT et COLOR_WINDOWTEXT avec COLOR_WINDOW ; de ne pas coder en dur les couleurs de texte ; de construire une interface qui ne dépend pas du thème parce que les utilisateurs personnalisent les couleurs ; de recalculer les couleurs sur WM_THEMECHANGED ; et que SPI_GETHIGHCONTRAST est le seul moyen pris en charge de vérifier. ↩ ↩2 ↩3
-
Microsoft Learn, Walkthrough: Creating an Accessible Windows-based Application. Sur la détection avec SystemInformation.HighContrast ; l’utilisation du schéma de couleurs système lorsqu’il est activé, l’ajout d’indices visuels à l’information transmise par la couleur, et l’omission des images derrière le texte ; la vérification au démarrage et le suivi de l’événement UserPreferenceChanged ; et l’exemple de basculement des couleurs d’une étiquette avec SystemColors. ↩ ↩2 ↩3
-
W3C / traduction japonaise par le Web Accessibility Infrastructure Committee (WAIC), Web Content Accessibility Guidelines (WCAG) 2.1, Japanese translation. Sur le critère de succès 1.4.3 (Contrast (Minimum)) avec 4,5:1 pour le texte et 3:1 pour le grand texte, et le critère de succès 1.4.1 (Use of Color). ↩ ↩2 ↩3
-
Microsoft Learn, Color in Windows. Sur le fait que Windows a deux modes de couleur, clair et sombre, que le choix de la couleur d’accent et du thème se reflète dans toute l’expérience de l’utilisateur, et sur l’assurance du contraste et l’accommodement de la diversité de la vision des couleurs. ↩ ↩2
-
Microsoft Learn, Reference for Windows 11 and Windows 10 settings. Sur le fait que AppsUseLightTheme et SystemUsesLightTheme sous HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize sont des valeurs DWORD représentant le mode clair/sombre des applications et de Windows. ↩
-
Microsoft Learn, Theming in Windows apps. Sur le fait que retirer RequestedTheme fait suivre à l’application le paramètre système, que le système remplace RequestedTheme lorsque l’utilisateur choisit un thème de contraste élevé, et que les modèles personnalisés devraient utiliser des pinceaux de thème plutôt que des couleurs codées en dur. ↩
-
Microsoft Learn, DwmSetWindowAttribute function (dwmapi.h). Sur la fonction qui définit les attributs de rendu DWM de la zone non cliente d’une fenêtre, et sa disponibilité à partir de Windows Vista. ↩
-
Microsoft Learn, Retrieve a window handle (HWND). Sur l’obtention du Handle depuis WindowInteropHelper dans WPF. ↩
-
Microsoft Learn, DWM_SYSTEMBACKDROP_TYPE enumeration (dwmapi.h). Sur le fait que DWMSBT_MAINWINDOW correspond à Mica et DWMSBT_TRANSIENTWINDOW à Acrylic sous Windows 11, que l’effet du matériau peut changer dans les versions futures de Windows, et sur la prise en charge à partir de Windows 11 build 22621. ↩
-
Microsoft Learn, UISettings.GetColorValue(UIColorType) Method. Sur la méthode qui renvoie la valeur de couleur du UIColorType spécifié. ↩
-
Microsoft Learn, WM_SETTINGCHANGE message. Sur le message envoyé à toutes les fenêtres de premier niveau lorsque SystemParametersInfo change un paramètre à l’échelle du système ou lorsqu’un paramètre de stratégie change. ↩
-
Microsoft Learn, SystemEvents.UserPreferenceChanged Event. Sur l’événement statique déclenché lorsqu’une préférence utilisateur change, et la fuite de mémoire qui résulte de ne pas détacher le gestionnaire. ↩
-
Microsoft Learn, WM_THEMECHANGED message. Sur le fait qu’il est diffusé à toutes les fenêtres après qu’un thème est activé, désactivé ou basculé, et que les handles de thème existants deviennent invalides et doivent être rouverts. ↩
-
Microsoft Learn, WM_SYSCOLORCHANGE message. Sur le fait qu’il est envoyé à toutes les fenêtres de premier niveau lorsqu’un paramètre de couleur système change, que les pinceaux qui utilisent des couleurs système doivent être recréés, et qu’il doit être transmis aux contrôles communs. ↩
-
Microsoft Learn, Compiler Error WFO5001. Sur le fait que SetColorMode et SystemColorMode étaient protégés comme fonctionnalités expérimentales à évaluer dans .NET 9, et que l’erreur ne s’applique pas à partir de .NET 10. ↩
-
Microsoft Learn, SystemColors.UseAlternativeColorSet Property. Sur le fait que la mettre à true fait que les valeurs KnownColor système renvoient un jeu de couleurs alternatif (actuellement la version du mode sombre), que c’est une fonctionnalité expérimentale sous SYSLIB5002, et que les valeurs KnownColor système renvoient toujours les couleurs Windows actuelles lorsqu’un thème de contraste élevé est actif dans Windows. ↩
-
Microsoft Learn, HIGHCONTRASTW structure (winuser.h). Sur HCF_HIGHCONTRASTON (0x00000001) dans dwFlags, et la nécessité de spécifier cbSize lors de l’utilisation avec SPI_GETHIGHCONTRAST. ↩
-
Microsoft Learn, SystemParameters.HighContrast Property. Sur la propriété statique WPF qui correspond à SPI_GETHIGHCONTRAST et HCF_HIGHCONTRASTON. ↩
-
Microsoft Learn, SystemParameters.StaticPropertyChanged Event. Sur l’événement statique déclenché lorsqu’une propriété quelconque de SystemParameters change. ↩ ↩2
-
Microsoft Learn, ThemeSettings Class (Microsoft.UI.System). Sur le créer lié à une fenêtre avec CreateForWindowId et recevoir les changements de contraste élevé via l’événement Changed, et sur le fait que relâcher la référence détruit l’objet et que l’événement ne se déclenche plus. ↩
-
Microsoft Learn, SystemColors.WindowBrushKey Property. Sur le fait qu’une référence dynamique faite avec la clé de ressource se met à jour automatiquement lorsque le pinceau change, et qu’une référence statique via WindowBrush ne le fait pas. ↩
-
Microsoft Learn, ResourceDictionary.ThemeDictionaries Property (Microsoft.UI.Xaml). Sur le fait qu’un contrôle personnalisé avec des dictionnaires de thème Light et Dark devrait aussi fournir un dictionnaire HighContrast, que HighContrast est la clé de repli lorsqu’il n’existe pas d’autre thème de contraste élevé, que Default est utilisé lorsqu’aucun ResourceDictionary pour le thème n’est trouvé, et que des ressources de couleur système telles que SystemColorButtonFaceColor peuvent être utilisées dans HighContrast. ↩
-
Microsoft Learn, Windows app development best practices. Sur le fait que Windows 11 s’est éloigné du blanc pur et du noir pur vers des tons plus faciles pour les yeux, et que les thèmes sombres et clairs sont un moyen de s’adapter à la préférence visuelle de l’utilisateur. ↩
-
Microsoft Learn, Color (Windows UX guidelines). Sur l’utilisation de la couleur comme renforcement visuel plutôt que comme moyen premier de communication, le choix des couleurs de thème et des couleurs système par usage et l’utilisation du premier plan et de l’arrière-plan en paires assorties, le traitement des changements de thème avec WM_THEMECHANGED, et le fait que High Contrast Black correspond à Aquatic et High Contrast White à Desert sous Windows 11. ↩
Articles associés
Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.
Accessibilité des applications Windows — UI Automation et l'obligation d'aménagement raisonnable
Comment les lecteurs d'écran lisent les applications Windows via UI Automation : nommage WinForms/WPF, clavier, contraste et outils de vé...
Fin de la maintenance des pilotes d'imprimante Windows — Comment les applications métier doivent préparer l'impression des rapports et des étiquettes
Microsoft met progressivement fin aux pilotes d'imprimante v3/v4. Ce que Windows protected print mode retire, et comment inventorier et p...
Ce qu'est vraiment « Ne répond pas » — comment Windows juge qu'une application est bloquée, et comment concevoir des applications qui ne le sont pas
Le « Ne répond pas » de Windows est un mécanisme dans lequel l'OS juge qu'une fenêtre n'a pas récupéré de message pendant 5 secondes et l...
Comment fonctionnent le presse-papiers et le glisser-déposer — traiter correctement le transfert de données OLE dans les applications métier
Un tableau Excel se défait à coller, et coller échoue une fois la source fermée : le presse-papiers place le même contenu dans plusieurs ...
Intégrer l'authentification Entra ID dans une application WinForms/WPF — Architecture pratique avec MSAL.NET et le broker WAM
Guide pratique pour intégrer l'authentification Entra ID (ex Azure AD) dans une application de bureau WinForms/WPF : la logique du client...
Sujets associés
Ces pages replacent le sujet dans un contexte plus large de services et de décisions.
Thèmes techniques Windows
Portail des sujets sur le développement Windows, l'analyse des incidents et la valorisation des actifs existants.
Thread UI et minuteries
Thread UI WPF / WinForms, flux asynchrones, Dispatcher et temporisation.
Services liés à ce sujet
Cet article est directement lié aux services suivants.
Développement d'applications Windows
Applications métier, intégration d'équipements et outils de communication, des besoins au développement.
Questions fréquentes
Questions souvent posées lors d’une consultation sur le sujet de cet article.
- J'ai basculé Windows en mode sombre, mais la barre de titre de notre application WinForms interne reste blanche. Pourquoi ?
- Parce que Windows n'a aucun moyen de savoir si une application prend en charge le mode sombre, et que, pour la compatibilité, il traite donc chaque fenêtre comme étant en mode clair par défaut. La zone non cliente, barre de titre comprise, est dessinée par le Gestionnaire de fenêtres du Bureau (DWM), et le cadre n'est dessiné en sombre lorsque le système est sombre qu'après que l'application a passé TRUE pour DWMWA_USE_IMMERSIVE_DARK_MODE (valeur 20) via DwmSetWindowAttribute. La prise en charge de cet attribut est documentée pour Windows 11 build 22000 et versions ultérieures. Si vous utilisez Application.SetColorMode dans WinForms sous .NET 9 ou une version ultérieure, ou ThemeMode dans WPF sous .NET 9 ou une version ultérieure, le framework effectue cet appel pour vous, donc l'appeler vous-même n'est nécessaire que dans les applications sous .NET 8 ou antérieur, .NET Framework, ou Win32/MFC. Notez qu'une barre de titre sombre au-dessus d'une zone cliente qui reste blanche est pire, pas mieux. N'activez cet attribut que lorsque vous êtes prêt à repeindre toute l'application en sombre.
- Le mode sombre et les thèmes de contraste (contraste élevé) sont-ils la même chose ?
- Non. Clair/sombre est le mode de couleur sous Paramètres > Personnalisation > Couleurs, et il utilise une palette large qui inverse la luminosité du premier plan et de l'arrière-plan. Un thème de contraste se choisit sous Paramètres > Accessibilité > Thèmes de contraste et utilise une palette contrainte avec un ratio de contraste d'environ 7:1 ou plus (les quatre thèmes fournis Aquatic, Desert, Dusk et Night sky, plus toutes les couleurs que l'utilisateur a éditées). La documentation Microsoft dit explicitement de ne pas confondre les deux, et le mode sombre n'est pas disponible tant qu'un thème de contraste est actif (SetColorMode dans WinForms ne fournit pas le mode sombre pendant un thème de contraste, et RequestedTheme en XAML est remplacé par le système). Dans l'implémentation, vérifiez d'abord si un thème de contraste est actif et, le cas échéant, remettez-vous entièrement aux couleurs système ; seulement sinon, choisissez la palette claire ou sombre. C'est l'ordre de priorité.
- Quel est le chemin le plus court pour faire prendre en charge le mode sombre à une application WinForms ?
- Sous .NET 9 ou une version ultérieure, le chemin le plus court est d'appeler Application.SetColorMode(SystemColorMode.System) avant Application.Run dans Program.cs. Sous .NET 9 c'était une fonctionnalité expérimentale, donc WFO5001 devait être masqué dans le fichier projet ; à partir de .NET 10, cela fonctionne sans masquage. L'appel à SetColorMode bascule SystemColors vers un jeu alternatif pour le mode sombre, et les contrôles standard sont dessinés en conséquence. Il y a trois précautions. Premièrement, le mode sombre n'est disponible que sous Windows 11 ou une version ultérieure et est désactivé tant qu'un thème de contraste est actif. Deuxièmement, même avec SystemColorMode.System, l'application ne suit pas un changement du paramètre Windows pendant qu'elle s'exécute (le changement prend effet au lancement suivant). Troisièmement, si un contrôle dessiné par le propriétaire utilise des contrôles communs Win32 tels que les barres de défilement, vous devez remplacer CreateParams et appeler SetStyle(ControlStyles.ApplyThemingImplicitly, true) avant base.CreateParams (le constructeur arrive trop tard).
- De quoi une application WPF a-t-elle besoin pour suivre le mode sombre ?
- WPF sous .NET 9 ou une version ultérieure livre un nouveau thème qui suit le design Fluent de Windows 11, et écrire ThemeMode="System" sur l'élément Application dans App.xaml suffit à charger le thème Fluent qui correspond au paramètre clair/sombre de Windows. ThemeMode contrôle aussi l'assombrissement de la fenêtre (la barre de titre) et l'application du matériau d'arrière-plan. Cependant, la lecture et l'écriture de la propriété ThemeMode depuis le code restent expérimentales dans .NET 10 (WPF0001), et les styles Fluent eux-mêmes sont décrits comme "still in progress" dans la documentation de .NET 10. Avant de l'adopter dans une application métier, évaluez si les contrôles que vous utilisez s'affichent correctement sous Fluent. Si vous restez sur le thème classique (il en va de même pour .NET 8 et antérieur et pour .NET Framework), préparez un ResourceDictionary clair et un sombre, échangez-les dans MergedDictionaries, référencez-les depuis XAML avec DynamicResource, et utilisez UISettings.ColorValuesChanged pour détecter le basculement. Pour la barre de titre, obtenez le HWND depuis WindowInteropHelper dans SourceInitialized et appelez DwmSetWindowAttribute.
- Pourquoi le texte disparaît-il ou devient-il illisible sous un thème de contraste (contraste élevé) ?
- Les causes typiques sont des couleurs codées en dur, ou la rupture de l'appariement des couleurs système de premier plan et d'arrière-plan. Sous un thème de contraste, l'utilisateur peut éditer librement les couleurs d'arrière-plan, de texte, de lien et d'autres, donc toute hypothèse du type "le texte sera noir" ou "la ligne sélectionnée sera bleu clair" s'écroule. Par exemple, si seul l'arrière-plan est fixé à #E6E6E6, certains thèmes donnent un premier plan blanc, et du texte blanc sur gris clair devient illisible. Il y a trois principes. Détectez l'état avec SPI_GETHIGHCONTRAST (SystemInformation.HighContrast dans WinForms, SystemParameters.HighContrast dans WPF) ; remplacez chaque couleur par la paire de couleurs système correcte (WindowText avec Window, ButtonText avec ButtonFace, HighlightText avec Highlight) ; et abandonnez les images derrière le texte et les graphismes multicolores, en dessinant uniquement avec les couleurs de premier plan et d'arrière-plan. GrayText ne doit servir à rien d'autre que l'état désactivé, et Hotlight à rien d'autre que les hyperliens. Les changements sont annoncés par WM_SYSCOLORCHANGE et WM_THEMECHANGED (SystemEvents.UserPreferenceChanged dans .NET), donc recalculez les couleurs à cet endroit et repeignez.
Profil de l’auteur
Page de présentation de l’auteur de l’article.
Go Komura
Représentant de KomuraSoft LLC
Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.