Win10 dark theme - how to use in WINAPI?

后端 未结 2 1788
慢半拍i
慢半拍i 2020-12-07 23:53

Starting with October 2018 Update (version 1809) Win10 has support for Dark theme in Windows Explorer.

It can be configured here:

  • UI:
相关标签:
2条回答
  • 2020-12-08 00:26

    See https://github.com/ysc3839/win32-darkmode

    This guy has it all laid out in some nice reusable code (MIT license).

    It seems that Dark Mode is still an area of development in Windows 10, but I believe that Microsoft will eventually properly document and expose it to desktop apps.

    Until then, we are stuck with undocumented ordinal-only imports, then custom draw and WM_CTLCOLOR* messages to dictate how controls that don't yet have native Dark Mode support are painted.

    The most fundamental of the new Windows APIs are SetPreferredAppMode (uxtheme@135), to be called prior to any window creation, and AllowDarkModeForWindow (uxtheme@133), to be called on any Window that intends to use native Windows 10 dark mode support.

    Here is the full list ordinal-only imports from that project:

    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 imports and initializes dark mode, in a safe manner, carefully checking for min and max supported Windows 10 builds:

    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();
                    }
                }
            }
        }
    }
    

    Elsewhere, he takes advantage of WM_CTLCOLOR* messages and custom draw notifications to paint dark where Windows doesn't (yet) do it for us.

    Note the FixDarkScrollBar. That is an IAT hook on OpenNcThemeData to over-ride the scrollbar theme selection by the listview class in comctl32. That is the part that bothers me most and I'm looking to axe it. I'm sure he is too.

    I've adapted this code to my own application and it works well. I am, however, uncomfortable using these undocumented ordinal-only APIs (even as safely as possible), and fully expect Microsoft to eventually announce and document dark mode for Win32 apps and make this work redundant.

    0 讨论(0)
  • 2020-12-08 00:35

    After some digging, I was able to find these two approaches. Both are undocumented and may change without notice.

    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);
    

    WARNING: Ordinal 133 may have completely different API behind it on other versions of Windows, including newer/older Win10 builds.

    Both approaches apply some effects, but not everything.
    For example, TreeView gets dark scrollbars and dark background for selected item, but the rest of background stays default.

    Unfortunately, so far it's not like "call a function and that's it". It seems that even with correct theme applied, some background colors need to be handled manually.

    0 讨论(0)
提交回复
热议问题