Search code examples
c#.netwpfwinapidwm

Detect system theme change in WPF


I need, for my WPF app, to detect when the DWM is turned on/off or when the system theme changes.
There is such an event in WinForms, but I can't see any in WPF.


Solution

  • I haven't heard of a WinForms event that fires when a WinForms window receives messages from the system, however it has its own WndProc() method that you can override. You're probably confusing window messages for form events. Ah, so it's the StyleChanged event that gets invoked in WinForms windows. The rest of my answer still stands though.

    WPF isn't closely tied to the Windows API either as it's a high-level technology that invests a lot of abstraction away from the internals. For one, it draws everything in a window by itself, and doesn't ask the system to do the drawing for it (EDIT: which is why WPF lacks such a StyleChanged event). That said, Windows sends messages to all windows when the DWM is toggled and when the theme changes, and you can still drill down into the low level from the WPF layer to access these messages and manipulate your WPF controls accordingly.

    Attach a window procedure to your WPF window's HWND (window handle) as part of your window's SourceInitialized event. In your window procedure, handle the WM_DWMCOMPOSITIONCHANGED and WM_THEMECHANGED window messages respectively.

    Here's a quick example (with boilerplate code adapted from this question of mine):

    private IntPtr hwnd;
    private HwndSource hsource;
    
    private const int WM_DWMCOMPOSITIONCHANGED= 0x31E;
    private const int WM_THEMECHANGED = 0x31A;
    
    private void Window_SourceInitialized(object sender, EventArgs e)
    {
        if ((hwnd = new WindowInteropHelper(this).Handle) == IntPtr.Zero)
        {
            throw new InvalidOperationException("Could not get window handle.");
        }
    
        hsource = HwndSource.FromHwnd(hwnd);
        hsource.AddHook(WndProc);
    }
    
    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        switch (msg)
        {
            case WM_DWMCOMPOSITIONCHANGED: 
            case WM_THEMECHANGED:         
    
                // Respond to DWM being enabled/disabled or system theme being changed
    
                return IntPtr.Zero;
    
            default:
                return IntPtr.Zero;
        }
    }