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: 我为TreeViewListView 尝试了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中使用?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 VSCode 用户设置中为深色和浅色主题指定颜色

如何修复 Eclipse 深色主题中选定文本项的黑色前景色

如何在 Win x64 上使用 WinAPI 正确安装虚拟打印机?

如何编辑 Visual Studio Code 的默认深色主题?

如何为 Material-ui 的组件设置主要的浅色/深色?我正在使用像这里这样的自定义主题

win10怎样修改窗口背景颜色?