Stop child from inheriting parent Style in TabControls - wpf

In my WPF application i have a TabControl that i am binding to a style i created:
On my View:
<TabControl Grid.Row="6" Style="{DynamicResource SideBarTabControl}">
On a separate ResourceDictionary:
<Style x:Key="SideBarTabControl" TargetType="{x:Type TabControl}" BasedOn="{StaticResource {x:Type TabControl}}" >
<Setter Property="FontSize" Value="{DynamicResource TitleFontSize}"/>
</Style>
So far so good, things work as expected. The problem is that now all the children of this TabControl, such as a ListView inside a TabItem, is also getting the same FontSize as the TabControl, instead of the default.
I thought that by specifying TargetType="{x:Type TabControl}" i would stop the style from being applied to children of different types. What i'm looking for is to actually stop it from affecting EVERYTHING BUT the component that explicitly inherited the style. So how can this be done? I think i am missing something simple...
If i override the font size in my ListView it works, but this means i have to do it for every child, which might become very cumbersome.
I have read this and other questions but i can't find the answer i'm looking for:
Is it possible to set a style in XAML that selectively affects controls?

This is working for me. The part that's doing the work is TabControl.ItemContainerStyle. It applies a font size only to the header content.
<TabControl>
<TabControl.ItemContainerStyle>
<Style
TargetType="TabItem"
BasedOn="{StaticResource {x:Type TabItem}}"
>
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<ContentControl
TextElement.FontSize="20"
Content="{Binding Header, RelativeSource={RelativeSource AncestorType=TabItem}}"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabItem Header="Foo">
<Label Content="Bar" />
</TabItem>
<TabItem Header="Baz">
<Label Content="Bar" />
</TabItem>
</TabControl>

You cannot stop it, it's not the style causing this unwanted trickle-down effect you want rid of; it's just how WPF controls work.
What you will have to do to stop this is write another style for your tab items to intercept the one being inherited from the TabControl.
I suggest writing this style inside your existing TabControl style, inside the Style.Resources tag like so:
<Style x:Key="SideBarTabControl" TargetType="{x:Type TabControl}" BasedOn="{StaticResource {x:Type TabControl}}" >
<Style.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="FontSize" Value="9001"/>
<!-- Any other setters you want for TabItems -->
</Style>
</Style.Resources>
<Setter Property="FontSize" Value="{DynamicResource TitleFontSize}"/>
</Style>
By making a style inside your other style's resources, it will be carried with it, and by not specifying any x:Key for the TabItem style - it will apply it to any TabItem not ordered to have a specific style, becoming the default style for any TabItem you make inside the TabControl now.

Related

Override property of custom style

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:
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.
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>
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).
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}" />
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.

WPF Stackpanel spacing between custom controls

how is it possible to get a space between some custom controls inside a stackpanel? I did it right before with a Textbox, Button, and so on, but i cannot do it with a custom control.
That's the code i've got so far
<Grid>
<StackPanel x:Name="spTasks" CanVerticallyScroll="True">
<StackPanel.Resources>
<Style TargetType="local:SmartTaskOverview">
<Setter Property="Margin" Value="50,50,50,50" />
</Style>
</StackPanel.Resources>
</StackPanel>
</Grid>
Thanks for your help
FrameworkElements and their sub-classes don't just look for a resource using the controls type, they use the value of DefaultStyleKey. It's common practice for most sub-classes of Control (and some other FrameworkElements) to override the default value of this dependency property in the static constructor to be the type of the control, but sub-classes of UserControl usually don't bother.
static Foo()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Foo), new FrameworkPropertyMetadata(typeof(Foor));
}
If you didn't do this in your SmartTaskOverview then it will be looking for its default style using typeof(UserControl) as the resource key and not typeof(SmartTaskOverview).
Note: The UserControl will require a control template to show its children, this is normally provided by the default style for UserControl but by changing the key it will find your default style instead. To resolve this, just base your style on the UserControl style.
<Style TargetType="local:SmartTaskOverview" BasedOn="{StaticResource {x:Type UserControl}}">
<Setter Property="Margin" Value="50,50,50,50" />
</Style>
Alternatively you could provide a simple template yourself.
<Style TargetType="local:SmartTaskOverview">
<Setter Property="Margin" Value="50,50,50,50" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SmartTaskOverview}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

WPF ControlTemplate breaks style

The stuff that does work
I need to style controls of a certain type that are children of a StackPanel. I'm using:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">...</Style>
</StackPanel.Resources>
<TextBlock ...>
...
</StackPanel>
And this works fine! Each TextBlock looks to the resources of it's parent (the StackPanel) to find out how it should be styled. It doesn't matter how far down you nest the TextBlock down a StackPanel... if it doesn't find a style in its direct parent, it will look at its parent's parent and so on, until it finds something (in this case, the style that was defined in ).
The stuff that doesn't work
I ran into a problem when I nested a TextBlock inside a ContentControl, which had a Template (see code below). The ControlTemplate seems to disrupt the way a TextBlock retrieves its style from its parents, grandparents,...
The use of a ControlTemplate effectively seems to knock out cold the TextBlock's means of finding its rightful style (the one in StackPanel.Resources). When it encounters a ControlTemplate, it stops looking for its style in the resources up the tree, and instead defaults to the style in MergedDictionaries of the Application itself.
<StackPanel Orientation="Vertical" Background="LightGray">
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Green" />
</Style>
</StackPanel.Resources>
<TextBlock Text="plain and simple in stackpanel, green" />
<ContentControl>
<TextBlock Text="inside ContentControl, still green" />
</ContentControl>
<ContentControl>
<ContentControl.Template>
<ControlTemplate TargetType="{x:Type ContentControl}">
<StackPanel Orientation="Vertical">
<ContentPresenter />
<TextBlock Text="how come this one - placed in the template - is not green?" />
</StackPanel>
</ControlTemplate>
</ContentControl.Template>
<TextBlock Text="inside ContentControl with a template, this one is green as well" />
</ContentControl>
</StackPanel>
Is there a way - besides duplicating the Style in StackPanel.Resources to ControlTemplate.Resources - to make the TextBlock inside that ControlTemplate find the defined style?
Thanks...
WPF considers ControlTemplates to be a boundry, and will not apply implicit styles (styles without an x:Key) inside of templates.
But there is one exception to this rule: anything that inherits from Control will apply implicit styles.
So you could use a Label instead of a TextBlock, and it would apply the implicit style defined further up your XAML hierarchy, however since TextBlock inherits from FrameworkElement instead of Control, it won't apply the implicit style automatically and you have to add it manually.
My most common way to get around this is to add an implicit style in the ControlTemplate.Resources that is BasedOn the existing implicit TextBlock style
<ControlTemplate.Resources>
<Style TargetType="{x:Type TextBlock}"
BasedOn="{StaticResource {x:Type TextBlock}}" />
<ControlTemplate.Resources>
Other common ways of getting around this are:
Place the implicit style in <Application.Resources>. Styles placed here will apply to your entire application, regardless of template boundaries. Be careful with this though, as it will apply the style to TextBlocks inside of other controls as well, like Buttons or ComboBoxes
<Application.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Green" />
</Style>
</Application.Resources>
Use a Label instead of a TextBlock since it's inherited from Control, so will apply implicit Styles defined outside the ControlTemplate
Give the base style an x:Key and use it as the base style for an implicit TextBlock styles inside the ControlTemplate. It's pretty much the same as the top solution, however it's used for base styles that have an x:Key attribute
<Style x:Key="BaseTextBlockStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Green" />
</Style>
...
<ControlTemplate.Resources>
<Style TargetType="{x:Type TextBlock}"
BasedOn="{StaticResource BaseTextBlockStyle}" />
<ControlTemplate.Resources>

How to define all textblock elements the same color

We are using global styles definitions for most of the types. We define then in the app.xaml file. When using TextBlock it is a problem to define a foreground color because it changes all the controls using TextBlock (Button's content color for example).
How can we define a global style which will act only on specific TextBlock usages?
current problematic usage:
<Style TargetType={x:Type TextBlock}>
<Setter Property="Foreground" Value="Red"/>
</Style>
Since I don't think there is a way to differentiate “your” TextBlocks and those that are part of other controls, your options are quite limited.
You could create named Style and add Style="{StaticResource coloredTextBlock}" or Foreground="{StaticResource textBlockColor}" to all TextBlocks. This would be quite tedious and non-DRY.
You could create your own type that inherits from TextBlock and style that. This has some of the disadvantages of the above solution (you have to remember doing that). But it has much less repetition.
This is because 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 TextBlock at Application level, then it will be applied to these TextBlock within ControlControls.
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.
Add this to your Application resources and it should work for you -
<DataTemplate DataType="{x:Type system:String}">
<TextBlock Text="{Binding}">
<TextBlock.Resources>
<Style TargetType="{x:Type TextBlock}"/>
</TextBlock.Resources>
</TextBlock>
</DataTemplate>
Declare a namespace in your xaml, if not referred already -
xmlns:system="clr-namespace:System;assembly=mscorlib"
EDIT : Check this sample where its working..
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Red"/>
</Style>
<DataTemplate DataType="{x:Type system:String}">
<TextBlock Text="{Binding}">
<TextBlock.Resources>
<Style TargetType="{x:Type TextBlock}"/>
</TextBlock.Resources>
</TextBlock>
</DataTemplate>
<Style TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="Yellow"/>
</Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="Blue"/>
</Style>
Just provide a x:key in the style, like:
<Style x:Key="stRedTextBlock" TargetType={x:Type TextBlock}>
<Setter Property="Foreground" Value="Red"/>
</Style>
and mention the key in the TextBlock control style, where ever you require this particular TextBlock style, like:
<TextBlock Name="textBlock1" Style="{StaticResource stRedTextBlock}" />

Why does x:Key unapply my TabItem Style

I am trying to make a custom style for a TabItem Header. I got it to work by accident.
this fails:
<Style TargetType="{x:Type TabItem}" x:Name="TabHeader3" x:Key="test">
but this works
<Style TargetType="{x:Type TabItem}" x:Name="TabHeader3">
What's going on?
The first Style you have defined is an "explicit" Style, so you must explicitly use it like so:
<TabItem Style="{StaticResource test}" />
The second Style you have defined is an "implicit" Style. So it will be applied to all TabItem controls below it in the visual/logical tree, or to all TabItem controls if it's defined in the application resources.
Your second Style is equivalent to:
<Style TargetType="{x:Type TabItem}" x:Name="TabHeader3" x:Key="{x:Type TabItem}">
So the key is the Type of the object to which it should be applied.
If a TabItem has a Style explicitly defined (like I show above), then any implicit Styles will not be used. Also, if you have two implicit Styles defined, then the closest one wins. So here:
<Window>
<Window.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Background" Value="Red" />
</Style>
</Window.Resources>
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Background" Value="Blue" />
</Style>
</Grid.Resources>
...
<TabItem ... />
...
</Grid>
</Window>
The Blue Style will take precedence over the Red Style.
Finally, you generally don't need to include x:Name on your Styles.
If you add the style to a resource dictionary without a key then the style gets applied to all TabItems that are within the scope of the resource dictionary by default. If you add a Key to the style then you need to manually set the Style

Resources