获取Windows 8自动配色主题的活动颜色

22
在Windows 8中,我已将颜色方案设置为自动,并配置壁纸每隔x分钟更换一次。颜色方案会根据当前壁纸的活动而改变。
我正在开发一个WPF应用程序,希望当Windows更改颜色方案以匹配当前壁纸时,我的渐变也能跟着改变。
有没有一种方法可以在C#中获取当前/实际颜色方案并被通知更改呢?

这涉及到相当多的Interop,并且只能使用某些未记录的API才能完成。事实上,我已经在我的Windows Vista/7项目中完成了这个,并在Windows 8上成功测试过。我可以尝试根据此编写答案,但需要一段时间。另请参见:Vista/7:如何获取玻璃颜色? - BoltClock
非常感谢提供的信息。我使用了注册表技巧,效果非常好。 - user1868880
这里有一个更好的答案,并且得到了支持(指不依赖于未记录的API):https://dev59.com/Iqzka4cB1Zd3GeqP3yOu - Xam
2个回答

17

是的,这是可能的。但是请注意:这需要涉及相当多的Win32互操作(这意味着从托管代码向本机DLL进行P/Invoke),并且只能够使用某些未记录的API完成。尽管如此,唯一涉及到的未记录的功能是获取窗口颜色方案(或DWM所称的窗口着色颜色),这在这个问题中已经涵盖:

Vista/7:如何获取玻璃颜色?

在我的项目中,我利用一个调用DwmGetColorizationParameters()

internal static class NativeMethods
{
    [DllImport("dwmapi.dll", EntryPoint="#127")]
    internal static extern void DwmGetColorizationParameters(ref DWMCOLORIZATIONPARAMS params);
}

public struct DWMCOLORIZATIONPARAMS
{
    public uint ColorizationColor, 
        ColorizationAfterglow, 
        ColorizationColorBalance, 
        ColorizationAfterglowBalance, 
        ColorizationBlurBalance, 
        ColorizationGlassReflectionIntensity, 
        ColorizationOpaqueBlend;
}

我已经测试过它,使用Windows 8和它的自动窗口着色功能效果很棒。如上面的链接所建议的那样,你可以在注册表中查找颜色值作为P/Invoke的替代方法,但我没有测试过那种方法,并且正如所述,这些方法是未记录的,不保证稳定。

一旦你获取了用于绘制梯度画刷的颜色,当窗口颜色方案更改(无论是手动还是自动由Windows)时,这些画刷就不会更新。幸运的是,Windows在每次发生这种情况时都会广播WM_DWMCOLORIZATIONCOLORCHANGED窗口消息,因此你只需要监听该消息并在发送时更新你的颜色即可。你可以通过挂钩窗口过程 (WndProc()) 来实现这一点。

WM_DWMCOLORIZATIONCOLORCHANGED的值为0x320;你需要在某个地方定义它作为一个常量,以便你在代码中使用。

此外,与WinForms不同,WPF窗口没有虚拟的WndProc()方法可以覆盖,因此你必须创建并将其作为委托挂入它们关联的窗口句柄 (HWNDs) 中。

从我的这些答案中取一些示例代码:

我们有:

const int WM_DWMCOLORIZATIONCOLORCHANGED = 0x320;

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 static Color GetWindowColorizationColor(bool opaque)
{
    var params = NativeMethods.DwmGetColorizationParameters();

    return Color.FromArgb(
        (byte)(opaque ? 255 : params.ColorizationColor >> 24), 
        (byte)(params.ColorizationColor >> 16), 
        (byte)(params.ColorizationColor >> 8), 
        (byte) params.ColorizationColor
    );
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case WM_DWMCOLORIZATIONCOLORCHANGED:

            /* 
             * Update gradient brushes with new color information from
             * NativeMethods.DwmGetColorizationParams() or the registry.
             */

            return IntPtr.Zero;

        default:
            return IntPtr.Zero;
    }
}

当Windows过渡颜色更改时,在转换的每个关键帧上分派WM_DWMCOLORIZATIONCOLORCHANGED,因此在颜色更改期间,您将在短时间内收到大量消息。这是正常的;只需像往常一样更新您的渐变刷子,您会注意到当Windows过渡窗口颜色方案时,您的渐变将与窗口框架的其余部分平稳过渡。

请记住,您可能需要考虑DWM不可用的情况,例如在运行Windows XP时或者在Windows Vista或更高版本上禁用桌面组合时。您还需要确保不过度使用此功能,否则可能会导致显著的性能损失并减慢您的应用程序速度。


2
这是我第一次在Windows 8上发布的答案... Windows 8还不错... - BoltClock
2
我尚未测试过这段代码,但是来自Windows Vista的未记录函数DwmGetColorizationParameters在Windows 10上仍然可以正确地检索强调/着色颜色。然而,在Windows 8上,您可以调用更有趣的未记录API:https://code.msdn.microsoft.com/windowsdesktop/How-to-get-Accent-brush-6652403f - Cody Gray

16
这可以在.NET 4.5及更高版本中完成,无需P/Invoke。 SystemParameters类现在具有静态的WindowGlassBrushWindowGlassColor属性以及StaticPropertyChanged事件。

从XAML中,您可以像下面这样绑定到WindowGlassBrush属性:

<Grid Background="{x:Static SystemParameters.WindowGlassBrush}">

然而,使用此任务时,当Windows更改其颜色时,背景色不会自动更新。不幸的是,SystemParameters没有提供WindowGlassBrushKey或WindowGlassColorKey属性以用作ResourceKeys与DynamicResource一起使用,因此获取更改通知需要使用代码处理StaticPropertyChanged事件。
public partial class MainWindow : Window
{
    public MainWindow()
    {
        this.InitializeComponent();
        SystemParameters.StaticPropertyChanged += this.SystemParameters_StaticPropertyChanged;

        // Call this if you haven't set Background in XAML.
        this.SetBackgroundColor();
    }

    protected override void OnClosed(EventArgs e)
    {
        SystemParameters.StaticPropertyChanged -= this.SystemParameters_StaticPropertyChanged;
        base.OnClosed(e);
    }

    private void SetBackgroundColor()
    {
        this.Background = SystemParameters.WindowGlassBrush;
    }

    private void SystemParameters_StaticPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "WindowGlassBrush")
        {
            this.SetBackgroundColor();
        }
    }
}

.NET 4.5框架中的SystemParameters.WindowGlassColor属性只是包装了DwmGetColorizationColor函数。 - Giles Bathgate
我正在模板中使用此属性,有没有办法更新模板以用新颜色替换旧颜色? - Nicke Manarin
1
遗憾的是,SystemParameters.WindowGlassBrush 将无法返回正确的强调色,至少在我的机器上是不同的。我使用这段代码:https://gist.github.com/paulcbetts/3c6aedc9f0cd39a77c37 - Robert Muehsig
正如@RobertMuehsig所建议的那样,如果启用了“从我的背景自动选择强调颜色”,则无法获取正确的颜色。 - Aidan Fitzpatrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接