다음을 통해 공유


Win32 앱에서 어둡고 밝은 테마 지원

Windows 설정의 개인화 옵션으로 밝음어두운 테마를 지원합니다. Windows는 기본적으로 밝은 모드를 사용하지만 사용자는 UI의 대부분을 어두운 색으로 변경하는 어두운 모드를 선택할 수 있습니다. 사용자는 낮은 조명 환경에서 눈에 쉽게 표시되기 때문에 이 설정을 선호하거나 일반적으로 더 어두운 인터페이스를 선호할 수 있습니다. 또한 어두운 UI 색은 OLED 화면과 같은 일부 유형의 컴퓨터 디스플레이에서 배터리 사용량을 줄일 수 있습니다.

왼쪽은 밝은 테마, 오른쪽은 어두운 테마인 앱 분할 이미지

기존 애플리케이션을 중단하지 않고 어두운 모드에 대한 지원을 확대하기 위해 노력하고 있으며, 이를 위해 밝은 모드와 어두운 모드를 모두 지원하도록 Win32 데스크톱 Windows 앱을 업데이트하기 위한 기술 가이드라인을 제공하고 있습니다.

어두운 모드 vs 밝은 모드

설정의 색 모드(밝음 및 어두운 모드 포함)는 운영 체제 및 앱에 대한 전체 포그라운드백그라운드 색을 정의하는 설정입니다.

모드 설명 예제
밝게 어두운 포그라운드가 대비되는 밝은 백그라운드입니다.

밝은 모드에서는 일반적으로 흰색 또는 밝은 백그라운드에 검은색 또는 어두운 텍스트가 표시됩니다.
밝은 모드의 알람 & 시계 앱 스크린샷
어둡게 밝은 포그라운드가 대비되는 어두운 백그라운드입니다.

어두운 모드에서는 일반적으로 검은색 또는 어두운 백그라운드에 흰색 또는 밝은 텍스트가 표시됩니다.
어두운 모드의 알람 & 시계 앱 스크린샷

참고 항목

'검은색 또는 어둡게' 및 '흰색 또는 밝음'을 사용하는 이유는 다양한 포그라운드 및 백그라운드을 착색할 수 있는 강조 색과 같은 추가 색이 있기 때문입니다. 따라서 실제로 UI의 일부 부분에서 진한 파란색 백그라운드에 연한 파란색 텍스트가 표시될 수 있으며 이는 여전히 허용되는 어두운 모드 UI로 간주됩니다.

다양한 앱, 다양한 UI 때문에 색 모드와 포그라운드 및 백그라운드는 하드 규칙보다 방향 지침에 더 적합합니다.

  • 포그라운드 요소, 강조 표시 및 텍스트는 백그라운드보다 포그라운드에 더 가깝습니다.
  • 크고 단색 배경 영역 및 텍스트 배경은 일반적으로 포그라운드보다 백그라운드에 더 가깝습니다.

실제로 어두운 모드에서는 대부분의 UI가 어둡게 표시되고 밝은 모드에서는 대부분의 UI가 밝아집니다. Windows에서 배경의 개념은 앱의 색 또는 페이지 색의 넓은 영역입니다. Windows에서 포그라운드의 개념은 텍스트 색입니다.

포그라운드색이 어두운 모드에서 밝은 것과 밝은 모드에서 어두운 것이 혼동되는 경우 포그라운드를 '기본 텍스트 색'으로 생각하는 것이 도움이 될 수 있습니다.

색 모드 전환 지원 사용

애플리케이션에서 어두운 모드 지원을 구현하는 방법에는 여러 가지가 있습니다. 일부 앱에는 두 가지 UI 세트(하나는 밝은색으로, 다른 하나는 어두운 색으로)가 포함되어 있습니다. WinUI 3와 같은 일부 Windows UI 프레임워크는 자동으로 시스템의 테마를 검색하고 시스템 테마를 따르도록 UI를 조정합니다. 어두운 모드를 완벽하게 지원하려면 앱 화면 전체가 어두운 테마를 따라야 합니다.

밝은 테마와 어두운 테마를 모두 지원하기 위해 Win32 앱에서 수행해야 하는 두 가지 주요 사항이 있습니다.

  • 어두운 모드가 활성화된 시점 파악

    시스템 설정에서 어두운 모드가 활성화된 시점을 알면 앱 UI를 어두운 모드 테마 UI로 전환해야 하는 시기를 알 수 있습니다.

  • Win32 애플리케이션에 대해 어두운 모드 제목 표시줄 사용

    모든 Win32 애플리케이션이 어두운 모드를 지원하는 것은 아니므로 Windows는 기본적으로 Win32 앱에 밝은 제목 표시줄을 제공합니다. 어두운 모드를 지원할 준비가 되었으면 어두운 모드를 사용할 때 어두운 제목 표시줄을 그리도록 Windows에 요청할 수 있습니다.

참고

이 문서에서는 시스템 테마 변경 내용을 탐지하고 Win32 애플리케이션 창에 밝은 제목 또는 어두운 제목 표시줄을 요청하는 방법의 예를 제공합니다. 어두운 모드 색 집합을 사용하여 앱 UI를 다시 칠하고 렌더링하는 방법에 대한 자세한 내용은 다루지 않습니다.

어두운 모드가 활성화된 시점 파악

첫 번째 단계는 색 모드 설정 자체를 추적하는 것입니다. 이렇게 하면 어두운 모드 색 집합을 사용하도록 애플리케이션의 그리기 및 렌더링 코드를 조정할 수 있습니다. 이렇게 하려면 앱이 시작할 때 색 설정을 읽고 앱 세션 중에 색 설정이 변경되는 시점을 알아야 합니다.

Win32 애플리케이션에서 이 작업을 수행하려면 Windows::UI::Color를 사용하고 색을 밝게 또는 어둡게 분류할 수 있는지 검색합니다. Windows::UI::Color을(를) 사용하려면 winrt에서 Windows.UI.ViewManagement 헤더를(pch.h에서) 가져와야 합니다.

#include <winrt/Windows.UI.ViewManagement.h>

또한 해당 네임스페이스를 main.cpp에 포함합니다.

using namespace Windows::UI::ViewManagement;

main.cpp에서 이 함수를 사용하여 색을 밝게 분류할 수 있는지 검색합니다.

inline bool IsColorLight(Windows::UI::Color& clr)
{
    return (((5 * clr.G) + (2 * clr.R) + clr.B) > (8 * 128));
}

이 함수는 색의 인식된 밝기를 빠르게 계산하고 RGB 색 값의 다른 채널이 사람의 눈에 얼마나 밝게 보이는지에 기여하는 방식을 고려합니다. 일반적인 CPU의 속도에 모든 정수 수학을 사용합니다.

참고

색 밝기를 실제로 분석하기 위한 모델이 아닙니다. 색을 밝게 또는 어둡게 분류할 수 있는지 여부를 결정해야 하는 빠른 계산에 적합합니다. 테마 색은 밝지만 순수한 흰색이 아니거나 어둡지만 순수한 검은색이 아닐 수 있습니다.

이제 색이 밝은지 여부를 확인하는 함수가 있으므로 해당 함수를 사용하여 어두운 모드를 사용할 수 있는지 탐지할 수 있습니다.

어두운 모드는 밝은 전경이 대조되는 어두운 배경으로 정의됩니다. IsColorLight는 색이 밝은 것으로 간주되는지 확인하므로 해당 함수를 사용하여 전경이 밝은지 확인할 수 있습니다. 포그라운드가 밝으면 어두운 모드가 활성화됩니다.

이렇게 하려면 시스템 설정에서 포그라운드의 UI 색 형식을 가져와야 합니다. main.cpp에서 이 코드를 사용합니다.

auto settings = UISettings();
    
auto foreground = settings.GetColorValue(UIColorType::Foreground);

UISettings는 색을 포함한 UI의 모든 설정을 가져옵니다. UISettings.GetColorValue(UIColorType::Foreground)를 호출하여 UI 설정에서 포그라운드 색 값을 가져옵니다.

이제 검사를 실행하여 포그라운드가 main.cpp에서 밝은 것으로 간주되는지 확인할 수 있습니다.

bool isDarkMode = static_cast<bool>(IsColorLight(foreground));

wprintf(L"\nisDarkMode: %u\n", isDarkMode);
  • 포그라운드가 밝으면 isDarkMode가 어두운 모드가 사용됨을 의미하는 1(true)로 평가됩니다.
  • 포그라운드가 어두우면 isDarkMode가 어두운 모드가 사용되지 않음을 의미하는 0(false)으로 평가됩니다.

앱 세션 중에 어두운 모드 설정이 변경되는 시기를 자동으로 추적하려면 다음과 같이 검사를 래핑할 수 있습니다.

auto revoker = settings.ColorValuesChanged([settings](auto&&...)
{
    auto foregroundRevoker = settings.GetColorValue(UIColorType::Foreground);
    bool isDarkModeRevoker = static_cast<bool>(IsColorLight(foregroundRevoker));
    wprintf(L"isDarkModeRevoker: %d\n", isDarkModeRevoker);
});

전체 코드는 다음과 같아야 합니다.

inline bool IsColorLight(Windows::UI::Color& clr)
{
    return (((5 * clr.G) + (2 * clr.R) + clr.B) > (8 * 128));
}

int main()
{
    init_apartment();

    auto settings = UISettings();
    auto foreground = settings.GetColorValue(UIColorType::Foreground);

    bool isDarkMode = static_cast<bool>(IsColorLight(foreground));
    wprintf(L"\nisDarkMode: %u\n", isDarkMode);

    auto revoker = settings.ColorValuesChanged([settings](auto&&...)
        {
            auto foregroundRevoker = settings.GetColorValue(UIColorType::Foreground);
            bool isDarkModeRevoker = static_cast<bool>(IsColorLight(foregroundRevoker));
            wprintf(L"isDarkModeRevoker: %d\n", isDarkModeRevoker);
        });
    
    static bool s_go = true;
    while (s_go)
    {
        Sleep(50);
    }
}

이 코드를 실행하는 경우:

어두운 모드가 사용하도록 설정하면 isDarkMode은(는) 1로 평가됩니다.

어두운 모드에 있는 앱의 스크린샷.

설정을 어두운 모드에서 밝은 모드로 변경하면 isDarkModeRevoker이(가) 0으로 평가됩니다.

밝은 모드에 있는 앱의 스크린샷.

Win32 애플리케이션에 대해 어두운 모드 제목 표시줄 사용

Windows는 애플리케이션이 어두운 모드를 지원할 수 있는지를 알 수 없으므로 이전 버전과의 호환성을 이유로 지원하지 않는 것으로 가정합니다. Windows 앱 SDK와 같은 일부 Windows 개발 프레임워크는 기본적으로 어두운 모드를 지원하고 추가 코드 없이 특정 UI 요소를 변경합니다. Win32 앱은 종종 어두운 모드를 지원하지 않으므로 Windows는 기본적으로 Win32 앱에 밝은 제목 표시줄을 제공합니다.

그러나 표준 Windows 제목 표시줄을 사용하는 앱의 경우 시스템이 어두운 모드인 경우 어두운 버전의 제목 표시줄을 사용하도록 설정할 수 있습니다. 어두운 제목 표시줄을 활성화하려면 DWMWA_USE_IMMERSIVE_DARK_MODE 창 특성을 사용하여 최상위 창에서 DwmSetWindowAttribute라는 Desktop Windows Manager(DWM) 함수를 호출합니다. (DWM은 창에 대한 특성을 렌더링합니다.)

다음 예제에서는 이 코드에서 만든 것과 같이 표준 제목 표시줄이 포함된 창이 있다고 가정합니다.

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, 
     CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

먼저 다음과 같이 DWM API를 가져와야 합니다.

#include <dwmapi.h>

그런 다음, 함수 위에 DWMWA_USE_IMMERSIVE_DARK_MODE매크로InitInstance를 정의합니다.

#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
…

마지막으로 DWM API를 사용하여 어두운 색을 사용하도록 제목 표시줄을 설정할 수 있습니다. 여기에서 호출 value라는 BOOL을(를) 만들어 TRUE로 설정합니다. 이 BOOL은(는) Windows 특성 설정을 트리거하는 데 사용됩니다. 그런 다음, DwmSetWindowAttribute을(를) 사용하여 창 특성을 변경하여 어두운 모드 색을 사용합니다.

BOOL value = TRUE;
::DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));

이 호출이 수행하는 작업에 대한 자세한 설명은 다음과 같습니다.

DwmSetWindowAttribute의 구분 블록은 다음과 같이 표시됩니다.

HRESULT DwmSetWindowAttribute(
       HWND    hwnd,
       DWORD   dwAttribute,
  [in] LPCVOID pvAttribute,
       DWORD   cbAttribute
);

hWnd(변경하려는 창에 핸들)을(를) 첫 번째 매개 변수로 전달한 후 DWMWA_USE_IMMERSIVE_DARK_MODEdwAttribute 매개 변수로 전달해야 합니다. 이는 DWM API의 상수로, 어두운 모드 시스템 설정을 사용할 때 Windows 프레임을 어두운 모드 색으로 그릴 수 있습니다. 밝은 모드로 전환하는 경우 제목 표시줄을 밝은 모드 색으로 그리려면 DWMWA_USE_IMMERSIVE_DARK_MODE를 20에서 0으로 변경해야 합니다.

pvAttribute 매개 변수는 BOOL 형식의 값을 가리킵니다(이전에 BOOL 값을 만든 이유임). 창에 어두운 모드를 적용하려면 pvAttribute이(가) TRUE여야 합니다. pvAttribute이(가) FALSE인 경우 창에서 밝은 모드를 사용합니다.

마지막으로 cbAttribute은(는) pvAttribute에 설정되는 특성의 크기가 있어야 합니다. 이 작업을 쉽게 수행하려면 sizeof(value)을(를) 전달합니다.

어두운 창 제목 표시줄을 그리는 코드는 다음과 같습니다.

#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
#endif


BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   BOOL value = TRUE;
   ::DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value));

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

이 코드를 실행하면 앱 제목 표시줄이 어둡게 표시됩니다.

어두운 제목 표시줄이 있는 앱의 스크린샷.

참고 항목