Nicke Manarin
Nicke Manarin

Reputation: 3358

Update visual of element that uses a Static property

I'm using SystemParameters.WindowGlassColor to change the Foreground of a bunch of TabItems, but wpf can't detect the color changes with that static value.

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

The converter detects if the color is darker or brighter and returns a inverted brightness brush. Also if the W10 machine has the setting Show Color on Start, Taskbar, Action Center turned off (via registry).

I can detect when the color changes via SystemParameters.StaticPropertyChanged, but I couldn't update the Foreground.

How could I make my app aware of changes in the window color?
or
How can I update the visual of my TabItems?

Upvotes: 0

Views: 157

Answers (1)

Nicke Manarin
Nicke Manarin

Reputation: 3358

I've made it!

My goal was to update the style of my TabItems based on the current window title/chrome color, but since the Static property wasn't triggering my Triggers, I had to do it by code.

Since my app can extend the chrome into the client area, some Labels may be difficult to read while the window color is dark.

This is the result of my work:

Light: Light Theme

Dark: Dark Theme

Here's how:

I detect the window color changes by using StaticPropertyChanged:

private void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "WindowGlassColor")
    {
        RibbonTabControl.UpdateVisual();
    }
}

I had to create a custom TabItem control with a single boolean property called IsDark.

My TabControl has a public method to update the IsDark value:

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

Gets if the Show Color on Start, Taskbar, Action Center is checked:

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

Gets the brightness value of a give 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);
}

And finally, here's my 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>

I've noticed that when using a dark window color, the black RadialGradientBrush had a strange effect, so I'm using the actual window color to make a soft background (to improve readability when the window is too transparent). And to use a GradientStop I had to create a Converter that takes the current window color and applies the given parameter as the alpha value.

ColorToAlpha converter:

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

Upvotes: 1

Related Questions