Skip to content

Commit ef86376

Browse files
authored
Add article for Win32 dark mode support (#2546)
* Add window theme doc, initial content and edit pass * edit pass * add link to new Win32 dark mode doc * updates and images
1 parent 50951ab commit ef86376

File tree

9 files changed

+282
-2
lines changed

9 files changed

+282
-2
lines changed
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
---
2+
description: Learn how to detect system theme changes for dark or light mode.
3+
title: Support Dark and Light themes in Win32 apps
4+
ms.topic: article
5+
ms.date: 05/05/2022
6+
ms.author: jimwalk
7+
author: jwmsft
8+
ms.localizationpriority: medium
9+
---
10+
11+
# Support Dark and Light themes in Win32 apps
12+
13+
Windows supports _Light_ and _Dark_ themes as a personalization option in Windows settings. Windows uses Light mode by default, but users can choose Dark mode, which changes much of the UI to a dark color. Users might prefer this setting because it's easier on the eyes in lower-light environments, or they might simply prefer a darker interface in general. Also, darker UI colors can reduce the battery usage on some types of computer displays, such as OLED screens.
14+
15+
:::image type="content" source="./images/apply-design/themes.png" alt-text="A split image of an app in light theme on the left, and dark theme on the right.":::
16+
17+
We are working hard to broaden support for Dark mode without breaking existing applications, and to that end we're providing technical guidance for updating a Win32 desktop Windows app to support both Light and Dark modes.
18+
19+
## Dark mode vs. Light mode
20+
21+
The Color Mode in settings (which includes Light and Dark modes) is a setting that defines the overall _foreground_ and _background_ colors for the operating system and apps.
22+
23+
| Mode | Description | Example |
24+
|--|---------|---------|
25+
| **Light** | A light background with a contrasting dark foreground.<br/><br/>In Light Mode, you will generally see black or dark text on white or light backgrounds. | :::image type="content" source="./images/apply-design/clock-app-light.png" alt-text="A screenshot of the Alarms & Clock app in light mode"::: |
26+
| **Dark** | A dark background with a contrasting light foreground.<br/><br/>In Dark mode, you will generally see white or light text on black or dark backgrounds. | :::image type="content" source="./images/apply-design/clock-app-dark.png" alt-text="A screenshot of the Alarms & Clocks app in Dark mode"::: |
27+
28+
> [!NOTE]
29+
> The reason we use "_black or dark_" and "_white or light_" is because there are additional colors such as the Accent color that can tint various foreground and background colors. So you might in fact see light blue text on a dark blue background in some parts of the UI, and that would still be considered acceptable Dark mode UI.
30+
31+
Due to the wide diversity of UI in different apps, the color mode, and foreground and background colors are meant as more of a directional guideline than a hard rule:
32+
33+
- Foreground elements, highlights, and text should be closer to the foreground color than the background color.
34+
- Large, solid background areas and text backgrounds should generally be closer to the background color than the foreground color.
35+
36+
In practice, this means that in Dark mode, most of the UI will be dark, and in Light mode most of the UI will be light. The concept of a background in Windows is the large area of colors in an app, or the page color. The concept of a foreground in Windows is the text color.
37+
38+
> [!TIP]
39+
> If you find it confusing that the _foreground_ color is light in Dark mode and dark in Light mode, it may help to think of the foreground color as "the default text color".
40+
41+
## Enable support for switching color modes
42+
43+
There are many approaches to implementing Dark mode support in an application. Some apps contain two sets of UIs (one with a light color and one with a dark color). Some Windows UI frameworks, such as [WinUI 3](/windows/apps/winui/winui3/), automatically detect a system's theme and adjust the UI to follow the system theme. To fully support Dark mode, the entirety of an app's surface must follow the dark theme.
44+
45+
There are two main things you can do in your Win32 app to support both Light and Dark themes.
46+
47+
- **Know when Dark mode is enabled**
48+
49+
Knowing when Dark mode is enabled in the system settings can help you know when to switch your app UI to a Dark mode-themed UI.
50+
51+
- **Enable a Dark mode title bar for Win32 applications**
52+
53+
Not all Win32 applications support Dark mode, so Windows gives Win32 apps a light title bar by default. If you are prepared to support Dark mode, you can ask Windows to draw the dark title bar instead when Dark mode is enabled.
54+
55+
> [!NOTE]
56+
> This article provides examples of ways to detect system theme changes, and request a light or dark title bar for your Win32 application's window. It does not cover specifics of how to repaint and render your app UI using a Dark mode color set.
57+
58+
## Know when Dark mode is enabled
59+
60+
The first step is to keep track of the color mode setting itself. This will let you adjust your application's painting and rendering code to use a Dark mode color set. Doing this requires the app to read the color setting at startup and to know when the color setting changes during an app session.
61+
62+
To do this in a Win32 application, use [Windows::UI::Color](/uwp/api/windows.ui.color) and detect if a color can be classified as _light_ or _dark_. To use `Windows::UI::Color`, you need to import (in `pch.h`) the `Windows.UI.ViewManagement` header from winrt.
63+
64+
```cpp
65+
#include <winrt/Windows.UI.ViewManagement.h>
66+
```
67+
68+
Also include that namespace in `main.cpp`.
69+
70+
```cpp
71+
using namespace Windows::UI::ViewManagement;
72+
```
73+
74+
In `main.cpp`, use this function to detect if a color can be classified as _light_.
75+
76+
```cpp
77+
inline bool IsColorLight(Windows::UI::Color& clr)
78+
{
79+
return (((5 * clr.G) + (2 * clr.R) + clr.B) > (8 * 128));
80+
}
81+
```
82+
83+
This function performs a quick calculation of the _perceived brightness_ of a color, and takes into consideration ways that different channels in an RGB color value contribute to how bright it looks to the human eye. It uses all-integer math for speed on typical CPUs.
84+
85+
> [!NOTE]
86+
> This is not a model for real analysis of color brightness. It is good for quick calculations that require you to determine if a color can be classified as _light_ or _dark_. Theme colors can often be light but not pure white, or dark but not pure black.
87+
88+
Now that you have a function to check whether a color is light, you can use that function to detect if Dark mode is enabled.
89+
90+
Dark mode is defined as a dark background with a contrasting light foreground. Since `IsColorLight` checks if a color is considered light, you can use that function to see if the foreground is light. If the foreground is light, then Dark mode is enabled.
91+
92+
To do this, you need to get the UI color type of the foreground from the system settings. Use this code in `main.cpp`.
93+
94+
```cpp
95+
auto settings = UISettings();
96+
97+
auto foreground = settings.GetColorValue(UIColorType::Foreground);
98+
```
99+
100+
[UISettings](/uwp/api/windows.ui.viewmanagement.uisettings) gets all the settings of the UI including color. Call [UISettings.GetColorValue](/uwp/api/windows.ui.viewmanagement.uisettings.getcolorvalue)([UIColorType::Foreground](/uwp/api/windows.ui.viewmanagement.uicolortype)) to get the foreground color value from the UI settings.
101+
102+
Now you can run a check to see if the foreground is considered light (in `main.cpp`).
103+
104+
```cpp
105+
bool isDarkMode = static_cast<bool>(IsColorLight(foreground));
106+
107+
wprintf(L"\nisDarkMode: %u\n", isDarkMode);
108+
```
109+
110+
- If the foreground is light, then `isDarkMode` will evaluate to 1 (`true`) meaning Dark mode is enabled.
111+
- If the foreground is dark, then `isDarkMode` will evaluate to 0 (`false`) meaning Dark mode is not enabled.
112+
113+
To automatically track when the Dark mode setting changes during an app session, you can wrap your checks like this.
114+
115+
```cpp
116+
auto revoker = settings.ColorValuesChanged([settings](auto&&...)
117+
{
118+
auto foregroundRevoker = settings.GetColorValue(UIColorType::Foreground);
119+
bool isDarkModeRevoker = static_cast<bool>(IsColorLight(foregroundRevoker));
120+
wprintf(L"isDarkModeRevoker: %d\n", isDarkModeRevoker);
121+
});
122+
123+
```
124+
125+
Your full code should look like this.
126+
127+
```cpp
128+
inline bool IsColorLight(Windows::UI::Color& clr)
129+
{
130+
return (((5 * clr.G) + (2 * clr.R) + clr.B) > (8 * 128));
131+
}
132+
133+
int main()
134+
{
135+
init_apartment();
136+
137+
auto settings = UISettings();
138+
auto foreground = settings.GetColorValue(UIColorType::Foreground);
139+
140+
bool isDarkMode = static_cast<bool>(IsColorLight(foreground));
141+
wprintf(L"\nisDarkMode: %u\n", isDarkMode);
142+
143+
auto revoker = settings.ColorValuesChanged([settings](auto&&...)
144+
{
145+
auto foregroundRevoker = settings.GetColorValue(UIColorType::Foreground);
146+
bool isDarkModeRevoker = static_cast<bool>(IsColorLight(foregroundRevoker));
147+
wprintf(L"isDarkModeRevoker: %d\n", isDarkModeRevoker);
148+
});
149+
150+
static bool s_go = true;
151+
while (s_go)
152+
{
153+
Sleep(50);
154+
}
155+
}
156+
```
157+
158+
When this code is run:
159+
160+
If Dark mode is enabled, `isDarkMode` will evaluate to 1.
161+
162+
:::image type="content" source="./images/apply-design/sample-app-dark.png" alt-text="A screenshot of an app in dark mode.":::
163+
164+
Changing the setting from Dark mode to Light mode will make `isDarkModeRevoker` evaluate to 0.
165+
166+
:::image type="content" source="./images/apply-design/sample-app-light.png" alt-text="A screenshot of an app in light mode.":::
167+
168+
## Enable a Dark mode title bar for Win32 applications
169+
170+
Windows doesn't know if an application can support Dark mode, so it assumes that it can't for backwards compatibility reasons. Some Windows development frameworks, such as [Windows App SDK](/windows/apps/windows-app-sdk/), support Dark mode natively and change certain UI elements without any additional code. Win32 apps often don't support Dark mode, so Windows gives Win32 apps a light title bar by default.
171+
172+
However, for any app that uses the standard Windows title bar, you can enable the dark version of the title bar when the system is in Dark mode. To enable the dark title bar, call a [Desktop Windows Manager](/windows/win32/dwm/dwm-overview) (DWM) function called [DwmSetWindowAttribute](/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute) on your top-level window, using the window attribute [DWMWA_USE_IMMERSIVE_DARK_MODE](/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute). (DWM renders attributes for a window.)
173+
174+
The following examples assume you have a window with with a standard title bar, like the one created by this code.
175+
176+
```cpp
177+
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
178+
{
179+
hInst = hInstance; // Store instance handle in our global variable
180+
181+
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0,
182+
CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
183+
184+
if (!hWnd)
185+
{
186+
return FALSE;
187+
}
188+
189+
ShowWindow(hWnd, nCmdShow);
190+
UpdateWindow(hWnd);
191+
192+
return TRUE;
193+
}
194+
```
195+
196+
First, you need to import the DWM API, like this.
197+
198+
```cpp
199+
#include <dwmapi.h>
200+
```
201+
202+
Then, define the `DWMWA_USE_IMMERSIVE_DARK_MODE` macros above your `InitInstance` function.
203+
204+
```cpp
205+
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
206+
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
207+
#endif
208+
209+
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
210+
{
211+
212+
```
213+
214+
Finally, you can use the DWM API to set the title bar to use a dark color. Here, you create a `BOOL` called `value` and set it to `TRUE`. This `BOOL` is used to trigger this Windows attribute setting. Then, you use `DwmSetWindowAttribute` to change the window attribute to use Dark mode colors.
215+
216+
```cpp
217+
BOOL value = TRUE;
218+
::DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
219+
```
220+
221+
Here's more explanation of what this call does.
222+
223+
The syntax block for [DwmSetWindowAttribute](/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute) looks like this.
224+
225+
```cpp
226+
HRESULT DwmSetWindowAttribute(
227+
HWND hwnd,
228+
DWORD dwAttribute,
229+
[in] LPCVOID pvAttribute,
230+
DWORD cbAttribute
231+
);
232+
```
233+
234+
After passing `hWnd` (the handle to the window you want to change) as your first parameter, you need to pass in `DWMA_USE_IMMERSIVE_DARK_MODE` as the `dwAttribute` parameter. This is a constant in the DWM API that lets the Windows frame be drawn in Dark mode colors when the Dark mode system setting is enabled. If you switch to Light mode, you will have to change `DWMA_USE_IMMERSIVE_DARK_MODE` from 20 to 0 for the title bar to be drawn in light mode colors.
235+
236+
The `pvAttribute` parameter points to a value of type `BOOL` (which is why you made the `BOOL` value earlier). You need `pvAttribute` to be `TRUE` to honor Dark mode for the window. If `pvAttribute` is `FALSE`, the window will use Light Mode.
237+
238+
Lastly, `cbAttribute` needs to have the size of the attribute being set in `pvAttribute`. To do easily do this, we pass in `sizeof(value)`.
239+
240+
Your code to draw a dark windows title bar should look like this.
241+
242+
```cpp
243+
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
244+
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
245+
#endif
246+
247+
248+
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
249+
{
250+
hInst = hInstance; // Store instance handle in our global variable
251+
252+
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
253+
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
254+
255+
BOOL value = TRUE;
256+
::DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));
257+
258+
if (!hWnd)
259+
{
260+
return FALSE;
261+
}
262+
263+
ShowWindow(hWnd, nCmdShow);
264+
UpdateWindow(hWnd);
265+
266+
return TRUE;
267+
}
268+
```
269+
270+
When this code is run, the app title bar should be dark:
271+
272+
:::image type="content" source="./images/apply-design/title-bar-dark.png" alt-text="A screenshot of an app with a dark title bar.":::
273+
274+
## See also
275+
276+
- [Make your app great on Windows 11](/windows/apps/get-started/make-apps-great-for-windows)
277+
- [Accessible text requirements](/windows/apps/design/accessibility/accessible-text-requirements)
278+
- [Desktop Windows Manager (DWM)](/windows/win32/dwm/dwm-overview)
10.7 KB
Loading
10.5 KB
Loading
60 KB
Loading
62.2 KB
Loading
33.3 KB
Loading
7.83 KB
Loading

hub/apps/get-started/make-apps-great-for-windows.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
description: The top 11 things you can do to make your app great on Windows 11.
33
title: Top 11 things you can do to make your app great on Windows 11
44
ms.topic: article
5-
ms.date: 03/16/2022
5+
ms.date: 05/06/2022
66
keywords: windows win32, desktop development
77
ms.author: jimwalk
88
author: jwmsft
@@ -125,7 +125,7 @@ We support Light and Dark themes, which is a great way to let the user express t
125125
![A split image of an app in light theme on the left, and dark theme on the right.](images/great-apps/themes.png)
126126

127127
- The color palette of WinUI is being updated to feel lighter (use WinUI 2.6 or greater). If your apps have hardcoded custom colors, you may need to make updates to match the overall color theory, regardless of technology.
128-
- If you are using [UXTheme](/windows/win32/api/uxtheme/) based Win32 surfaces, the Light theme will have rejuvenated controls (for example, rounded buttons). You should test your apps to validate that local styling does not override updated global defaults.
128+
- If you are using [UXTheme](/windows/win32/api/uxtheme/) based Win32 surfaces, the Light theme will have rejuvenated controls (for example, rounded buttons). You should test your apps to validate that local styling does not override updated global defaults. (For Win32 apps, see [Support Dark and Light themes in Win32 apps](../desktop/modernize/apply-windows-themes.md).)
129129

130130
## 8. Optimize your app's context menu extensions and Share targets
131131

hub/apps/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ items:
138138
href: desktop/modernize/apply-rounded-corners.md
139139
- name: Support snap layouts on Windows 11
140140
href: desktop/modernize/apply-snap-layout-menu.md
141+
- name: Support Dark and Light themes
142+
href: desktop/modernize/apply-windows-themes.md
141143
- name: Call Windows Runtime APIs
142144
items:
143145
- name: Overview

0 commit comments

Comments
 (0)