检测WPF中的系统主题变化

发布于 2024-11-15 17:24:20 字数 84 浏览 9 评论 0原文

对于我的 WPF 应用程序,我需要检测 DWM 何时打开/关闭或系统主题何时更改。
WinForms中有这样的事件,但我在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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

墨离汐 2024-11-22 17:24:20

我还没有听说过当 WinForms 窗口从系统接收消息时触发的 WinForms 事件,但是它有自己的 WndProc() 方法,您可以使用它覆盖。您可能混淆了窗体事件的窗口消息。 啊,所以在 WinForms 窗口中调用的是 StyleChanged 事件。我的其余答案仍然有效。

WPF 与 Windows API 的联系并不紧密,因为它是一项高级技术,投入了大量远离内部的抽象。其一,它自己在窗口中绘制所有内容,并且不要求系统为其进行绘制(编辑:,这就是为什么 WPF 缺乏这样的<代码>StyleChanged事件)。也就是说,当 DWM 切换和主题更改时,Windows 会向所有窗口发送消息,并且您仍然可以从 WPF 层深入到低级别来访问这些消息并相应地操作 WPF 控件。

将窗口过程附加到 WPF 窗口的 HWND(窗口句柄),作为窗口的 SourceInitialized 事件的一部分。在您的窗口过程中,处理 WM_DWMCOMPOSITIONCHANGEDWM_THEMECHANGED分别是窗口消息。

这是一个简单的示例(样板代码改编自 我的这个问题):

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

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;
    }
}
·深蓝 2024-11-22 17:24:20

不幸的是,接受的解决方案不适用于 Aero 颜色主题更改,并且 WM 消息十六进制数字混合在一起 - 但我同意如果您想在 WPF 中捕获 WM 消息,它非常有用。一段时间以来,我一直在尝试寻找这个问题的解决方案,并且我认为我已经解决了所有可能的情况(对于航空和经典主题)。

Aero 颜色变化会触发 WM_DWMCOLORIZATIONCOLORCHANGED 消息。

要检测颜色主题何时发生变化,您必须使用多种方法。 Form.StyleChanged 事件将检测所有主题更改,Aero 颜色更改除外。这是 StyleChanged 的​​替代解决方案。 (好吧,我知道这是 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);
    }

对于 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
        }
    }

如您所见如上所述,它远非直观。 Aero 颜色更改会触发“常规”首选项更改事件,尽管有许多更适合于此的事件,例如“VisualStyle”等...

如果您想彻底,则应该将保存的 DWM 颜色与当前的 DWM 进行比较color,以确保触发此事件的确实是 Aero 颜色主题(使用 DwmGetColorizationParameters API 调用),而不是其他内容。请参阅以下有关如何检索 Aero 颜色的答案:
获取 Windows 8 自动颜色主题的活动颜色

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. 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.

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. Here is an alternative solution to StyleChanged. (Ok, I know this is WinForms, but you've got the idea. The WPF equivalent is in the accepted answer anyway.)

    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!):

    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...

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. See these answers on how Aero colors can be retrieved:
Get the active color of Windows 8 automatic color theme

一个人的旅程 2024-11-22 17:24:20

SystemEvents.UserPreferenceChanged 事件也能达到这个目的。
UserPreferenceChanged(日语)

The event SystemEvents.UserPreferenceChanged also does the trick.
UserPreferenceChanged(in Japaense)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文