Default TextBlock style overriding button text color
Asked Answered
F

3

7

My problem occurs with WPF in .NET 3.5 SP1 and can be described as follows:

I have a default Style hitting all TextBlock elements in my UI. That is how it looks:

<Style TargetType="{x:Type TextBlock}">
   <Setter Property="TextTrimming" Value="CharacterEllipsis"/>
   <Setter Property="Foreground" Value="Red"/>
</Style>

That works fine for all TextBlocks. In addition to that I have a Button style including a ControlTemplate that looks like this (shortened):

<Style x:Key="MyButtonStyle" TargetType="{x:Type Button}" BasedOn="{x:Null}">
   <Setter Property="Foreground" Value="Green"/>
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type Button}">
            <Border x:Name="Border" 
                    Background="{TemplateBinding Background}" 
                    BorderThickness="{TemplateBinding BorderThickness}" 
                    Padding="{TemplateBinding Padding}" 
                    Height="24" 
                    BorderBrush="{TemplateBinding BorderBrush}">
               <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                 VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                 TextBlock.Foreground="{TemplateBinding Foreground}"/>
            </Border>
            <ControlTemplate.Triggers>...</ControlTemplate.Triggers>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
</Style>

Notice the line TextBlock.Foreground="{TemplateBinding Foreground}" in the ContentPresenter. This should set the button text to green and in fact it does in the designer view of Visual Studio. But when I compile and run the program the button text is red, the text color is set by the default TextBlock style. I verified this with Snoop.

How can I prevent the defaultTextBlock style from overriding the TextBlock.Foreground value? The OverridesDefaultStyle property of ContentPresenter doesn't help in this case.

Any idea?

Ferrocene answered 21/12, 2010 at 14:9 Comment(0)
M
15

See answer 5 at this link

This happends because the ContentPresenter creates a TextBlock for a string content, and since that TextBlock isn't in the visual tree, it will lookup to Application level resource. And if you define a style for the TextBlock at Application level, then it will be applied to these TextBlock within ContentControl

A workaround is to define a DataTemplate for System.String, where we can explicitly use a default TextBlock to display the content. You can place that DataTemplate in the same dictionary you define the TextBlock style so that this DataTemplate will be applied to whatever ContentPresenter effected by your style.

Try adding this to the ResourceDictionary

<DataTemplate DataType="{x:Type sys:String}">
    <TextBlock Text="{Binding}">
        <TextBlock.Resources> 
            <Style TargetType="{x:Type TextBlock}"/>
        </TextBlock.Resources>
    </TextBlock>
</DataTemplate>
Meg answered 21/12, 2010 at 14:23 Comment(6)
Thank you for your answer. There's two things I don't understand: 1. Even if the TextBlock created by the ContentPresenter is not in the VisualTree it should still accept the values I put in TextBlock.Foreground, right? 2. By using the DataTemplate you described I am restricted to only one style for all texts inside ContentPresenter elements, correct?Ferrocene
@HA: As I understand it, it will only make sure the other TextBlock Style isn't picket up. But I could be wrong, I've used it before and I haven't had any problems with itMeg
@HA: Also, this will only be used when a TextBlock is created because of the Content of a ContentControl. You can still do the things you do in your example, like setting the Foreground etc. Or am I missunderstanding the question?Meg
No, you're right. I didn't fully understand it at first. However I decided not to use this solution, because it fails if I put an Image or Rectangle or something else inside the button. Instead I removed the Foreground property from the default TextBlock style. That way works better for me. Nonetheless thank you for your help.Ferrocene
OVERRIDING DATA TEMPLATE FOR System.String IS A BAD IDEA, because it loses underscore-to-keyboard-shortcut and ContentStringFormat functionality. This put me in hot water. See more details here: ikriv.com/dev/dotnet/wpftextstyle/StringTemplate.shtmlStabilize
You can use AccessText instead of TextBlock to preserve keyboard shortcut functionality.Calyx
S
6

You are better off not overriding default style for the TextBlock. The best idea I could come up with so far is to create a style for Control and apply it to all top level windows.

<!-- App.xaml -->
<Application.Resources>
    <Style x:Key="RedStyle" TargetType="{x:Type Control}">
        <Setter Property="TextTrimming" Value="CharacterEllipsis"/>
        <Setter Property="Foreground" Value="Red"/>
    </Style>
</Application.Resources>

<!-- MainWindow.xaml -->
<Window Style="{StaticResource RedStyle}" ...>
    ...
</Window>

See here for more details: http://www.ikriv.com/dev/dotnet/wpftextstyle/

Stabilize answered 28/10, 2014 at 19:51 Comment(0)
T
2

With respect to the 2 options:

  1. @Fredrik Hedblad

Try adding this to the ResourceDictionary

<DataTemplate DataType="{x:Type sys:String}">
    <TextBlock Text="{Binding}">
        <TextBlock.Resources> 
            <Style TargetType="{x:Type TextBlock}"/>
        </TextBlock.Resources>
    </TextBlock>
</DataTemplate>
  1. @Ivan Krivyakov

You are better off not overriding default style for the TextBlock. The best idea I could come up with so far is to create a style for Control and apply it to all top level windows.

I would suggest an alternative approach and use an attached Dependency Property e.g.

namespace AttachedProperties
{
  public static class TextBlockExtensions
  {
    public static bool GetUseAppThemeStyle(DependencyObject obj)
    {
      return (bool)obj.GetValue(UseAppThemeStyleProperty);
    }
    public static void SetUseAppThemeStyle(DependencyObject obj, bool value)
    {
      obj.SetValue(UseAppThemeStyleProperty, value);
    }
    // Using a DependencyProperty as the backing store for UseAppThemeStyle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty UseAppThemeStyleProperty =
        DependencyProperty.RegisterAttached("UseAppThemeStyle", typeof(bool), typeof(TextBlockExtensions), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits));
  }
}

NOTE: You can set it to be true or false by default

Then having the namespace:

xmlns:attachedProperties="clr-namespace:AttachedProperties"

make a default style:

   <Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
    <Style.Triggers>
      <DataTrigger Binding="{Binding Path=(attachedProperties:TextBlockExtensions.UseAppThemeStyle), RelativeSource={RelativeSource Mode=Self}}" Value="True">
        <Setter Property="TextTrimming" Value="CharacterEllipsis"/>
        <Setter Property="Foreground" Value="Red"/>
      </DataTrigger>
    </Style.Triggers>
  </Style>

Then if you need to get the default you can just set the attached property either in a style:

  <Style x:Key="blueButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
    <Setter Property="attachedProperties:TextBlockExtensions.UseAppThemeStyle" Value="False" />
    <Setter Property="Foreground" Value="Blue" />
  </Style>

or directly on a button:

<Button attachedProperties:TextBlockExtensions.UseAppThemeStyle="False" Foreground="Blue">I'm blue da ba dee da ba die...</Button>
Torrens answered 25/5, 2020 at 13:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.