Win10暗黑主题 - 如何在WINAPI中使用?

34
从2018年10月更新(版本1809)开始,Win10在Windows资源管理器中支持深色主题。 可以在以下位置进行配置: UI:桌面|上下文菜单|个性化|颜色|选择默认应用模式=暗色 注册表: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme = DWORD:0 虽然此设置已经存在一段时间,但它只影响UWP应用程序。 但是,随着这个Windows 10版本的发布,它也会影响桌面应用程序Windows资源管理器。这意味着Windows现在内部支持它。不过,目前除了Windows资源管理器之外的其他桌面应用程序并没有受到影响。 我想在我的应用程序中使用它。它在幕后是如何实现的?是否有某种方法(清单、WINAPI等)可以订阅新的深色主题? 更新1: 我注意到Windows资源管理器控制面板是部分浅色和部分深色,因此它应该是窗口级别的设置,而不是进程级别的设置。 另一个例子:所有桌面应用程序中的打开文件对话框都变暗,而应用程序本身仍处于旧的浅色主题中。 更新2:我尝试了在 TreeViewListView 上使用 SetWindowTheme(hwnd, L"Explorer", NULL);,这显然改变了 TreeView 的样式(+ 按钮变成了 V),但窗口仍然是白色的。

2
仅仅是一个观察:我的一个应用程序使用 IExplorerBrowser 嵌入 Explorer,当切换到暗色主题后,似乎部分采用了暗色主题。资源管理器列表视图看起来很暗,而资源管理器树形视图则仍然是浅色,并带有暗色选择条。应用程序的剩余用户界面也是浅色的。这可能表明存在窗口级别的设置。 - zett42
1
至少一些常见控件的视觉样式有Explorer特定的变体,例如“Explorer::ListView”而不仅仅是“ListView”。 - Jonathan Potter
资源管理器主题子类自XP以来就存在,对我来说似乎是一个误导。 - Anders
2个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
18

请查看https://github.com/ysc3839/win32-darkmode,这位开发者将一些好用的可重复使用代码整理得很棒(以MIT许可证发布)。

似乎在Windows 10中暗黑模式仍处于开发阶段,但我相信微软最终会对其进行适当的文档记录并公开给桌面应用。

在此之前,我们需要处理没有原生暗黑模式支持的控件,通过未记录的序数导入、自定义绘制和WM_CTLCOLOR*消息来指定它们的绘制方式。

最基本的 Windows 新API是SetPreferredAppModeuxtheme@135),需在创建任何窗口之前调用,并且在任何打算使用Windows 10本机暗黑模式支持的窗口上调用AllowDarkModeForWindowuxtheme@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应用程序的暗模式,使这项工作变得多余。


2
如果您在列表视图中使用标准的“Explorer”主题(如@Codeguard的答案所示),则不需要使用“FixDarkScrollBar” hack。另一方面,列表视图仍然使用蓝色选择框(而不是深色模式灰色)。但就目前而言,我认为这比hack更好的解决方案。 - Martin Prikryl
1
必须使用该分支才能使其正常工作:https://github.com/komiyamma/win32-darkmode - Slion

14

经过一番搜索,我找到了这两种方法。它们都没有记录,并且可能会在没有通知的情况下发生变化。

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。 - zett42
3
AllowDarkModeForWindow 的方法似乎更好。将 DarkMode_Explorer 主题设置为奇怪的是,并没有启用“资源管理器”主题的可视样式(例如列表视图中项目选择的柔和颜色),只是将滚动条的颜色设置为深色。而 AllowDarkModeForWindow 保持了“资源管理器”模式的“柔和”样式。 - Martin Prikryl
7
这就是我们从微软可以期待的全部,为Win32开发真是个悲伤的世界。即使过了5年,仍然没有官方方法来获取窗口强调色。 - Lothar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,