更新使用 Static 属性 的元素的视觉效果

Update visual of element that uses a Static property

我正在使用 SystemParameters.WindowGlassColor 更改一堆 TabItemsForeground,但 wpf 无法检测到该静态值的颜色变化。

<Setter Property="Foreground" 
        Value="{Binding Source={x:Static SystemParameters.WindowGlassColor}, 
        Converter={StaticResource WindowColorToForegroundConverter}}"/>

转换器检测颜色是更暗还是更亮,以及 returns 反转亮度画笔。此外,如果 W10 计算机已关闭设置 Show Color on Start, Taskbar, Action Center(通过注册表)。

我可以通过 SystemParameters.StaticPropertyChanged 检测颜色何时发生变化,但我无法更新 Foreground

如何让我的应用知道 window 颜色的变化?

如何更新 TabItems 的视觉效果?

我做到了!

我的目标是根据当前 window title/chrome 颜色更新 TabItems 的样式,但由于 Static 属性 不是为了触发我的 Triggers,我必须通过代码来完成。

由于我的应用程序可以将 chrome 扩展到客户区,因此当 window 颜色较暗时,某些 Labels 可能难以阅读。

这是我的工作成果:

灯光:

深色:

方法如下:

我使用 StaticPropertyChanged:

检测 window 颜色变化
private void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "WindowGlassColor")
    {
        RibbonTabControl.UpdateVisual();
    }
}

我必须用一个名为 IsDark.

的布尔值 属性 创建自定义 TabItem 控件

我的 TabControl 有一个 public 方法来更新 IsDark 值:

public void UpdateVisual()
{
    //If glass isn't enabled, ignore.
    var isDark = !SystemParameters.IsGlassEnabled 
           //Gets a registry value. See below.
        || !Glass.UsesColor 
           //Color threshold. See below.   
        || SystemParameters.WindowGlassColor.GetBrightness() < 137; 

    //Manually update the IsDark property.
    foreach (var tab in _tabPanel.Children.OfType<AwareTabItem>())
    {
        tab.IsDark = isDark;
    }
}

如果选中 Show Color on Start, Taskbar, Action Center 则获取:

public static bool UsesColor
{
    get
    {
        try
        {
            //Start menu: 
            //HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\
            //CurrentVersion\Themes\Personalize
            var autoColorization = 
                Registry.GetValue(@"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\DWM", 
                    "ColorPrevalence", "0").ToString();

            return autoColorization.Equals("1");
        }
        catch (Exception)
        {
            return false;
        }
    }
}

获取给定的亮度值Color:

public static int GetBrightness(this Color c)
{
    //I discovered that 137 is the threshold, if more than that,
    //the window title is white. Less than that, is black.
    return (int)Math.Sqrt(
        c.R * c.R * .241 +
        c.G * c.G * .691 +
        c.B * c.B * .068);
}

最后,这是我的 AwareTabItem Style:

<Style TargetType="{x:Type local:AwareTabItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:AwareTabItem}">
                <Grid Name="Panel" Background="Transparent">
                    <Border Name="ContentBorder" BorderBrush="#FFD4D4D4" BorderThickness="0">
                        <ContentPresenter x:Name="ContentSite"
                                          VerticalAlignment="Center" Effect="{x:Null}"
                                          HorizontalAlignment="Center"
                                          ContentSource="Header" Margin="10,2"/>
                    </Border>
                </Grid>

                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True" SourceName="Panel">
                        <Setter Property="Foreground" Value="#FF2B579A" />
                        <Setter Property="Background" Value="#FFFAFAFA" />
                    </Trigger>

                    <Trigger Property="IsSelected" Value="True">
                        <Setter TargetName="Panel" Property="Background" Value="#FFFAFAFA" />
                        <Setter Property="Foreground" Value="#FF2B579A" />
                        <Setter TargetName="ContentBorder" Property="BorderThickness" Value="1,1,1,0" />
                    </Trigger>

                    <!--When ExtendChrome, !IsDark, !IsSelected-->
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Source={x:Static prop:Settings.Default}, Path=EditorExtendChrome, FallbackValue=False}" Value="True"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsDark}" Value="False"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsSelect}" Value="False"/>
                        </MultiDataTrigger.Conditions>

                        <Setter Property="Foreground" Value="#FF000000"/>

                        <Setter TargetName="ContentBorder" Property="Background">
                            <Setter.Value>
                                <RadialGradientBrush>
                                    <GradientStop Color="#9AFFFFFF" Offset="0"/>
                                    <GradientStop Color="#90FFFFFF" Offset="0.4"/>
                                    <GradientStop Offset="1"/>
                                </RadialGradientBrush>
                            </Setter.Value>
                        </Setter>
                    </MultiDataTrigger>

                    <!--When ExtendChrome, !IsDark, IsMouseOver-->
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Source={x:Static prop:Settings.Default}, Path=EditorExtendChrome, FallbackValue=False}" Value="True"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsDark}" Value="False"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True"/>
                        </MultiDataTrigger.Conditions>

                        <Setter Property="Foreground" Value="#FF2B579A"/>
                    </MultiDataTrigger>

                    <!--When ExtendChrome, !IsDark, IsSelected-->
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Source={x:Static prop:Settings.Default}, Path=EditorExtendChrome, FallbackValue=False}" Value="True"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsDark}" Value="False"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsSelected}" Value="True"/>
                        </MultiDataTrigger.Conditions>

                        <Setter TargetName="Panel" Property="Background" Value="#FFFAFAFA" />
                        <Setter Property="Foreground" Value="#FF2B579A" />
                        <Setter TargetName="ContentBorder" Property="BorderThickness" Value="1,1,1,0" />
                    </MultiDataTrigger>

                    <!--When ExtendChrome, IsDark, !IsSelected-->
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Source={x:Static prop:Settings.Default}, Path=EditorExtendChrome, FallbackValue=False}" Value="True"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsDark}" Value="True"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsSelected}" Value="False"/>
                        </MultiDataTrigger.Conditions>

                        <Setter Property="Foreground" Value="#FFF8F8FF"/>

                        <Setter TargetName="ContentBorder" Property="Background">
                            <Setter.Value>
                                <RadialGradientBrush>
                                    <GradientStop Color="{Binding Source={x:Static SystemParameters.WindowGlassColor}, 
                                                  Converter={StaticResource ColorToAlphaConverter}, ConverterParameter=6E}" Offset="0"/>
                                    <GradientStop Color="{Binding Source={x:Static SystemParameters.WindowGlassColor}, 
                                                  Converter={StaticResource ColorToAlphaConverter}, ConverterParameter=50}" Offset="0.4"/>
                                    <GradientStop Offset="1"/>
                                </RadialGradientBrush>
                            </Setter.Value>
                        </Setter>
                    </MultiDataTrigger>

                    <!--When ExtendChrome, IsDark, IsMouseOver-->
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Source={x:Static prop:Settings.Default}, Path=EditorExtendChrome, FallbackValue=False}" Value="True"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsDark}" Value="True"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMouseOver}" Value="True"/>
                        </MultiDataTrigger.Conditions>

                        <Setter Property="Foreground" Value="#FFBFEFFF"/>
                    </MultiDataTrigger>

                    <!--When ExtendChrome, IsDark, IsSelected-->
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Source={x:Static prop:Settings.Default}, Path=EditorExtendChrome, FallbackValue=False}" Value="True"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsDark}" Value="True"/>
                            <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsSelected}" Value="True"/>
                        </MultiDataTrigger.Conditions>

                        <Setter TargetName="Panel" Property="Background" Value="#FFFAFAFA" />
                        <Setter Property="Foreground" Value="#FF2B579A" />
                        <Setter TargetName="ContentBorder" Property="BorderThickness" Value="1,1,1,0" />
                    </MultiDataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>

    <!--Default Values-->
    <Setter Property="FontFamily" Value="Segoe UI Semilight"/>
</Style>

我注意到当使用深 window 颜色时,黑色 RadialGradientBrush 有一种奇怪的效果,所以我使用实际的 window 颜色来制作柔和的背景(当 window 太透明时提高可读性)。要使用 GradientStop,我必须创建一个 Converter,它采用当前 window 颜色并将给定参数应用为 alpha 值。

ColorToAlpha 转换器:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    var color = value as Color?;
    var alphaAux = parameter as string;

    if (!color.HasValue)
        return value;

    if (String.IsNullOrEmpty(alphaAux))
        return value;

    int alpha = 0;
    if (!int.TryParse(alphaAux, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out alpha))
        return value;

    return Color.FromArgb((byte)alpha, color.Value.R, color.Value.G, color.Value.B);
}