Win10深色主题——如何在WINAPI中使用?
Posted
技术标签:
【中文标题】Win10深色主题——如何在WINAPI中使用?【英文标题】:Win10 dark theme - how to use in WINAPI? 【发布时间】:2018-11-27 13:53:40 【问题描述】:从October 2018 Update (version 1809)
开始,Win10 在 Windows 资源管理器中支持深色主题。
可以在这里配置:
用户界面:Desktop | Context Menu | Personalize | Colors | Choose your default app mode = Dark
注册表:HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme
= DWORD:0
虽然此设置已存在一段时间,但它仅影响 UWP 应用程序。但是,在此 Windows 10 版本中,它也会影响作为桌面应用程序的 Windows 资源管理器。这意味着 Windows 现在对其有内部支持。不过,Windows Explorer 以外的桌面应用程序目前不受影响。
我想在我的应用程序中使用它。它是如何在幕后实现的?是否有某种方式(清单、WINAPI 等)订阅新的深色主题?
更新 1: 我注意到 Windows 资源管理器控制面板部分亮部分暗,因此它应该是每个窗口的设置,而不是每个进程的设置。
另一个示例:所有桌面应用程序中的“打开文件”对话框变暗,而应用程序本身仍保持旧的浅色主题。
更新 2:
我为TreeView
和ListView
尝试了SetWindowTheme(hwnd, L"Explorer", NULL);
。这明显改变了TreeView
样式(+
展开按钮变为V
),但窗口仍然是白色的。
【问题讨论】:
只是一个观察:我的一个应用程序使用IExplorerBrowser
嵌入资源管理器,在切换到深色主题后出现部分深色主题。资源管理器列表视图看起来很暗,资源管理器树视图仍然很亮,带有深色选择栏。应用程序的其余 UI 也显得很轻。这可能表示每个窗口的设置。
至少有一些常见的控件视觉样式有特定于资源管理器的变体,例如“Explorer::ListView” 而不仅仅是“ListView”。
Explorer 主题子类自 XP 以来就已经存在,对我来说似乎是一条红鲱鱼。
【参考方案1】:
见https://github.com/ysc3839/win32-darkmode
这个家伙用一些很好的可重用代码(MIT 许可证)将所有内容都列出来了。
暗模式似乎仍然是 Windows 10 中的一个开发领域,但我相信微软最终会正确记录并将其公开给桌面应用程序。 p>
在此之前,我们一直坚持使用未记录的仅序号导入,然后是自定义绘制和 WM_CTLCOLOR*
消息,以指示如何绘制尚不支持本机暗模式的控件。
最基本的新 Windows API 是 SetPreferredAppMode
(uxtheme@135
),在创建任何窗口之前调用,以及 AllowDarkModeForWindow
(@ 987654327@),将在任何打算使用本机 Windows 10 暗模式支持的 Window 上调用。
这是从该项目导入的仅序号的完整列表:
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
中列表视图类的滚动条主题选择。这是最困扰我的部分,我正在寻求解决它。我相信他也是。
我已将此代码改编为我自己的应用程序,它运行良好。但是,我对使用这些未记录的仅序数 API(即使尽可能安全)感到不舒服,并且完全希望 Microsoft 最终宣布并记录 Win32 应用程序的暗模式,并使这项工作变得多余。
【讨论】:
如果您对列表视图使用标准的“Explorer”主题(如@Codeguard 的回答所示),则不需要FixDarkScrollBar
hack。另一方面,列表视图仍然使用蓝色选择框(而不是暗模式灰色)。但就目前而言,我认为这是一个比 hack 更好的解决方案。
必须使用那个叉子才能工作:github.com/komiyamma/win32-darkmode【参考方案2】:
经过一番挖掘,我找到了这两种方法。两者均未记录,如有更改,恕不另行通知。
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);
警告:Ordinal 133 在其他版本的 Windows 上可能有完全不同的 API,包括较新/较旧的 Win10 版本。
这两种方法都有一些效果,但不是全部。
例如,TreeView
为所选项目获取深色滚动条和深色背景,但其余背景保持默认。
不幸的是,到目前为止,它还不像“调用一个函数就可以了”。似乎即使应用了正确的主题,也需要手动处理一些背景颜色。
【讨论】:
感谢您分享您的发现,但需要注意的是,这两种方法均未记录在案,因此如有更改,恕不另行通知。您对树视图的观察与我自己对“暗模式”下的IExplorerBrowser
外观的观察一致,我应该为此提交错误报告,因为它是一个记录在案的 API。
AllowDarkModeForWindow
的方法似乎更好。奇怪的是,设置DarkMode_Explorer
主题不会启用Explorer
主题的视觉样式(例如列表视图中项目选择的柔和颜色),只会将滚动条的颜色设置为深色。而AllowDarkModeForWindow
保持Explorer
模式的“柔和”风格。
这就是我们对微软的所有期望,当你为 win32 开发时,这是一个悲伤的世界。即使 5 年后,也没有正式的方法来获得窗口强调色。以上是关于Win10深色主题——如何在WINAPI中使用?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Win x64 上使用 WinAPI 正确安装虚拟打印机?
如何编辑 Visual Studio Code 的默认深色主题?