Styling a Textblock autogenerated in a ContentPresenter - wpf

As I saw, a lot of people ran into this exact problem but I can't understand why my case is not working and it is starting to drive me crazy.
Context: I have a DataGrid which is to be colored according to the values of each cell. Hence, I have a dynamic style resolving the actual template to be used for each cell. Backgrounds now work accordingly.
New problem: when I have a dark background, I want the font color to be white and the font weight to be bold so the text is correctly readable. And... I can't style it correctly.
I read some Stackoverflow posts about that:
This one fits my problem but doesn't provide me any working solution
This one is also clear and detail but... duh
This is almost the same problem as me but... Solution does not work
Here is what I tried so far:
<!-- Green template-->
<ControlTemplate x:Key="Green" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="Green">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ContentPresenter.Resources>
<Style BasedOn="{StaticResource BoldCellStyle}" TargetType="{x:Type TextBlock}" />
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</ControlTemplate>
Does not work. Background is green, but text stays in black & not bold.
BTW, the BoldCellStyle is as easy as it can be:
<Style x:Key="BoldCellStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="White" />
</Style>
Okay. Second try (which is a real stupid one but well...)
<!-- Green template -->
<ControlTemplate x:Key="Green" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="Green">
<ContentPresenter
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ContentPresenter.Resources>
<Style x:Key="BoldCellStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="White" />
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</ControlTemplate>
Doesn't work either.
Then, I tried to play with the ContentPresenter's properties:
<!-- Green template -->
<ControlTemplate x:Key="Green" TargetType="{x:Type tk:DataGridCell}">
<Grid Background="Green">
<ContentPresenter TextElement.FontWeight="Bold" TextElement.Foreground="White" TextBlock.Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
And... As you can expect, this does not even work.
Intrigued, I used Snoop to browse all the components of my interface.
In the first two cases, Snoop actually shows me that each cell is a Grid with a ContentPresenter containing a TextBlock and the actual Style but... The TextBlock's properties do not apply and FontWeight is still normal.
Last case, even more shocking, I can see that snoop shows me that we actually have a ContentPresenter with the right properties (ie TextElement.FontWeight="Bold"), but the autogenerated TextBlock under is - still - not styled.
I can't get what am I missing here. I tried as you can see almost all I could possibly do here, and the TextBlocks keep being non-formatted.
Any idea here? Thanks again!

The DataGridColumns that derive from DataGridBoundColumn (all except DataGridTemplateColumn) has a property ElementStyle that is applied to the TextBlock when it is created. For e.g. DataGridTextColumn It looks like this
static DataGridTextColumn()
{
ElementStyleProperty.OverrideMetadata(typeof(DataGridTextColumn),
new FrameworkPropertyMetadata(DefaultElementStyle));
// ...
}
It overrides the metadata for ElementStyle and provides a new default value, DefaultElementStyle, which basically just sets the default margin for the TextBlock.
public static Style DefaultElementStyle
{
get
{
if (_defaultElementStyle == null)
{
Style style = new Style(typeof(TextBlock));
// Use the same margin used on the TextBox to provide space for the caret
style.Setters.Add(new Setter(TextBlock.MarginProperty, new Thickness(2.0, 0.0, 2.0, 0.0)));
style.Seal();
_defaultElementStyle = style;
}
return _defaultElementStyle;
}
}
This style is set in code everytime a new DataGridCell is created with element.Style = style; and this is overriding the Style you are trying to set, even if you try to set it implicitly.
As far as I know, you'll have to repeat this for your columns
<DataGridTextColumn Header="Column 1" ElementStyle="{StaticResource BoldCellStyle}" .../>
<DataGridTextColumn Header="Column 2" ElementStyle="{StaticResource BoldCellStyle}" .../>

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.

Stop child from inheriting parent Style in TabControls

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.

Why does data binding my chart series color fail?

I am drawing simple line charts using the WPF toolkit. My goal is to set the line color of my series via Data Binding. This succeeds only partially. The question is: why?
Setup
Namespaces:
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit" x:Class="WpfApplication3.MainWindow"
xmlns:media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
Chart:
<chartingToolkit:Chart x:Name="chart">
<chartingToolkit:LineSeries x:Name="seriesEntries" IndependentValueBinding="{Binding Key}" DependentValueBinding="{Binding Value}" DataPointStyle="{StaticResource CommonLineSeriesDataPoint}">
<chartingToolkit:LineSeries.Tag>
<media:Brush>Green</media:Brush>
</chartingToolkit:LineSeries.Tag>
</chartingToolkit:LineSeries>
</chartingToolkit:Chart>
Ignore the Tag for now, it will be relevant later.
Notice the chart has a custom data point style, CommonLineSeriesDataPoint:
<Style x:Key="CommonLineSeriesDataPoint" TargetType="chartingToolkit:LineDataPoint">
<Setter Property="Background">
<Setter.Value>
<media:Brush>Red</media:Brush>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="chartingToolkit:LineSeries">
<Setter Property="DataPointStyle" Value="{StaticResource CommonLineSeriesDataPoint}" />
</Style>
As expected, this colors my line series red:
Breaking Change
Now I want to data bind my data point background. I make only one change. Instead of specifying the background brush directly, I bind it to the Tag property of my LineSeries, which is also a brush (see previous LineSeries declaration, it's a green one).
<Style x:Key="CommonLineSeriesDataPoint" TargetType="chartingToolkit:LineDataPoint">
<Setter Property="Background">
<Setter.Value>
<Binding Path="Tag" RelativeSource="{RelativeSource AncestorType={x:Type chartingToolkit:LineSeries}}" />
</Setter.Value>
</Setter>
</Style>
<Style TargetType="chartingToolkit:LineSeries">
<Setter Property="DataPointStyle" Value="{StaticResource CommonLineSeriesDataPoint}" />
</Style>
The result is this:
So the dots are green. But the line is gone.
My expectation is to see a green line as well! Where is it?
I found a solution after digging in the WPF Toolkit Sources.
Turns out the Stroke property of the series' Polyline is bound to a Background property via TemplateBinding. I suspect this doesn't go well with my try binding the Background property itself.
This answer on SO suggests that TemplateBinding is evaluated at compile time. So let's get rid of the TemplateBinding and bind the Stroke property directly to the Tag of my LineSeries (remember: the Tag contains the green brush).
From the WPF Toolkit Source \Source\DataVisualization\Themes\generic.xaml I copied part of the style definition for the LineSeries and added it to my ResourceDictionary:
<Style x:Key="CommonLineSeries" TargetType="chartingToolkit:LineSeries" BasedOn="{StaticResource {x:Type chartingToolkit:LineSeries}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="chartingToolkit:LineSeries">
<Canvas x:Name="PlotArea">
<Polyline Points="{TemplateBinding Points}" Stroke="{Binding Tag, RelativeSource={RelativeSource AncestorType={x:Type chartingToolkit:LineSeries}}}" Style="{TemplateBinding PolylineStyle}"/>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
(If interested you can search for <!-- charting:LineSeries --> in generic.xaml to find the source I copied from.)
The only thing I modified is the binding of Stroke. Here I use the same binding I used for my data points.
Last thing to do: tell the LineSeries to use this style:
<chartingToolkit:LineSeries x:Name="seriesEntries" IndependentValueBinding="{Binding Key}" DependentValueBinding="{Binding Value}" DataPointStyle="{StaticResource CommonLineSeriesDataPoint}" Style="{StaticResource CommonLineSeries}">
And lo and behold, it works. The line is back and it's green:
(If you look closely you see that the legend entry for the series still has the wrong color. But I assume the solution will be quite similar to the above.)

WPF shared property

I have a question which I guess it's some basic knowledge which I missing in WPF.
I set default width (generix.XML) to Textbox with some Minim width for the textbox
<Style TargetType="{x:Type TextBox}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="MinWidth" Value="50"/>
</Style>
I have two deferent controls which holds text box. Both Textboxes has same width..
I which to add some property to one of the controls which will declare the width of the textbox, and will override its width declaration, in a way that the textbox will 'find' to this property.
here is some drawing describes my requirement:
Update:
I just figure out that I didn't described one more importing thing.
I Have some DataTemplate which uses the textbox. As I wrote above, I have two controls which have the same DataType (MyData) I also created DateTemplate to display MyData. I would like that each control will display the textbox (from the datatemple) with different width.
update 2:
here is some more code
1- The dataTemplate to my data where is using textbox
<DataTemplate DataType="{x:Type ml:MyData}">
<Border BorderBrush="Transparent" ClipToBounds="True" Style="{StaticResource errorBorder}">
<TextBox Text="{Binding MyText}"/>
</Border>
</DataTemplate>
2- the way I used the datatemplate which uses the Textbox.
<ContentPresenter Grid.Column="1" Margin="10,1,10,1" HorizontalAlignment="Left" Content="{Binding}" />
This contentPresentor is been displayed in two diffrent controls. and as I wrote before, I would like that each control will display the textbox in diferent width
It's look like I miss some basic knloage (attached proerty? logic/visual tree?).
Thanks, Leon
Good question, the main idea in DataTemplate is that you have specific graphical representation for some data. You can read more about it in MSDN.
If you want to customize your TextBox, and have it different properties inside different UserControls, you might want to use ControlTemplate.
The thing is that if you want to control properties of specific control (in this case TextBox with some border) you should use ControlTemplate.
Your XAML should look something like:
<ControlTemplate TargetType="{x:Type TextBox}">
<--! define the ControlTemplate here with some Width property-->
<ControlTemplate>
and the Control which use it will have TextBox (as you defined it, with Border):
<TextBox Grid.Column="1" Margin="10,1,10,1" HorizontalAlignment="Left" Content="{Binding}" Width="50"/>

Underline the implicit Textblock created in Silverlight for a ContentPresenter when Content is a string?

I am trying to create a template for a content control such as Button or HeaderedContentControl etc. where the text is underlined.
I just want to underline the text when Content="This text is underlined" is specified.
It must continue to work as normal if Content is another UIElement.
Most posts asking this same question are satisfied with modifying the template to only work for a string as content. Scott Gu has a good article about styling buttons but doesn't address this issue.
The following sample will work if you actually pass in Content as an instance of type TextBlock but not as a string. Surely the visual tree has a TextBlock so it should style it. Perhaps this is a Sivlerlight limitation.
This example shows black text and big red text when I want it to display both as big red text.
<navigation:Page.Resources>
<Style TargetType="TextBlock" x:Key="style123">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="72"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="TextDecorations" Value="Underline"/>
</Style>
</navigation:Page.Resources>
<StackPanel>
<!-- This doesn't work and shows black text -->
<ContentPresenter Content="Small black text">
<ContentPresenter.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource style123}"/>
</ContentPresenter.Resources>
</ContentPresenter>
<!-- This works and shows red text -->
<ContentPresenter>
<ContentPresenter.Content>
<TextBlock Text="This is big red text"/>
</ContentPresenter.Content>
<ContentPresenter.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource style123}"/>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
You could subclass whatever actual ContentControl (i.e. Button) you are using and override OnContentChanged in order to reset the Content property to an underlined TextBlock if the newContent is a string. In the case that the newContent is not a string it would perform in the usual way.
public class UnderlineButton : Button
{
protected override void OnContentChanged(object oldContent, object newContent)
{
if (newContent is string)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = newContent as string;
textBlock.TextDecorations = TextDecorations.Underline;
this.Content = textBlock;
}
base.OnContentChanged(oldContent, newContent);
}
}
It's kind of annoying to subclass just to accomplish this but it avoids messy style templates and subclassing ContentPresenter.
Try this example, using a DataTemplate to custom-render string content (I've just set the background to red):
<ContentControl Content="{Binding YourData}" >
<ContentControl.Resources>
<DataTemplate DataType="{x:Type s:String}">
<TextBlock Text="{Binding}" Background="Red" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
EDIT: just as a note, you could pull this out into a ContentControl style rather than applying it inline each time, if you need better reusability...

Resources