Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I fall back to a high contrast color in WPF?

Tags:

wpf

xaml

I have some XAML which sets the foreground color directly:

<Style x:Key="HomeHeaderText" TargetType="TextBlock">
    <Setter Property="FontSize" Value="24" />
    <Setter Property="FontFamily" Value="Segoe Light UI" />
    <Setter Property="Foreground" Value="#FF606060" />
    <Setter Property="Margin" Value="0,50,0,30" />
</Style>

I would like to detect in the style whether or not the system is in high contrast mode, and fall back to one of the system colors if so.

How can one do this using styles?


I tried setting this using a trigger, but this results in XamlParseException at runtime:

<Style x:Key="HomeHeaderText" TargetType="TextBlock">
    <Setter Property="FontSize" Value="24" />
    <Setter Property="FontFamily" Value="Segoe Light UI" />
    <Setter Property="Foreground" Value="#FF606060" />
    <Setter Property="Margin" Value="0,50,0,30" />
    <Style.Triggers>
        <DataTrigger Binding="{x:Static SystemParameters.HighContrast}" Value="True">
           <Setter Property="Foreground"
               Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
        </DataTrigger>
    </Style.Triggers>
</Style>
like image 779
Billy ONeal Avatar asked Sep 09 '13 23:09

Billy ONeal


3 Answers

The problem with your attempt is that DataTrigger.Binding wants a binding, but you gave it a direct value. You can solve this by setting the binding source:

{Binding Source={x:Static SystemParameters.HighContrast}}

However, this will not be dynamic -- the style would not update if someone toggles high-contrast while the application is running. Ideally it would be nice to have something like this:

{Binding Source={x:Static SystemParameters}, Path=HighContrast}

But unfortunately that isn't possible since it's a static property. So, binding to the HighContrastKey resource is the better option. Instead of using Tag, you could bind this to an attached property. Come to think of it, Microsoft probably should have implemented SystemParameters as attached properties in the first place. Try something like this:

public static class SystemParameterProperties {
    public static readonly DependencyProperty HighContrastProperty =
        DependencyProperty.RegisterAttached("HighContrast", typeof(bool), typeof(SystemParameterProperties),
            new FrameworkPropertyMetadata() {Inherits = true});

    public static bool GetHighContrast(DependencyObject obj) {
        return (bool)obj.GetValue(HighContrastProperty);
    }

    public static void SetHighContrast(DependencyObject obj, bool value) {
        obj.SetValue(HighContrastProperty, value);
    }
}

I used Inherits = true on the property so that we can just set it on the outermost container and let it be accessible everywhere, i.e.:

<Window ...
        xmlns:attachedProperties="..."
        attachedProperties:SystemParameterProperties.HighContrast="{DynamicResource ResourceKey={x:Static Member=SystemParameters.HighContrastKey}}">
    ...
</Window>

Finally, your trigger would be:

<Trigger Property="attachedProperties:SystemParameterProperties.HighContrast" Value="True">
     <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
</Trigger>
like image 134
nmclean Avatar answered Nov 05 '22 11:11

nmclean


I'd have solved leveraging an helper:

public class HighContrastHelper
    : DependencyObject
{
    #region Singleton pattern

    private HighContrastHelper()
    {
        SystemParameters.StaticPropertyChanged += SystemParameters_StaticPropertyChanged;
    }

    private static HighContrastHelper _instance;

    public static HighContrastHelper Instance
    {
        get
        {
            if (_instance == null)
                _instance = new HighContrastHelper();

            return _instance;
        }
    }

    #endregion

    void SystemParameters_StaticPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        Console.WriteLine(e.PropertyName);
        if (e.PropertyName == "HighContrast")
        {
            HighContrastHelper.Instance.IsHighContrast = SystemParameters.HighContrast;
        }
    }

    #region DP IsHighContrast

    public static readonly DependencyProperty IsHighContrastProperty = DependencyProperty.Register(
        "IsHighContrast",
        typeof(bool),
        typeof(HighContrastHelper),
        new PropertyMetadata(
            false
            ));

    public bool IsHighContrast
    {
        get { return (bool)GetValue(IsHighContrastProperty); }
        private set { SetValue(IsHighContrastProperty, value); }
    }

    #endregion  
 }

Afterward, the usage in your code is straightforward:

<Style x:Key="HomeHeaderText" TargetType="TextBlock">
    <Setter Property="FontSize" Value="24" />
    <Setter Property="FontFamily" Value="Segoe Light UI" />
    <Setter Property="Foreground" Value="#FF606060" />
    <Setter Property="Margin" Value="0,50,0,30" />
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=IsHighContrast, Source={x:Static local:HighContrastHelper.Instance}}" Value="True">
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
        </DataTrigger>
    </Style.Triggers>
</Style>

Hope it helps.

like image 24
Mario Vernari Avatar answered Nov 05 '22 10:11

Mario Vernari


You can bind it to Tag property of textblock and use that in DataTrigger like below:

  <Style x:Key="MyTextBoxStyle" TargetType="{x:Type TextBlock}">
     <Setter Property="Tag" Value="{DynamicResource {x:Static SystemParameters.HighContrastKey}}"/>
     <Style.Triggers>
         <DataTrigger Binding="{Binding Path=Tag , RelativeSource= {x:Static RelativeSource.Self}}" Value="True">
                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
         </DataTrigger>
     </Style.Triggers>
  </Style>
like image 22
Nitin Avatar answered Nov 05 '22 10:11

Nitin