user3161924
user3161924

Reputation: 2315

Certain controls aren't drawing in dark mode for native Win32 application?

I found this article on Win32 Dark Mode which references this github project. I used a forked version with more up to date build numbers (and added additional ones since then).

I created a generic handler for this dialog based application:

BOOL CALLBACK SetDarkThemeForChildren(HWND hwnd, LPARAM lparam)
{
  if (!::IsWindow(hwnd)) {
    return TRUE;
  }
  

  TCHAR classname[80];
  classname[0]=0;
  ::GetClassName(hwnd, classname, _countof(classname));
  if (_tcsicmp(classname, _T("SysListView32"))==0) {
    InitListView(hwnd);
  }

  SetWindowTheme(hwnd, L"Explorer", nullptr);

  return TRUE;
}

//---------------------------------------------------------------------------

BOOL CALLBACK DarkThemeChangedForChildren(HWND hwnd, LPARAM lparam)
{
  if (!::IsWindow(hwnd)) {
    return TRUE;
  }

  _AllowDarkModeForWindow(hwnd, g_darkModeEnabled);
  SendMessage(hwnd, WM_THEMECHANGED, 0, 0);
  
  return TRUE;
}


//---------------------------------------------------------------------------
// Purpose: Handle messages for darkmode
//
// Input:   hwnddlg  - [i] dialog handle
//          msg      - [i] dialog message
//          wparam   - [i] win message parameter
//          lparm    - [i] win message parameter
//          newbrush - [o] brush to use for message (only if true returned)
//
// Output:  TRUE handled - abort normal handlers as would be appropriate
//          FALSE not handled or use default handlers.
//
// Notes:   Only returns TRUE for messages that were WM_CTLCOLOR* type and we
//          handled it (and returned the HBRUSH) for dark mode.
//
bool HandleDarkMode(HWND hwnddlg, UINT msg, WPARAM wparam, LPARAM lparam, HBRUSH *newbrush)
{
  bool result=false;

  static HBRUSH hbrBkgnd = nullptr;
  constexpr COLORREF darkBkColor = 0x383838;
  constexpr COLORREF darkTextColor = 0xFFFFFF;

  switch (msg) {
    case UWM_INITCHILDREN:
    case WM_INITDIALOG:
    {
      if (g_darkModeSupported) {
        _AllowDarkModeForWindow(hwnddlg, true);
        RefreshTitleBarThemeColor(hwnddlg);

        EnumChildWindows(hwnddlg, SetDarkThemeForChildren, NULL);

        SendMessage(hwnddlg, WM_THEMECHANGED, 0, 0);
      }
    }
    break;


    case WM_DESTROY:
      // only for main dialog
      if (hbrBkgnd && hwnddlg==g_hwndDlgMain) {
        DeleteObject(hbrBkgnd);
        hbrBkgnd = nullptr;
      }
      break;

    case WM_CTLCOLOR:
    case WM_CTLCOLORBTN:
    case WM_CTLCOLOREDIT:
    case WM_CTLCOLORLISTBOX:
    case WM_CTLCOLORSCROLLBAR:
    case WM_CTLCOLORDLG:
    case WM_CTLCOLORSTATIC:
    {
      if (g_darkModeSupported && g_darkModeEnabled) {
        HDC hdc = reinterpret_cast<HDC>(wparam);
        SetTextColor(hdc, darkTextColor);
        SetBkColor(hdc, darkBkColor);
        if (!hbrBkgnd)
          hbrBkgnd = CreateSolidBrush(darkBkColor);
        *newbrush=hbrBkgnd;
        return true;
      }
    }
    break;

    case WM_SETTINGCHANGE:
    {
      if (g_darkModeSupported && IsColorSchemeChangeMessage(lparam))
        PostMessage(hwnddlg, WM_THEMECHANGED, 0, 0);
    }
    break;

    case WM_THEMECHANGED:
    {
        if (g_darkModeSupported) {
            _AllowDarkModeForWindow(hwnddlg, g_darkModeEnabled);
            RefreshTitleBarThemeColor(hwnddlg);

            EnumChildWindows(hwnddlg, DarkThemeChangedForChildren, NULL);

            UpdateWindow(hwnddlg);
        }
    }
    break;
  }

  *newbrush=NULL;
  return false;
}

Basically in the Window Procedure before the switch statement I do this:

  HBRUSH newbrush;
  bool darkmode=HandleDarkMode(hwnddlg, message, wparam, lparam, &newbrush);
  // handle WM_CTLCOLOR* since we don't in main switch
  if (darkmode) {
    return (INT_PTR) newbrush;
  }

It basically works for most things, but using the latest Win10 Pro x64 version I find certain things don't work:

  1. Group Box: Text is black (everything else if fine (line is white, background is black)

  2. Check Box/Radio Button: They still draw with black text and checkbox itself is still white (which is fine with me, the black text is the problem).

  3. ComboBox: The combo box is still light style, while the drop-down selection is correctly dark.

4. ListView: The column header background is still white/light. The text has changed but hard to see on the white/light background. The body of the list view is fine.

Found Issue for #4: Need to skip SysHeader32 so the theme is not changed.

Any ideas on what may be happening and what is needed to fix it?

Upvotes: 1

Views: 54

Answers (1)

Jan Ringoš
Jan Ringoš

Reputation: 141

The Dark theme for Win32 is mostly undocumented, but internally dark graphics in Aero.msstyles are implemented through several sub-styles. For the few controls that actually support it, that is.

You are already using SetWindowTheme so try:

SetWindowTheme (..., L"DarkMode_Explorer", NULL);  // in general
SetWindowTheme (..., L"ExplorerStatusBar", NULL);  // for msctls_statusbar32
SetWindowTheme (..., L"DarkMode_CFD", NULL);       // for COMBOBOX and EDIT
SetWindowTheme (..., L"DarkMode_ItemsView", NULL); // for SysHeader32 and SysListView32

But these graphics are hopelessly incomplete, and unless your use-case exactly matches Explorer's, you may find it distorted or outright better to fully owner-draw them yourself.

Also do test your app in dark High Contrast mode. The colors are already inverted.

Upvotes: 0

Related Questions