6
votes

I have Style that applies to all of the buttons of my application:

<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
    <Setter Property="Background" Value="Red" />
    <Setter Property="Foreground" Value="Black" />
    <Setter Property="FontSize" Value="16" />

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid>
                    <Ellipse x:Name="StatusButtonCircle" Stroke="Black" StrokeThickness="0" Fill="AliceBlue" Stretch="Uniform">
                        <Ellipse.Width>
                            <Binding ElementName="StatusButtonCircle" Path="ActualHeight"/>
                        </Ellipse.Width>
                    </Ellipse>
                    <Ellipse x:Name="StatusButtonCircleHighlight" Margin="4" Stroke="Black" StrokeThickness="2" Stretch="Uniform">
                        <Ellipse.Width>
                            <Binding ElementName="StatusButtonCircleHighlight" Path="ActualHeight"/>
                        </Ellipse.Width>
                    </Ellipse>
                    <ContentPresenter HorizontalAlignment="Center"  
                                    VerticalAlignment="Center"/>
                </Grid>

                <ControlTemplate.Triggers>
                    ... some Triggers here
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

How can I change properties (e.g. FontWeight, FontSize etc.) in XAML? I tried this:

<Button FontWeight="Bold" FontSize="30" Foreground="Red">
</Button>

In the designer-view, I see the changes. But during runtime those changes are not applied.


After some investigation, I also have a Style for all TextBlock like this:

<Style TargetType="{x:Type TextBlock}">
    <Setter Property="FontSize" Value="16" />
    <Setter Property="FontFamily" Value="Segoe UI Semibold" />
    <Setter Property="Foreground" Value="White" />
</Style>

This Style seems to override the TextBlock that is used on the Button. I still can't change the Text Properties in XAML.

Here's what it looks like if I use the Styles above in an empty project:

enter image description here

In the designer, the changes are applied, during runtime the one from the TextBlock are applied. If I assign a x:Key to the TextBlock, it works fine. But then I have to assign this Style to every TextBlock used in the app manually.

4
The code you provided does not reproduce the issue. I just created new project, added Button and TextBlock styles to resources, added the Button element, and it works (foreground is Red). Try creating a test project where you can reproduce the issue.djomlastic
BasedOn should point to another "base" style for Button, it is not used to specify the target type.Fredrik
@Fredrik It can be used like that when you want to extend global style for the type - microsoft docs.djomlastic
@djomlastic true. thanks.Fredrik

4 Answers

1
votes

You can do it with the BasedOn. I show you an example.

<Window.Resources>
    <Style TargetType="ToggleButton" BasedOn="{StaticResource DefToggleButton}">
       <Setter Property="FontWeight" Value="Bold"/>
       <Setter Property="Content" Value="Some Cool Stuff"/>
           <Style.Triggers>
                <Trigger Property="IsChecked" Value="True">
                      <Setter Property="Content" Value="More Stuff"/>
                </Trigger>
           </Style.Triggers>
    </Style>
</Window.Resources>

Here in my resources I have DefToggleButton, now in my xaml file I can set up any Property according to my need (which in this case is the FontWeight and Content Property).

1
votes

You are facing typical style inheritance issue in wpf.

A control looks for its style at the point when it is being initalized. The way the controls look for their style is by moving upwards in logical tree and asking the logical parent if there is appropriate style for them stored in parent's resources dictionary.

In your case, you are using ContentPresenter in button as a default behaviour. and it is using TextBlock to represent text in button by default.

Therefore at the time of initialization, ContentPresenter finding TextBlock style and applying to represent content in button.

If you want to restrict ContentPresenter to look for the style then you have to bind a blank style to content presenter so that it will not look for any further style.

<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
            <Setter Property="Background" Value="Red" />
            <Setter Property="Foreground" Value="Black" />
            <Setter Property="FontSize" Value="16" />

            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <Ellipse x:Name="StatusButtonCircle" Stroke="Black" StrokeThickness="0" Fill="AliceBlue" Stretch="Uniform">
                                <Ellipse.Width>
                                    <Binding ElementName="StatusButtonCircle" Path="ActualHeight"/>
                                </Ellipse.Width>
                            </Ellipse>
                            <Ellipse x:Name="StatusButtonCircleHighlight" Margin="4" Stroke="Black" StrokeThickness="2" Stretch="Uniform">
                                <Ellipse.Width>
                                    <Binding ElementName="StatusButtonCircleHighlight" Path="ActualHeight"/>
                                </Ellipse.Width>
                            </Ellipse>
                            <ContentPresenter HorizontalAlignment="Center"  
                                    VerticalAlignment="Center">
                                <ContentPresenter.Resources>
                                    <Style TargetType="TextBlock" BasedOn="{x:Null}"/>
                                    <!--  Assigned Blank style here therefore it will not search for any further style-->
                                </ContentPresenter.Resources>
                            </ContentPresenter>
                        </Grid>


                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
0
votes

I think if you remove the Template from your Style, then you can do what you want to do, like this:

<Window.Resources>
    <Style TargetType="Button" x:Key="stBtn>
        <Setter Property="Background" Value="Blue" />
        <Setter Property="Foreground" Value="White" />
        <Setter Property="FontFamily" Value="Segoe UI Semibold" />
    </Style>
</Window.Resources>

The Template that you have says, that all Buttons should be shown as a Border with a ContentPresenter inside, which is not what you have asked.

Without the Template, you can define your Buttons like this:

<Button Content="Hi!" Style="{StaticResource stBtn}" Foreground="Red" >

Like this, you have a Blue Button with Red Foreground.

=================

Edit

So what if you define a Template, and use it in you style, like this?

Then, by TemplateBinding you can define that the Foreground and teh Content come later, when the Button is actually defined.

<Window.Resources>
    <ControlTemplate x:Key="ctBtn" TargetType="{x:Type Button}">
        <Label Background="Green" Content="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}"/>
    </ControlTemplate>

    <Style x:Key="stBtn2" TargetType="{x:Type Button}">
        <Setter Property="Template"
            Value="{StaticResource ctBtn}" />
    </Style> 
<Window.Resources>

Then by defining the Button:

<Button Content="Hi!" Style="{StaticResource stBtn2}" Foreground="Red" >

===============

Edit2

So the general idea is that you can define a TemplateBinding for the properties of the elements in your template. So for example,you have an Ellipse in your template:

<Ellipse Fill="{TemplateBinding BorderBrush}" /> 

This defines that the Fill property of your Ellipse comes from the BorderBrush of your Button (Assuming that the template is targeting a Button)

Accordingly, you can put a Label in your Template, and set a TemplateBinding for its Forground and FontWeight property.

<Label Foreground="{TemplateBinding Foreground}" />   
0
votes

First, for this issue to be reproduced, Styles need to be set within a ResourceDictionary which is then added to Application.Resources (precisellyTextBlock global style). Setting Styles within for example Window.Resources will not reproduce the issue.

Global TextBlock Style is applied to the TextBlock created by ConentPresenter

As noticed in the question, the issue is that the global (keyless) Style for TextBlock is applied to the TextBlock created by ContentPresenter when it concludes the content to display is a string. For some reason this doesn't happen when that Style is defined within Window.Resources. As it turns out, there is more to this than just "controls are looking for their styles within their parent's resources".

ControlTemplate is a boundary for elements not deriving from Control class

For TextBlock (which doesn't derive from Control class, but from UIElement) within ControlTemplate, it means that wpf will not look for it's implicit Style beyond it's templated parent. So it won't look for implicit Style within it's parent's resources, it will apply application level implicit Style found within Application.Resources.

This is by design (hardcoded into FrameworkElement if you will), and the reason is exactly to prevent issues like this one. Let's say you're creating a specific Button design (as you are) and you want all buttons in your application to use that design, even buttons within other ControlTemplates. Well, they can, as Button does derive from Control. On the other hand, you don't want all controls that use TextBlock to render text, to apply the implicit TextBlock Style. You will hit the same issue with ComboBox, Label... as they all use TextBlock, not just Button.

So the conclusion is: do not define global Style for elements which don't derive from Control class within Application.Resources, unless you are 100% sure that is what you want (move it to Window.Resources for example). Or, to quote a comment I found in source code for MahApps.Metro UI library: "never ever make a default Style for TextBlock in App.xaml!!!". You could use some solution to style the TextBlock within your Button's ControlTemplate, but then you'll have to do it for Label, ComboBox, etc... So, just don't.