简体   繁体   English

检测WPF中的系统主题更改

[英]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. 对于我的WPF应用程序,我需要检测DWM何时打开/关闭或系统主题何时更改。
There is such an event in WinForms, but I can't see any in WPF. 在WinForms中有这样的事件,但我在WPF中看不到任何事件。

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. 我没有听说过WinForms窗口从系统接收消息时触发的WinForms事件 ,但是它有自己的WndProc()方法可以覆盖。 You're probably confusing window messages for form events. 您可能会混淆表单事件的窗口消息。 Ah, so it's the StyleChanged event that gets invoked in WinForms windows. 啊,所以这是在WinForms窗口中调用的StyleChanged事件。 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. WPF与Windows API没有密切联系,因为它是一种高级技术,可以从内部投入大量抽象。 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). 首先,它自己在窗口中绘制所有内容 ,并且不要求系统为它绘制图形( 编辑:这就是为什么WPF缺少这样的StyleChanged事件)。 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. 也就是说,当切换DWM时以及主题发生变化时,Windows会向所有窗口发送消息,您仍然可以从WPF层深入到低级别来访问这些消息并相应地操作WPF控件。

Attach a window procedure to your WPF window's HWND (window handle) as part of your window's SourceInitialized event. 将窗口过程附加到WPF窗口的HWND(窗口句柄),作为窗口SourceInitialized事件的一部分。 In your window procedure, handle the WM_DWMCOMPOSITIONCHANGED and WM_THEMECHANGED window messages respectively. 在窗口过程中,分别处理WM_DWMCOMPOSITIONCHANGEDWM_THEMECHANGED窗口消息。

Here's a quick example (with boilerplate code adapted from this question of mine ): 这是一个简单的例子(从我的这个问题改编的样板代码):

private IntPtr hwnd;
private HwndSource hsource;

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: // Define this as 0x31A
        case WM_THEMECHANGED:          // Define this as 0x31E

            // Respond to DWM being enabled/disabled or system theme being changed

            return IntPtr.Zero;

        default:
            return IntPtr.Zero;
    }
}

The event SystemEvents.UserPreferenceChanged also does the trick. 事件SystemEvents.UserPreferenceChanged也可以解决这个问题。 UserPreferenceChanged(in Japaense) UserPreferenceChanged(在Japaense中)

Unfortunately the accepted solution does not work with Aero color theme changes, and the WM message hex numbers are mixed up - but I agree that it is very useful if you want to catch WM messages in WPF. 遗憾的是,已接受的解决方案不适用于Aero颜色主题更改,并且WM消息十六进制数字混淆了 - 但我同意如果您想在WPF中捕获WM消息,它非常有用。 I've been trying to find a solution to this problem for a while, and I think I've got it solved for all possible cases (for aero and classic themes). 我一直试图找到这个问题的解决方案一段时间,我想我已经解决了所有可能的情况(对于航空和经典主题)。

The Aero color change triggers the WM_DWMCOLORIZATIONCOLORCHANGED message. Aero颜色更改会触发WM_DWMCOLORIZATIONCOLORCHANGED消息。

To detect when the color theme changes you have to use multiple methods. 要检测颜色主题何时更改,您必须使用多种方法。 The Form.StyleChanged event is going to detect all theme change, except for Aero color changes . 除了Aero颜色更改外, Form.StyleChanged事件将检测所有主题更改 Here is an alternative solution to StyleChanged. 这是StyleChanged的替代解决方案。 (Ok, I know this is WinForms, but you've got the idea. The WPF equivalent is in the accepted answer anyway.) (好吧,我知道这是WinForms,但是你已经有了这个想法。无论如何,WPF等价物都在接受的答案中。)

    private const int WM_DWMCOLORIZATIONCOLORCHANGED = 0x320;
    private const int WM_DWMCOMPOSITIONCHANGED = 0x31E;
    private const int WM_THEMECHANGED = 0x031A;

    protected override void WndProc(ref Message m)
    {
        switch(m.Msg)
        {
            case WM_DWMCOLORIZATIONCOLORCHANGED:
            case WM_DWMCOMPOSITIONCHANGED:
            case WM_THEMECHANGED:
                // you code here
                break;
            default:
                break;
        }
        base.WndProc(ref m);
    }

For Aero color themes, the SystemEvents.UserPreferenceChanged event works too (thanks sees!): 对于Aero颜色主题,SystemEvents.UserPreferenceChanged事件也可以工作(谢谢看!):

    Microsoft.Win32.SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;

    private void SystemEvents_UserPreferenceChanged(object sender, Microsoft.Win32.UserPreferenceChangedEventArgs e)
    {
        if (e.Category == Microsoft.Win32.UserPreferenceCategory.General)
        {
            // your code here, compare saved theme color with current one
        }
    }

As you can see above, it is far from intuitive. 如您所见,它远非直观。 Aero color change triggers a 'General' preference change event, even though there are many more suitable ones for this, like 'VisualStyle', etc... Aero颜色更改会触发“常规”首选项更改事件,即使有更多合适的更改事件,例如“VisualStyle”等...

If you want to be thorough, you should compare the saved DWM color to the current DWM color, to make sure it was indeed an Aero color theme that triggered this event (using the DwmGetColorizationParameters API call), and not something else. 如果你想要彻底,你应该将保存的DWM颜色与当前的DWM颜色进行比较,以确保它确实是一个触发此事件的Aero颜色主题(使用DwmGetColorizationParameters API调用),而不是其他东西。 See these answers on how Aero colors can be retrieved: Get the active color of Windows 8 automatic color theme 请参阅以下有关如何检索Aero颜色的答案: 获取Windows 8自动颜色主题的活动颜色

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM