Reputation: 3358
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
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:
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