Redraw titlebar after setting dark mode in Windows
Asked Answered
A

1

7

I am using the following library call to change the window to dark mode:

BOOL dark = TRUE;
DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark, sizeof(dark));

This works, with a small caveat. The title bar doesn't update until some additional event happens, like maximizing, losing focus, resizing, etc. (but not moving the window).

I have tried UpdateWindow and a huge number of combinations of flags on RedrawWindow with no luck. How can I force the title bar to redraw?

Edit I was able to force the reset by programmatically resizing the window, then resetting it to the previous size. But that seems like a terrible method. There must be a proper solution.

Alienage answered 3/12, 2022 at 13:27 Comment(5)
does this helps? invalidating-non-client-areasGiantess
Did you try the answer that's not accepted, i.e. calling SetWindowPos?Harvey
Yes, that didn;t work eitherAlienage
The only way we've found to do this is by using an undocumented API. Unfortunately Microsoft have not properly finished or documented their Dark Mode API.Discoid
@JonathanPotter can you please point out which undocumented API did you use to achieve this? I am facing a similar issue and haven't found a solution yet.Fetation
V
0

This is how I'm triggering the titlebar redraw after calling DwmSetWindowAttribute on Windows 10 (not yet tested on Windows 11). As mentioned by OP none of the usual methods for triggering WM_NCPAINT seem to work. This approach works by changing and then restoring the window width. It does so in the least visible way possible, and works for both normal and maximized windows. It makes the window width smaller instead of larger to avoid potential overflow onto a secondary monitor.

if (IsWindowVisible(hwnd) && !IsIconic(hwnd)) {
    // Get the current window rect.
    RECT rect = {};
    ::GetWindowRect(hwnd, &rect);

    if (IsZoomed(hwnd)) {
        // Window is currently maximized.
        WINDOWPLACEMENT placement = {};
        GetWindowPlacement(hwnd, &placement);

        // Remember the old restore rect.
        const RECT oldrect = placement.rcNormalPosition;

        // Change the restore rect to almost the same as the current
        // maximized rect.
        placement.rcNormalPosition = rect;
        placement.rcNormalPosition.right -= 1;
        SetWindowPlacement(hwnd, &placement);

        // Restore and then re-maximize the window. Don't update in-between.
        LockWindowUpdate(hwnd);
        ShowWindow(hwnd, SW_SHOWNORMAL);
        ShowWindow(hwnd, SW_SHOWMAXIMIZED);
        LockWindowUpdate(nullptr);

        // Put back the old restore rect.
        placement.rcNormalPosition = oldrect;
        SetWindowPlacement(hwnd, &placement);
    } else {
        // Window is currently normal. Change and then restore the window width.
        // Use Defer functions to make the change less noticeable. Don't update
        // in-between.
        HDWP defer = BeginDeferWindowPos(2);
        DeferWindowPos(defer, hwnd, NULL, 0, 0,
                       rect.right - rect.left - 1, rect.bottom - rect.top,
                       SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
        DeferWindowPos(defer, hwnd, NULL, 0, 0, rect.right - rect.left,
                       rect.bottom - rect.top,
                       SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
        LockWindowUpdate(hwnd);
        EndDeferWindowPos(defer);
        LockWindowUpdate(nullptr);
    }
}
Vestal answered 3/4 at 18:56 Comment(1)
Note that Microsoft doesn't like this usage of LockWindowUpdate and suggests sending the WM_SETREDRAW message instead.Vestal

© 2022 - 2024 — McMap. All rights reserved.