Search code examples
wpfstylesbackground-colorforeground

Style to choose suitable foreground according to background color


I have a WPF application which ships with a set of default styles for Label, TextBox etc. defined by item types (no explicit keys defined).

Within the application there are two main containers used, one with dark background and one with light background, such that sometimes it's right to use black as the foreground color for a Label and sometimes its dramatically wrong. On the other hand, editors are always styled rather traditionally with light background and dark foreground, so I cannot just set the foreground for all child elements to the inverse.

Is there an elegant way to make my labels (and maybe TextBlocks as well) to decide about their foreground color dependent on 'their' background? I only want to switch between two colors, thus no auto-contrast-maximization needed, only some threshold to avoid white font on white ground.

I also don't want to define two sets of default styles, I strongly search for some way to make my single Label-Default-Style be appropriate for both background variants.

Is it possible (and feasible without too much performance hit) to add a trigger/binding to the style, which evaluates the current background color?

Alternatively, I would be interested in best practices how to cleanly set background-colors for certain FrameworkElements, especially containers/panels, without running into the problems described above.

Here is what I tried (simplified of course):

  <UniformGrid>
  <UniformGrid.Resources>
  <!-- SimpleStyles: Label -->
     <Style x:Key="{x:Type Label}" TargetType="{x:Type Label}">
        <Setter Property="HorizontalContentAlignment" Value="Left"/>
        <Setter Property="VerticalContentAlignment" Value="Top"/>
        <Setter Property="Template">
           <Setter.Value>
              <ControlTemplate TargetType="{x:Type Label}">
                 <Border>
                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
                 </Border>
                 <ControlTemplate.Triggers>
                    <Trigger Property="Background" Value="Black">
                       <Setter Property="Foreground" Value="Red"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="false">
                       <Setter Property="Foreground" Value="#888888"/>
                    </Trigger>
                 </ControlTemplate.Triggers>
              </ControlTemplate>
           </Setter.Value>
        </Setter>
     </Style>
  </UniformGrid.Resources>
  <Label x:Name="bgSetExplicitly" Background="Black">abc
  </Label>
  <Border Background="Black">
     <Label x:Name="bgInheritedFromParent" >abc
     </Label>
  </Border>
  <Label>abc
  </Label>
  <Label>abc
  </Label>

You can see that the label's background is chosen nicely, if the Label has an explicit background set (x:Name=bgSetExplicitly), but if the background is 'inherited' from the parent in the VisualTree (x:Name="bgInheritedFromParent"), it's not. I would love to have that working that the style can evaluate the "effective background" (no matter where it comes from) and choose an appropriate foreeground-brush for this background.


Solution

  • This question seems to imply a deeper issue. I would guess that you haven't consolidated the management of foreground colors, and so you have an application that has code or styles that set foreground colors all over the place. And now you're dealing with one of the implications of that.

    I'd face the deeper issue and fix that. I don't really understand why you're resistant to the idea of creating default styles, but assuming for the moment that you have a good reason not to do this (other than "I should have created default styles at some point, but now that I haven't and my application has gotten big, it's just too hard," in which case, I have a different suggestion), how about creating global resources?

    Create objects in the application's resource dictionary for the controls' foreground and background colors, and fix the XAML so that instead of referencing brushes directly, it uses the DynamicResource markup extension to get them from the resource dictionary. In code, instead of setting the Foreground property on the controls to a brush directly, use GetResource to get the brush. And set the background the same way.

    Once you've done this, if you want to change foreground/background colors globally in your application, you just change the resources.

    This is basically how you start making a WPF application skinnable, which seems to be the road you're going down.