原生Win32应用暗黑模式下部分控件绘制异常求助
我基于Win32暗黑模式相关实现,使用了包含更新构建号的分支项目,为对话框应用编写了通用暗黑模式处理程序,代码如下:
通用暗黑模式处理代码
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; }
窗口过程调用代码
在窗口过程的switch语句前,添加以下调用逻辑:
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; }
当前存在的问题
在Win10 Pro x64最新版本中,上述实现存在以下问题:
- 分组框(Group Box):文本为黑色(线条为白色、背景为黑色,其余显示正常)
- 复选框/单选按钮:文本仍为黑色(复选框本身为白色可接受,黑色文本是核心问题)
- 组合框(ComboBox):控件主体为浅色样式,下拉选择区域显示正确的深色
- 列表视图(ListView):列标题背景为白色/浅色,文本已变色但在浅色背景上难以辨认(已找到解决方法:跳过
SysHeader32控件,不修改其主题)
问题原因与修复方案
1. 分组框文本颜色问题
原因:分组框属于STATIC类控件(样式为BS_GROUPBOX),默认处理WM_CTLCOLORSTATIC时,设置的文本颜色会被控件的绘制逻辑忽略,且分组框需要透明背景才能正确继承对话框的深色背景。
修复:修改WM_CTLCOLORSTATIC分支的处理逻辑,针对性处理分组框:
case WM_CTLCOLORSTATIC: { if (g_darkModeSupported && g_darkModeEnabled) { HDC hdc = reinterpret_cast<HDC>(wparam); HWND hwndCtl = reinterpret_cast<HWND>(lparam); UINT controlStyle = GetWindowLongPtr(hwndCtl, GWL_STYLE) & 0xFF; // 单独处理分组框 if (controlStyle == BS_GROUPBOX) { SetTextColor(hdc, darkTextColor); SetBkMode(hdc, TRANSPARENT); // 设置背景透明,继承对话框深色 *newbrush = (HBRUSH)GetStockObject(NULL_BRUSH); return true; } // 其他静态控件保持原有处理 SetTextColor(hdc, darkTextColor); SetBkColor(hdc, darkBkColor); if (!hbrBkgnd) hbrBkgnd = CreateSolidBrush(darkBkColor); *newbrush = hbrBkgnd; return true; } } break;
2. 复选框/单选按钮文本颜色问题
原因:复选框、单选按钮属于BUTTON类控件,WM_CTLCOLORBTN中设置的文本颜色会被控件的主题绘制逻辑覆盖,且默认背景不透明导致文本颜色显示异常。
修复:修改WM_CTLCOLORBTN分支,针对复选框/单选按钮做特殊处理:
case WM_CTLCOLORBTN: { if (g_darkModeSupported && g_darkModeEnabled) { HDC hdc = reinterpret_cast<HDC>(wparam); HWND hwndCtl = reinterpret_cast<HWND>(lparam); UINT btnStyle = GetWindowLongPtr(hwndCtl, GWL_STYLE) & 0xFF; // 处理复选框和单选按钮 if (btnStyle == BS_CHECKBOX || btnStyle == BS_RADIOBUTTON) { SetTextColor(hdc, darkTextColor); SetBkMode(hdc, TRANSPARENT); *newbrush = (HBRUSH)GetStockObject(NULL_BRUSH); // 透明背景 return true; } // 普通按钮保持原有处理 SetTextColor(hdc, darkTextColor); SetBkColor(hdc, darkBkColor); if (!hbrBkgnd) hbrBkgnd = CreateSolidBrush(darkBkColor); *newbrush = hbrBkgnd; return true; } } break;
同时,确保在SetDarkThemeForChildren中不对按钮控件强制设置Explorer主题(或设置后配合上述颜色调整),避免主题优先级覆盖自定义颜色。
3. 组合框主体浅色问题
原因:组合框由编辑控件和下拉列表两部分组成,默认SetWindowTheme仅作用于下拉列表,编辑控件未被正确启用暗黑模式。
修复:修改SetDarkThemeForChildren函数,单独处理组合框的编辑子控件:
BOOL CALLBACK SetDarkThemeForChildren(HWND hwnd, LPARAM lparam) { if (!::IsWindow(hwnd)) { return TRUE; } TCHAR classname[80]; classname[0]=0; ::GetClassName(hwnd, classname, _countof(classname)); // 跳过列表视图的列标题控件(解决问题4) if (_tcsicmp(classname, _T("SysHeader32"))==0) { return TRUE; } if (_tcsicmp(classname, _T("SysListView32"))==0) { InitListView(hwnd); SetWindowTheme(hwnd, L"Explorer", nullptr); } // 单独处理组合框 else if (_tcsicmp(classname, _T("COMBOBOX"))==0) { // 获取组合框内嵌的编辑控件 HWND hwndEdit = GetWindow(hwnd, GW_CHILD); if (hwndEdit) { _AllowDarkModeForWindow(hwndEdit, g_darkModeEnabled); SetWindowTheme(hwndEdit, L"Explorer", nullptr); } SetWindowTheme(hwnd, L"Explorer", nullptr); } else { SetWindowTheme(hwnd, L"Explorer", nullptr); } return TRUE; }
同时确保WM_CTLCOLOREDIT分支正常处理文本和背景颜色,因为组合框的编辑控件会发送该消息。
4. 列表视图列标题问题(补充完善)
原因:列表视图的列标题是SysHeader32控件,设置Explorer主题会强制使用浅色背景,与暗黑模式冲突。
修复:在枚举子控件时直接跳过SysHeader32,不设置其主题,代码已包含在上述SetDarkThemeForChildren的修改中。
内容的提问来源于stack exchange,提问作者user3161924

