TreeView
和 ListView
上使用 SetWindowTheme(hwnd, L"Explorer", NULL);
,这显然改变了 TreeView
的样式(+
按钮变成了 V
),但窗口仍然是白色的。TreeView
和 ListView
上使用 SetWindowTheme(hwnd, L"Explorer", NULL);
,这显然改变了 TreeView
的样式(+
按钮变成了 V
),但窗口仍然是白色的。请查看https://github.com/ysc3839/win32-darkmode,这位开发者将一些好用的可重复使用代码整理得很棒(以MIT许可证发布)。
似乎在Windows 10中暗黑模式仍处于开发阶段,但我相信微软最终会对其进行适当的文档记录并公开给桌面应用。
在此之前,我们需要处理没有原生暗黑模式支持的控件,通过未记录的序数导入、自定义绘制和WM_CTLCOLOR*
消息来指定它们的绘制方式。
最基本的 Windows 新API是SetPreferredAppMode
(uxtheme@135
),需在创建任何窗口之前调用,并且在任何打算使用Windows 10本机暗黑模式支持的窗口上调用AllowDarkModeForWindow
(uxtheme@133
)。
以下是该项目中完整的未记录序数导入列表:
using fnRtlGetNtVersionNumbers = void (WINAPI *)(LPDWORD major, LPDWORD minor, LPDWORD build);
// 1809 17763
using fnShouldAppsUseDarkMode = bool (WINAPI *)(); // ordinal 132
using fnAllowDarkModeForWindow = bool (WINAPI *)(HWND hWnd, bool allow); // ordinal 133
using fnAllowDarkModeForApp = bool (WINAPI *)(bool allow); // ordinal 135, removed since 18334
using fnFlushMenuThemes = void (WINAPI *)(); // ordinal 136
using fnRefreshImmersiveColorPolicyState = void (WINAPI *)(); // ordinal 104
using fnIsDarkModeAllowedForWindow = bool (WINAPI *)(HWND hWnd); // ordinal 137
using fnGetIsImmersiveColorUsingHighContrast = bool (WINAPI *)(IMMERSIVE_HC_CACHE_MODE mode); // ordinal 106
using fnOpenNcThemeData = HTHEME(WINAPI *)(HWND hWnd, LPCWSTR pszClassList); // ordinal 49
// Insider 18290
using fnShouldSystemUseDarkMode = bool (WINAPI *)(); // ordinal 138
// Insider 18334
using fnSetPreferredAppMode = PreferredAppMode (WINAPI *)(PreferredAppMode appMode); // ordinal 135, since 18334
using fnIsDarkModeAllowedForApp = bool (WINAPI *)(); // ordinal 139
InitDarkMode
导入并以安全的方式初始化黑暗模式,仔细检查最小和最大支持的Windows 10版本:
void InitDarkMode()
{
fnRtlGetNtVersionNumbers RtlGetNtVersionNumbers = reinterpret_cast<fnRtlGetNtVersionNumbers>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlGetNtVersionNumbers"));
if (RtlGetNtVersionNumbers)
{
DWORD major, minor;
RtlGetNtVersionNumbers(&major, &minor, &g_buildNumber);
g_buildNumber &= ~0xF0000000;
if (major == 10 && minor == 0 && 17763 <= g_buildNumber && g_buildNumber <= 18363) // Windows 10 1809 10.0.17763 - 1909 10.0.18363
{
HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (hUxtheme)
{
_OpenNcThemeData = reinterpret_cast<fnOpenNcThemeData>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(49)));
_RefreshImmersiveColorPolicyState = reinterpret_cast<fnRefreshImmersiveColorPolicyState>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(104)));
_GetIsImmersiveColorUsingHighContrast = reinterpret_cast<fnGetIsImmersiveColorUsingHighContrast>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(106)));
_ShouldAppsUseDarkMode = reinterpret_cast<fnShouldAppsUseDarkMode>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(132)));
_AllowDarkModeForWindow = reinterpret_cast<fnAllowDarkModeForWindow>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133)));
auto ord135 = GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135));
if (g_buildNumber < 18334)
_AllowDarkModeForApp = reinterpret_cast<fnAllowDarkModeForApp>(ord135);
else
_SetPreferredAppMode = reinterpret_cast<fnSetPreferredAppMode>(ord135);
//_FlushMenuThemes = reinterpret_cast<fnFlushMenuThemes>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(136)));
_IsDarkModeAllowedForWindow = reinterpret_cast<fnIsDarkModeAllowedForWindow>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(137)));
if (_OpenNcThemeData &&
_RefreshImmersiveColorPolicyState &&
_ShouldAppsUseDarkMode &&
_AllowDarkModeForWindow &&
(_AllowDarkModeForApp || _SetPreferredAppMode) &&
//_FlushMenuThemes &&
_IsDarkModeAllowedForWindow)
{
g_darkModeSupported = true;
AllowDarkModeForApp(true);
_RefreshImmersiveColorPolicyState();
g_darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast();
FixDarkScrollBar();
}
}
}
}
}
在其他地方,他利用WM_CTLCOLOR*
消息和自定义绘制通知来在Windows未涂黑的地方绘制黑色。
请注意FixDarkScrollBar
。这是一个对OpenNcThemeData
的IAT挂钩,以覆盖comctl32
中listview类的滚动条主题选择。那是我最烦恼的部分,我正在考虑取消它。我相信他也是这样想的。
我已将此代码适应于我的应用程序,并且运行良好。但是,我不舒服使用这些仅支持序数的未记录API(即使尽可能安全),并且完全希望微软最终宣布并记录Win32应用程序的暗模式,使这项工作变得多余。
经过一番搜索,我找到了这两种方法。它们都没有记录,并且可能会在没有通知的情况下发生变化。
1
SetWindowTheme(hwnd, L"DarkMode_Explorer", NULL);
2
using TYPE_AllowDarkModeForWindow = bool (WINAPI *)(HWND a_HWND, bool a_Allow);
static const TYPE_AllowDarkModeForWindow AllowDarkModeForWindow = (TYPE_AllowDarkModeForWindow)GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133));
AllowDarkModeForWindow(a_HWND, true);
SetWindowTheme(hwnd, L"Explorer", NULL);
警告: 在其他版本的Windows上(包括新/旧Win10版本),序数133可能有完全不同的API。
这两种方法都应用了一些效果,但并非全部。
例如,TreeView
会得到深色滚动条和深色背景以突出显示选定项,但其余部分的背景仍为默认设置。
不幸的是,目前看来并不像“调用一个函数就可以完成”那样简单。即使应用了正确的主题,某些背景颜色也需要手动处理。
IExplorerBrowser
外观的观察相符,对此我应该提交一个错误报告,因为它是有文档记录的API。 - zett42AllowDarkModeForWindow
的方法似乎更好。将 DarkMode_Explorer
主题设置为奇怪的是,并没有启用“资源管理器”主题的可视样式(例如列表视图中项目选择的柔和颜色),只是将滚动条的颜色设置为深色。而 AllowDarkModeForWindow
保持了“资源管理器”模式的“柔和”样式。 - Martin Prikryl
IExplorerBrowser
嵌入 Explorer,当切换到暗色主题后,似乎部分采用了暗色主题。资源管理器列表视图看起来很暗,而资源管理器树形视图则仍然是浅色,并带有暗色选择条。应用程序的剩余用户界面也是浅色的。这可能表明存在窗口级别的设置。 - zett42