Custom control's properties not changing when used in a template - wpf

I have created a custom control (inherits Control) that exposes a single enumerated DependencyProperty. The default control template renders differently based on the selected value for the property using Triggers to turn elements on/off. The control works great when placed directly into a UserControl for viewing in the UI. However, the point of the control is to exist as part of a large composite control so it is also used in the ControlTemplate of another custom control. When I do so, changes to the dependency property are not recognized by the control. I verified this by adding a PropertyChangedCallback to the dependency property and setting a break point which is never hit.
For example, when I use "CustomControl" in a template like this:
<ControlTemplate>
<my:CustomControl EnumProperty="EnumValue" />
</ControlTemplate>
The EnumProperty (which is a DependencyProperty) is not changed to "EnumValue" and it remains the default value. And, as I said, a breakpoint in the PropertyChangedCallback for the DP is never called.
What am I missing?
UPDATE
Here is a cleansed version of my control:
public class CustomControl : Control
{
static CustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)));
}
public StandardIcon()
: base()
{
BorderType = BorderType.None;
}
public static readonly DependencyProperty BorderTypeProperty = DependencyProperty.Register("BorderType", typeof(BorderType), typeof(CustomControl), new PropertyMetadata(BorderType.None));
public BorderType BorderType
{
get { return (BorderType)GetValue(BorderTypeProperty); }
set { SetValue(BorderTypeProperty, value); }
}
}
<Style TargetType="{x:Type local:CustomControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<Border x:Name="Rectangle"
BorderBrush="{TemplateBinding Foreground}"
BorderThickness="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ContentPresenter ContentSource="Content" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="BorderType" Value="Rectangle">
<Setter Property="BorderThickness" TargetName="Rectangle" Value="2" />
</Trigger>
<Trigger Property="BorderType" Value="RoundedRectangle">
<Setter Property="BorderThickness" TargetName="Rectangle" Value="2" />
<Setter Property="CornerRadius" TargetName="Rectangle" Value="5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And this is how it is being used within another control (notice that it is in a DataTemplate and not a ControlTemplate as I originally indicated).
<Style TargetType="{x:Type local:OtherControl}">
<Setter Property="FontFamily" Value="{x:Static theme:StandardFonts.FontFamily}" />
<Setter Property="FontSize" Value="{x:Static theme:StandardFonts.FontSizeXS}" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:CustomControl BorderType="{Binding TemplatedParent.BorderType, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{Binding TemplatedParent.Foreground, RelativeSource={RelativeSource TemplatedParent}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And then it is used like this:
<controls:OtherControl Foreground="Red" BorderType="Rectangle" />
The Foreground property IS changing as expected. When I change the Foreground of the OtherControl, the Foreground of the CustomControl is changed. But the BorderType property is not being respected - it always renders with the default BorderType.None value.

The parent of your ControlTemplate needs to have something for your CustomControl to bind to. Then, you bind the CustomControl in your template to the parent.
In the following example, I'm using a Border to template a Button, which binds its BorderBrush to the Button's Background:
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderBrush="{TemplateBinding Background}" />
</ControlTemplate>
Replace Button with your "large composite control" and Border with my:CustomControl and you should be set...

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.

Binding Path Fill to Button Foreground in ContentPresenter

I have a Button Style with a Template containing a ContentPresenter, in which I am attempting to bind the Fill of a Path to the Foreground of a button:
<!-- This is inside the template of a button style -->
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
I also have a Path with no Fill set, that I can reference in the button as the content, like so:
<Button Style="{DynamicResource MyButtonStyle}" Content="{DynamicResource PathIcon}" Foreground="Blue"/>
I would expect the Path inside the button to be blue, but it isn't... it doesn't grab the foreground from the button.
How can I get the Path to bind to the color of the button?
Thank you!
P.S.:
If I put a hardcoded color in the Value (i.e. Value="Red"), the Path inside the button is red... so I know that works...
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="Red"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
Edit:
Here is the complete Style and ControlTemplate:
<Style x:Key="Button_Style" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{StaticResource White_Brush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="grid" Background="Transparent">
<ContentPresenter>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Path}">
<Setter Property="Fill" Value="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- Should affect Text as well as Paths in the Content property of the button! -->
<Setter Property="Foreground" Value="{StaticResource Black_Brush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Okay, let's order:
it doesn't grab the foreground from the button.
In styles this construction:
RelativeSource={RelativeSource AncestorType=Button}
will not work, because the Style is just the collection of setters, he does not know about control, are there, specifically about the content of the visual tree. Because RelativeSource should refer to the items above in the visual tree. For this purpose, usually using DataTemplate or ControlTemplate.
If I put a hardcoded color in the Value (i.e. Value="Red")
Yes, in this case, will be working, and always better to create the design of the form:
<SolidColorBrush x:Key="MyButtonColor" Color="Blue" />
And use it for control, like Button:
<Button Background="{StaticResource MyButtonColor}" ... />
and in Style or elsewhere:
<Setter Property="Fill" Value="{StaticResource MyButtonColor}" />
That is, it is better not to depend on the element parameters (background color, etc.) located in a visual tree, because it can:
May move to another panel (Grid, StackPanel) or UserControl
May leave from the project
And brushes in the as resources will always be in one place, changing them in this place, all the elements of their pick up. Also colors can be stored in a special data model that does not depend on the specific technical implementations (resources, variables) in which the data can come from an external source, such as the project/config settings.
If possible, it is better to avoid the use of dynamic resources due to unnecessary use of system perfomance (and in some cases memory leaks), in your cases they are not needed.
Dynamic resources are usually explicitly defined for SolidColorBrush and another species brushes, because by default they are frozen, and they not recommended changed because of the above mentioned reasons (memory leaks). More information can be found here:
Freezable Objects Overview on MSDN
Edit
As I understand it, you want to make universal Style for Button to make the contents of Path or Text (in the case of simultaneous use will be easier). As I have already mentioned above, RelativeSource should be around ControlTemplate, therefore, the Path will be in the Grid with the ContentPresenter.
To style knew, which is provided for the text or for the path, to the Tag (optional property) indicates two properties: OnlyText or OnlyPath.
To set the data for the Path, I've created a attached dependency property, and prescribed it in the ControlTemplate.
Below is a complete example:
XAML
<Window x:Class="ButtonPathHelp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ButtonPathHelp"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<SolidColorBrush x:Key="Green_Brush" Color="Green" />
<SolidColorBrush x:Key="Black_Brush" Color="Black" />
<Style x:Key="Button_Style" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{StaticResource Green_Brush}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid x:Name="grid">
<ContentPresenter x:Name="MyContent"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}" />
<Path x:Name="MyPath"
SnapsToDevicePixels="True"
Width="20"
Height="18"
Stretch="Fill"
Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
Data="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:MyDependencyClass.DataForPath)}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="{StaticResource Black_Brush}"/>
</Trigger>
<Trigger Property="Tag" Value="OnlyText">
<Setter TargetName="MyPath" Property="Visibility" Value="Collapsed" />
<Setter TargetName="MyContent" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="Tag" Value="OnlyPath">
<Setter TargetName="MyPath" Property="Visibility" Value="Visible" />
<Setter TargetName="MyContent" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<WrapPanel>
<WrapPanel.Resources>
<sys:String x:Key="Save">
F1 M 20.5833,20.5833L 55.4167,20.5833L 55.4167,55.4167L 45.9167,55.4167L 45.9167,44.3333L 30.0833,44.3333L 30.0833,
55.4167L 20.5833,55.4167L 20.5833,20.5833 Z M 33.25,55.4167L 33.25,50.6667L 39.5833,50.6667L 39.5833,55.4167L 33.25,
55.4167 Z M 26.9167,23.75L 26.9167,33.25L 49.0833,33.25L 49.0833,23.75L 26.9167,23.75 Z
</sys:String>
<sys:String x:Key="Search">
F1 M 23.4454,49.2637L 31.7739,41.1598C 30.6986,39.2983 30.4792,37.1377 30.4792,34.8333C 30.4792,27.8377 35.7544,
22.1667 42.75,22.1667C 49.7456,22.1667 55.4167,27.8377 55.4167,34.8333C 55.4167,41.8289 49.7456,47.1042 42.75,
47.1042C 40.5639,47.1042 38.5072,46.9462 36.7125,45.9713L 28.3196,54.1379C 27.0829,55.3746 24.6821,55.3746 23.4454,
54.1379C 22.2088,52.9013 22.2088,50.5004 23.4454,49.2637 Z M 42.75,26.9167C 38.3777,26.9167 34.8333,30.4611 34.8333,
34.8333C 34.8333,39.2056 38.3777,42.75 42.75,42.75C 47.1222,42.75 50.6667,39.2056 50.6667,34.8333C 50.6667,
30.4611 47.1222,26.9167 42.75,26.9167 Z
</sys:String>
</WrapPanel.Resources>
<Button Name="SaveButton"
Style="{StaticResource Button_Style}"
Tag="OnlyPath"
local:MyDependencyClass.DataForPath="{StaticResource Save}"
Margin="10" />
<Button Name="JustText"
Style="{StaticResource Button_Style}"
Tag="OnlyText"
Content="Just Text"
Margin="10" />
<Button Name="SearchButton"
Style="{StaticResource Button_Style}"
Tag="OnlyPath"
local:MyDependencyClass.DataForPath="{StaticResource Search}"
Margin="10" />
</WrapPanel>
</Window>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MyDependencyClass : DependencyObject
{
#region IsCheckedOnDataProperty
public static readonly DependencyProperty DataForPathProperty;
public static void SetDataForPath(DependencyObject DepObject, string value)
{
DepObject.SetValue(DataForPathProperty, value);
}
public static string GetDataForPath(DependencyObject DepObject)
{
return (string)DepObject.GetValue(DataForPathProperty);
}
#endregion
static MyDependencyClass()
{
PropertyMetadata MyPropertyMetadata = new PropertyMetadata(String.Empty);
DataForPathProperty = DependencyProperty.RegisterAttached("DataForPath",
typeof(string),
typeof(MyDependencyClass),
MyPropertyMetadata);
}
}
Note: In the Style I have not used TemplateBinding for attached property, because TemplateBinding doesn’t work outside a template or outside its VisualTree property, so you can’t even use TemplateBinding inside a template’s trigger. Therefore, we must use the construction {RelativeSource TemplatedParent} and a Path equal to the dependency property whose value you want to retrieve.
Output
To download the entire example please follow this link.
I stumbled across simillar problem but was wondering how to get to the 'Foreground Colour' of the Button in its DISABLED state (to have correct colour of my drawing). Here is a finally simple sollution. No templates, No styles, no code, nothing at all. Just the right relative binding :-) :
<StackPanel Orientation="Horizontal">
<Button Height="22" IsEnabled="False">
<Polygon Points="4,0 4,5 5,5 2.5,10 0,5 1,5 1,0 "
Fill="{Binding (TextElement.Foreground), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}">
<Polygon.LayoutTransform>
<RotateTransform Angle="90"></RotateTransform>
</Polygon.LayoutTransform>
</Polygon>
</Button>
<Button Height="22" IsEnabled="True">
<Polygon Points="4,0 4,5 5,5 2.5,10 0,5 1,5 1,0 "
Fill="{Binding (TextElement.Foreground), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}">
<Polygon.LayoutTransform>
<RotateTransform Angle="180"></RotateTransform>
</Polygon.LayoutTransform>
</Polygon>
</Button>
</StackPanel>

WPF change style (with trigger) programmatically

I need to change the background of the selected item in a list view, programmatically.
The style currently looks like this (as default)
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="images/selection-large.png"/>
</Setter.Value>
</Setter>
</Trigger>
I need to be able to change that background image of the selected item programmatically.
I have attempted to do this myself however i am not really sure how to go about it.
I did notice that the listview has a styles property but other than that i am completely stumped.
I am using VB.net
Edit
Would something like this work? and if so, what am i doing wrong? My listview is completely invisible with this code.
Dim trigger As New Trigger()
trigger.Property = ListViewItem.IsSelectedProperty
trigger.Value = True
trigger.Setters.Add(New Setter(ListViewItem.BackgroundProperty, Brushes.Pink))
mylistview.Style.Triggers.Add(trigger)
mylistview.ItemContainerStyle = Style
Bind your IsSelected property to some bool value in the ViewModel and toggle that.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
Item ViewModel:
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
NotifyPropertyChange("IsSelected");
}
}
In WPF you don't manipulate UI elements in procedural code, instead you use DataBinding and manipulate your models / ViewModels' properties
As an aside, I recommend against using PNG or other bitmap based stuff for regular UI elements in WPF.
Instead of that you should create your UI using WPF elements which are vector-based and thus resolution independent.
You can do this within the XAML, Try this:
<Window.Resources>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="images/selection-large.png"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
I was able to finally work it out..
All i had to do was make another style based on the first one and give them both different x:keys.. Then i was able to change styles like this.
mylistview.ItemContainerStyle = DirectCast(FindResource("XKeyName"), Style)

Why does an implicit TextBlock style get applied when binding Label.Content to a non-string, but not a string?

I was looking at this question, and discovered that binding Label.Content to a non-string value will apply an implicit TextBlock style, however binding to a string does not.
Here's some sample code to reproduce the problem:
<Window.Resources>
<Style TargetType="Label">
<Setter Property="FontSize" Value="26"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="26"/>
<Setter Property="Margin" Value="10"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding SomeString}" Background="Red"/>
<Label Content="{Binding SomeDecimal}" Background="Green"/>
</StackPanel>
</Grid>
Where the code for the bound values are
SomeDecimal = 50;
SomeString = SomeDecimal.ToString();
And the end result looks like this, with the Margin property from the implicit TextBlock style getting applied to the Label bound to a non-string only:
Both labels get rendered as
<Label>
<Border>
<ContentPresenter>
<TextBlock />
</ContentPresenter>
</Border>
</Label>
When I check out the VisualTree with Snoop, I can see that it looks exactly the same for both elements, except the 2nd TextBlock applies the Margin from the implicit style, while the first does not.
I've used Blend to pull out a copy of the default Label Template, but don't see anything strange there, and when I apply the template to both my labels, the same thing happens.
<Label.Template>
<ControlTemplate TargetType="{x:Type Label}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True">
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Label.Template>
It should also be noted that setting a default ContentTemplate to a TextBlock does make both items render without the implicit style, so it must have something to do with when WPF tries to render a non-string value as part of the UI.
<Window.Resources>
<Style TargetType="Label">
<Setter Property="FontSize" Value="26"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="TemplatedStyle" TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding }"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="26"/>
<Setter Property="Margin" Value="10"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding SomeString}" Background="Red"/>
<Label Content="{Binding SomeDecimal}" Background="Green"/>
<Label Content="{Binding SomeString}" Background="Red"
Style="{StaticResource TemplatedStyle}"/>
<Label Content="{Binding SomeDecimal}" Background="Green"
Style="{StaticResource TemplatedStyle}"/>
</StackPanel>
</Grid>
What is the logic that causes a non-string inserted into the UI to be drawn using an implicit TextBlock style, but a string inserted into the UI does not? And where does this occur at?
EDIT: (maybe move this to the bottom?)
And I poked a bit more - and I think I got to the crux of the problem (w/ emphasis on 'I think')
Put this into some Button1_Click or something (again, we need to go 'lazy' on this - as we need the visual tree constructed - we cannot do it on 'Loaded' as we just made the templates - this required better initialization technique true, but it's just a test so who cares)
void Button_Click(object sender, EventArgs e)
{
var insideTextBlock = FindVisualChild<TextBlock>(_labelString);
var value = insideTextBlock.GetProperty<bool>("HasImplicitStyleFromResources"); // false
value = insideTextBlock.GetProperty<bool>("ShouldLookupImplicitStyles"); // true
var boundaryElement = insideTextBlock.TemplatedParent; // ContentPresenter and != null
insideTextBlock = FindVisualChild<TextBlock>(_labelDecimal);
value = insideTextBlock.GetProperty<bool>("HasImplicitStyleFromResources"); // true
value = insideTextBlock.GetProperty<bool>("ShouldLookupImplicitStyles"); // true
boundaryElement = insideTextBlock.TemplatedParent; // == null !!
As mentioned here Implicit styles in Application.Resources vs Window.Resources?
The FindImplicitStyleResource (in FrameworkElement) uses something like...
boundaryElement = fe.TemplatedParent;
And seems that if there is no TemplatedParent (and due to the ways
the TextBlock is constructed within the DefaultTemplate) - there
are no 'boundaries' set - and search for implicit resources / styles -
propagates all the way.
Original Answer: (read this first if you just arrived)
(#dowhilefor and #Jehof already touched on the main things)
I'm not sure this is an 'answer' as such - it's still a guess work - but I needed more space to explain what I think is going on.
You can find the 'ContentPresenter source' code on the web - it's easier than using reflector - just 'google' for it, I'm not posting it here for the obvious reasons :)
It's about the ContentTemplate that is chosen for the ContentPresenter (and in this order)...
ContentTemplate // if defined
ContentTemplateSelector // if defined
FindResource // for typeof(Content) - eg if defined for sys:Decimal takes that one
DefaultTemplate used internally by the presenter
...specific templates are chosen based on typeof(Content)
And indeed it doesn't have anything to do with the Label but any ContentControl or control template that uses ContentPresenter. Or you could bind to resource etc.
Here is a repro of what's going on inside - my goal was to reproduce similar behavior for 'strings' or any type of content.
In XAML just 'name' the labels (and it isn't a typo, a deliberately put strings in both to level the playing field sort of)...
<Label Name="_labelString" Content="{Binding SomeString}" Background="Red"/>
<Label Name="_labelDecimal" Content="{Binding SomeString}" Background="Green"/>
And from code behind (the minimal code that sort of mimics what presenter does):
note: I did it on Loaded as I needed access to the presenter implicitly created
void Window1_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(TextBlock));
factory.SetValue(TextBlock.TextProperty, new TemplateBindingExtension(ContentProperty));
var presenterString = FindVisualChild<ContentPresenter>(_labelString);
presenterString.ContentTemplate = new DataTemplate() { VisualTree = factory };
// return;
var presenterDecimal = FindVisualChild<ContentPresenter>(_labelDecimal);
presenterDecimal.ContentTemplate = new DataTemplate();
// just to avoid the 'default' template kicking in
// this is what 'default template' does actually, the gist of it
TextBlock textBlock = new TextBlock();
presenterDecimal.SetProperty(typeof(FrameworkElement), "TemplateChild", textBlock);
textBlock.Text = presenterDecimal.Content.ToString();
First part (for _labelString) does what 'text' template does for strings.
If you return right after that - you'll get the two same looking boxes, no implicit template.
Second part (for _labelDecimal) mimics the 'default template' which is invoked for the 'decimal'.
End result should behave the same as the original example. We
constructed the templates as for the string and decimal - but we
can put anything in the content (if it makes sense of course).
As to why - my guess is something like this (though far from certain - somebody will jump in with something more sensible I guess)...
As per this link FrameworkElementFactory
This class is a deprecated way to programmatically create templates,
which are subclasses of FrameworkTemplate such as ControlTemplate or
DataTemplate; not all of the template functionality is available when
you create a template using this class. The recommended way to
programmatically create a template is to load XAML from a string or a
memory stream using the Load method of the XamlReader class.
And I'm guessing it doesn't invoke any defined styles for the TextBlock.
While the 'other template' (default template) - actually constructs the TextBlock and somewhere along those lines - it actually picks up the implicit style.
Frankly, that's as much as I was able to conclude, short of going through the entire WPF 'internals' and how/where actually styles get applied.
I used this code Finding control within WPF itemscontrol for FindVisualChild.
And the SetProperty is just the reflection - for that one property we need access to to be able to do all this. e.g.
public static void SetProperty<T>(this object obj, string name, T value) { SetProperty(obj, obj.GetType(), name, value); }
public static void SetProperty<T>(this object obj, Type typeOf, string name, T value)
{
var property = typeOf.GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
property.SetValue(obj, value, null);
}
After going through this question and valuable comments from all, I have done some research on TextBlock Styling.
To my understanding the problem here is not with the Label or TextBlock, it is with the contentpresenter and the controls which use contentpresenter like Label, button and ComboBoxItem.
One of the properties of content presenter from MSDN : http://msdn.microsoft.com/en-us/library/system.windows.controls.contentpresenter.aspx
" If there is a TypeConverter that converts the type of Content to a string, the ContentPresenter uses that TypeConverter and creates a TextBlock to contain that string. The TextBlock is displayed "
In the example above, For SomeString Content presenter is converting it into textblock and applying the TextBlock margin (10) along with Label margin (10) making it 20.
In order to avoid this scenario you need to override the TextBlock style in contentpresenter as shown below
<ContentPresenter >
<ContentPresenter.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5" />
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
Following is the changes to your code.
<Window.Resources>
<Style TargetType="Label">
<Setter Property="FontSize" Value="26"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<Grid>
<Rectangle Fill="{TemplateBinding Background}" />
<ContentPresenter >
<ContentPresenter.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5" />
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="26"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="Foreground" Value="Pink" />
</Style>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding SomeString}" Background="Red" />
<Label Content="{Binding SomeDecimal}" Background="Green"/>
</StackPanel>
</Grid>
</Window>
This explanation is just based on my understanding. Let me know your comments.
Thanks
According to my comment i add more information to the question. Its not a direct answer, but provides additional information to the described problem.
The XAML below will display the described behavior directly in the Designer of Visual Studio and i have narrowed it down to the ContentPresenter, which seems to be source of the problem. The style gets applied to the first both ContentPresenter (intPresenter and boolPresenter), but not the last that uses a string as Content (stringPresenter).
<Window.Resources>
<system:Int32 x:Key="intValue">5</system:Int32>
<system:Boolean x:Key="boolValue">false</system:Boolean>
<system:String x:Key="stringValue">false</system:String>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="26" />
<Setter Property="Margin" Value="10" />
</Style>
</Window.Resources>
<Grid>
<StackPanel Orientation="Horizontal">
<ContentPresenter x:Name="intPresenter"
VerticalAlignment="Center"
Content="{StaticResource intValue}" />
<ContentPresenter x:Name="boolPresenter"
VerticalAlignment="Center"
Content="{StaticResource boolValue}" />
<ContentPresenter x:Name="stringPresenter"
VerticalAlignment="Center"
Content="{StaticResource stringValue}" />
</StackPanel>
</Grid>
In the debugger i have analyzed that the stringPresenter uses the DefaultStringTemplate while the intPresenter does not.
Its also interesting that the Language of the intPresenter is set, while by the stringPresenter its not.
And the implementation of the method looks something like that (taken from dotPeek)
private bool IsUsingDefaultStringTemplate
{
get
{
if (this.Template == ContentPresenter.StringContentTemplate || this.Template == ContentPresenter.AccessTextContentTemplate)
return true;
DataTemplate dataTemplate1 = ContentPresenter.StringFormattingTemplateField.GetValue((DependencyObject) this);
if (dataTemplate1 != null && dataTemplate1 == this.Template)
return true;
DataTemplate dataTemplate2 = ContentPresenter.AccessTextFormattingTemplateField.GetValue((DependencyObject) this);
return dataTemplate2 != null && dataTemplate2 == this.Template;
}
}
The StringContentTemplate and AccessTextTemplate are using a FrameworkElementFactory to generate the Visuals.

Binding a Command inside a Style

I'm currently working on a WPF application that uses MVVM. I've got a ListBox with a style set up to make it display like a RadioButtonList as follows:
<Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}">
<Setter Property="BorderBrush" Value="{x:Null}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Background="Transparent">
<RadioButton Focusable="False" IsHitTestVisible="False" IsChecked="{TemplateBinding IsSelected}" Content="{Binding Path=DisplayName}" Command="{Binding ElementName=ShippingWindow, Path=DataContext.ShipOtherMethodSelected}">
</RadioButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
<ListBox Name="lbShipOtherMethodOptions" Style="{StaticResource RadioButtonList}" ItemsSource="{Binding Path=ShipOtherMethodOptions}" Margin="13,74,366,282" />
What I'm trying to do is bind a command to the RadioButton, so that I can fire off an event when a selection is made. I've got the following code in my viewmodel, but I can't seem to get it to fire:
private ICommand shipOtherMethodSelected;
public ICommand ShipOtherMethodSelected
{
get
{
return shipOtherMethodSelected ??
(shipOtherMethodSelected = new RelayCommand(param => ShipOpenItems(), param => true));
}
}
private void ShipOpenItems()
{
MessageBox.Show("GOT HERE");
}
I'm pretty new to WPF and MVVM, so I'm probably missing something obvious. Can anyone point me in the right direction?
EDIT:
Per jberger's suggestion, I put in some code that I attempted that didn't work. Setting breakpoints in that section didn't get tripped, nor did the message box show up.
EDIT 2:
So after inspecting the DataContext on the sender, it turns out it was pointing to the object that I'm binding the RadioButton to, and not my viewmodel. I updated the code above (adding in an x:Name to my window and updating the Command binding), and now I'm getting the event to fire when its initially bound, but it doesn't fire when I select a value. Seems like we're getting really close now.
The ShipOtherMethodSelected is in your (main) ShippingVM NOT your ShipItemVM, so you need to set
Command="{Binding ElementName=ShippingWindow, Path=DataContext.ShipOtherMethodSelected}"
where ShippingWindow is the x:Name of an element "above" the ListBoxItem
Also, the Focusable="False" IsHitTestVisible="False" is denying the click. Remove the setters.

Resources