none
Change TextBlock Foreground color based on its Background color

    Question

  • In my WPF application, I have to keep on updating TextBlock background based on user conditions. TextBlock style is defined in App.xaml. If the background is too dark (Green/Blue) I want to set the foreground to white else black. How can I achieve this? I explored following two options:

    1. Via DataTriggers: In App.xaml:

    <Style TargetType="TextBlock">             
         <Setter Property="FontSize" Value="14"/>
         <Setter Property="FontStyle" Value="Normal"/>
         <Style.Triggers>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=Background,PresentationTraceSources.TraceLevel=High}" Value="White">
                <Setter Property="Foreground" Value="Maroon"/>
            </DataTrigger>
         </Style.Triggers>
     </Style>


    This doesn't seem to work. I never see an update in textblock's foreground property. While debugging, I see the following for the binding: <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    System.Windows.Data Warning: 72 : RelativeSource.Self found TextBlock (hash=61003640) System.Windows.Data Warning: 78 : BindingExpression (hash=6398298): Activate with root item TextBlock (hash=61003640) System.Windows.Data Warning: 107 : BindingExpression (hash=6398298): At level 0 using cached accessor for TextBlock.Background: DependencyProperty(Background) System.Windows.Data Warning: 104 : BindingExpression (hash=6398298): Replace item at level 0 with TextBlock (hash=61003640), using accessor DependencyProperty(Background) System.Windows.Data Warning: 101 : BindingExpression (hash=6398298): GetValue at level 0 from TextBlock (hash=61003640) using DependencyProperty(Background): SolidColorBrush (hash=58614288) System.Windows.Data Warning: 80 : BindingExpression (hash=6398298): TransferValue - got raw value SolidColorBrush (hash=58614288) System.Windows.Data Warning: 89 : BindingExpression (hash=6398298): TransferValue - using final value SolidColorBrush (hash=58614288) <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    What is "SolidColorBrush (hash=58614288)"? Is it the Hex color code or hascode for the object of type SolidColorBrush?

    1. Using IValueConverter: Have not tried it since I don't want to convert one value to another but change a UIElement's property based on some other property change. Also, wouldn't converters will give a performance hit since almost all UIElements use TextBlock internally to display data?

    Any help is highly appreciated.

    Thanks,

    RDV


    • Edited by RDV17 Wednesday, August 28, 2013 10:59 PM Added Code
    Wednesday, August 28, 2013 12:45 AM

Answers

  • Hi,

    I have moved the same style which i defined inline to a global style in App.xaml. it seems to work fine for me.

    App.xaml

    <Application.Resources>
            <Style x:Key="TextBlockStyle" TargetType="TextBlock">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=Background.Color}" Value="White">
                        <Setter Property="Foreground" Value="Black"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=Background.Color}" Value="Red">
                        <Setter Property="Foreground" Value="White"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=Background.Color}" Value="Green">
                        <Setter Property="Foreground" Value="White"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Application.Resources>

    MainWindow.xaml

    <StackPanel x:Name="myStackPanel">
                    <TextBlock Text="Hai" Style="{StaticResource TextBlockStyle}"/>
                    <TextBlock Text="Bye" Style="{StaticResource TextBlockStyle}"/>
                    <TextBlock Text="Good" Style="{StaticResource TextBlockStyle}"/>
                    <Button Content="Red" Click="Button_Click"/>
                    <Button Content="White" Click="Button_Click"/>
                    <Button Content="Green" Click="Button_Click"/>
                </StackPanel>

    Code Behind(Button_Click event)

      private void Button_Click(object sender, RoutedEventArgs e)
            {
                var button = sender as Button;
                if (button.Content.Equals("Red"))
                {
                    foreach (var child in this.myStackPanel.Children)
                    {
                        if (child is TextBlock)
                        {
                            var textblock = child as TextBlock;
                            textblock.Background = new SolidColorBrush(Colors.Red);
                        }
                    }
                }
                if (button.Content.Equals("Green"))
                {
                    foreach (var child in this.myStackPanel.Children)
                    {
                        if (child is TextBlock)
                        {
                            var textblock = child as TextBlock;
                            textblock.Background = new SolidColorBrush(Colors.Green);
                        }
                    }
                }
                if (button.Content.Equals("White"))
                {
                    foreach (var child in this.myStackPanel.Children)
                    {
                        if (child is TextBlock)
                        {
                            var textblock = child as TextBlock;
                            textblock.Background = new SolidColorBrush(Colors.White);
                        }
                    }
                }
            }

    Outputs I got


    srithar

    • Marked as answer by RDV17 Friday, August 30, 2013 8:28 PM
    Thursday, August 29, 2013 5:28 AM

All replies

  • Hi,

    You must target the binding to Background.Color in data trigger not to Background.

     <StackPanel Orientation="Horizontal">
                    <Button Content="Green" Click="Button_click"></Button>
                    <Button Content="Red" Click="Button_click"></Button>
                    <Button Content="White" Click="Button_click"></Button>
                    <TextBlock x:Name="txtBlock" Text="Hello Good Morning How are you been">
                        <TextBlock.Style>
                            <Style>
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding Path=Background.Color,RelativeSource={RelativeSource Mode=Self}}" Value="Green">
                                        <Setter Property="TextBlock.Foreground" Value="White" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding Path=Background.Color,RelativeSource={RelativeSource Mode=Self}}" Value="Red">
                                        <Setter Property="TextBlock.Foreground" Value="White" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding Path=Background.Color,RelativeSource={RelativeSource Mode=Self}}" Value="White">
                                        <Setter Property="TextBlock.Foreground" Value="Black" />
                                    </DataTrigger>
                                </Style.Triggers>
                            </Style>
                        </TextBlock.Style>
                    </TextBlock>
                </StackPanel>

    Button_Click Event

    private void Button_click(object sender, RoutedEventArgs e)
            {
                var button = sender as Button;
                if (button.Content.Equals("Red"))
                {
                    this.txtBlock.Background = new SolidColorBrush(Colors.Red);
                }
                if (button.Content.Equals("Green"))
                {
                    this.txtBlock.Background = new SolidColorBrush(Colors.Green);
                }
                if (button.Content.Equals("White"))
                {
                    this.txtBlock.Background = new SolidColorBrush(Colors.White);
                }
            }


    srithar

    Wednesday, August 28, 2013 5:15 AM
  • Thanks Srithar. But Background.Color did not help me. I updated my question with the styling code I am using. A little more about my application:

    When my application starts, my TextBlocks have default background color. All the Textblock styling is stored in a ResourceDictionary which is stored in a different solution. I have only one ResourceDictionary in App.xaml of my application:

    <Application x:Class="MySolution"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <Application.Resources>
            <ResourceDictionary>
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="pack://application:,,,/ResourcesSolution;component/Resources/GenericStyles.xaml"/>
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </Application.Resources>
    </Application>

    FontWeight, FontStyle, even Foreground etc. are correctly picked up from here. But these are static properties. On certain user action, I change the TextBlock's background color at runtime but sometimes that makes text unreadable like Black text on Green background. I can definitely bind the foreground color as well when background color is changing, but in that case I would have to do that binding in all the views. Instead I want to have a global style take care of this job so that even if I forget to bind the foreground color, correct color is automatically picked.

    I have a big application and performance is a major concern. That's why I am hesitant to use converters and was looking for some xaml based solutions as this is just a condition based issue.

    Thanks,

    RDV

    Wednesday, August 28, 2013 11:13 PM
  • Hi,

    I have moved the same style which i defined inline to a global style in App.xaml. it seems to work fine for me.

    App.xaml

    <Application.Resources>
            <Style x:Key="TextBlockStyle" TargetType="TextBlock">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=Background.Color}" Value="White">
                        <Setter Property="Foreground" Value="Black"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=Background.Color}" Value="Red">
                        <Setter Property="Foreground" Value="White"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=Background.Color}" Value="Green">
                        <Setter Property="Foreground" Value="White"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Application.Resources>

    MainWindow.xaml

    <StackPanel x:Name="myStackPanel">
                    <TextBlock Text="Hai" Style="{StaticResource TextBlockStyle}"/>
                    <TextBlock Text="Bye" Style="{StaticResource TextBlockStyle}"/>
                    <TextBlock Text="Good" Style="{StaticResource TextBlockStyle}"/>
                    <Button Content="Red" Click="Button_Click"/>
                    <Button Content="White" Click="Button_Click"/>
                    <Button Content="Green" Click="Button_Click"/>
                </StackPanel>

    Code Behind(Button_Click event)

      private void Button_Click(object sender, RoutedEventArgs e)
            {
                var button = sender as Button;
                if (button.Content.Equals("Red"))
                {
                    foreach (var child in this.myStackPanel.Children)
                    {
                        if (child is TextBlock)
                        {
                            var textblock = child as TextBlock;
                            textblock.Background = new SolidColorBrush(Colors.Red);
                        }
                    }
                }
                if (button.Content.Equals("Green"))
                {
                    foreach (var child in this.myStackPanel.Children)
                    {
                        if (child is TextBlock)
                        {
                            var textblock = child as TextBlock;
                            textblock.Background = new SolidColorBrush(Colors.Green);
                        }
                    }
                }
                if (button.Content.Equals("White"))
                {
                    foreach (var child in this.myStackPanel.Children)
                    {
                        if (child is TextBlock)
                        {
                            var textblock = child as TextBlock;
                            textblock.Background = new SolidColorBrush(Colors.White);
                        }
                    }
                }
            }

    Outputs I got


    srithar

    • Marked as answer by RDV17 Friday, August 30, 2013 8:28 PM
    Thursday, August 29, 2013 5:28 AM
  • Thanks Srithar,

    I tested my code by setting Background on only TextBlock control and I could see the following style triggers working as expected when declared as global style sheet:

    <Style TargetType="TextBlock">
            <Setter Property="FontSize" Value="12"/>
            <Setter Property="FontStyle" Value="Italic"/>
            <Style.Triggers>
                <Trigger Property="Background" Value="White">
                    <Setter Property="Foreground" Value="Aqua"/>
                </Trigger>
                <Trigger Property="Background.Color" Value="Transparent">
                    <Setter Property="Foreground" Value="BlueViolet"/>
                </Trigger>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=Background}" Value="White">
                    <Setter Property="Foreground" Value="Maroon"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=Background.Color}" Value="#FF008000">
                    <Setter Property="Foreground" Value="Blue"/>
                </DataTrigger>
              
            </Style.Triggers>
        </Style>

    However, I did not notice the behavior initially because I assumed that the Button's content, which is internally represented by TextBlock, should also use the TextBlock's style trigger (it is picking up TextBlock's FontSize and FontStyle defined in global style).

    I think this is related to ContentPresenter issue and should be addressed in a different thread.

    Thanks again,

    RDV

    Friday, August 30, 2013 8:40 PM