WPF TemplateBinding vs RelativeSource TemplatedParent - wpf

What is the difference between these 2 bindings:
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderBrush="{TemplateBinding Property=Background}">
<ContentPresenter />
</Border>
</ControlTemplate>
and
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderBrush="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}">
<ContentPresenter />
</Border>
</ControlTemplate>
?

TemplateBinding is not quite the same thing. MSDN docs are often written by people that have to quiz monosyllabic SDEs about software features, so the nuances are not quite right.
TemplateBindings are evaluated at compile time against the type specified in the control template. This allows for much faster instantiation of compiled templates. Just fumble the name in a templatebinding and you'll see that the compiler will flag it.
The binding markup is resolved at runtime. While slower to execute, the binding will resolve property names that are not visible on the type declared by the template. By slower, I'll point out that its kind of relative since the binding operation takes very little of the application's cpu. If you were blasting control templates around at high speed you might notice it.
As a matter of practice use the TemplateBinding when you can but don't fear the Binding.

TemplateBinding - More limiting than using regular Binding
More efficient than a Binding but it has less functionality
Only works inside a ControlTemplate's visual tree
Doesn't work with properties on Freezables
Doesn't work within a ControlTemplate's Trigger
Provides a shortcut in setting properties(not as verbose),e.g. {TemplateBinding targetProperty}
Regular Binding - Does not have above limitations of TemplateBinding
Respects Parent Properties
Resets Target Values to clear out any explicitly set values
Example: <Ellipse Fill="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Background}"/>

One more thing - TemplateBindings don't allow value converting. They don't allow you to pass a Converter and don't automatically convert int to string for example (which is normal for a Binding).

TemplateBinding is a shorthand for Binding with TemplatedParent but it does not expose all the capabilities of the Binding class, for example you can't control Binding.Mode from TemplateBinding.

I thought TemplateBinding does not support Freezable types (which includes brush objects). To get around the problem. One can make use of TemplatedParent

They are used in a similar way but they have a few differences.
Here is a link to the TemplateBinding documentation:
http://msdn.microsoft.com/en-us/library/ms742882.aspx

Related

How do I access the styled object from the value of a setter?

I am attempting to style every DataRecordPresenter in a XamDataGrid according to a VisualBrush that should flex according to the presenter in question. Specifically I'm aiming to highlight some rows, where the highlighting pattern is potentially complex (i.e. arbitrarily large, albeit in practice probably not more than 5-6 colours).
The solution I'm hoping to use looks something like this:
<!-- idp bound to namespace for Infragistics DataPresenters -->
<idp:XamDataGrid>
<idp:XamDataGrid.Resources>
<Style TargetType={x:Type idp:DataRecordPresenter>
<Style.Setters>
<!-- THE RELATIVESOURCE FAILS HERE -->
<Setter Property="Tag">
<Grid DataContext={Binding RelativeSource={RelativeSource Mode=FindAncestor,TargetType={x:Type idp:DataRecordPresenter}}}>
<!-- Content that relies on properties of the DataRecordPresenter -->
</Grid>
</Setter>
<Setter Property="Background">
<!-- THE RELATIVESOURCE WORKS HERE -->
<VisualBrush
ViewPort="12,12,12,12"
Visual="{Binding Tag,RelativeSource={RelativeSource Mode=FindAncestor,TargetType={x:Type idp:DataRecordPresenter}}"
>
</VisualBrush>
</Setter>
</Style.Setters>
</Style>
</idp:XamDataGrid.Resources>
</idp:XamDataGrid>
The issue is that whilst I am able to identify the DataRecordPresenter when constructing the actual VisualBrush (tested using Snoop - as intended the result is the contents of the Tag property), whilst trying to find the same object via the same mechanism from the context of the setter for the Tag property, I fail to identify any such ancestor.
I suspect this is because the Tag property is not associated with the visual (or logical) trees, whereas Background is, but I haven't managed to come up with a solution as yet that addresses the issue. I'd equally be happy to move the Grid into the VisualBrush, but I believe a VisualBrush is also detached from the relevant trees, so I don't think that'll work, or at least not with a simple inline definition.

Modify Template in code-behind

In our app we use 3rd party library components.
I need to change only one value in whole template. How can I archieve this without redefine template?
For example, controlTemplate:
<ControlTemplate TargetType="{x:Type Label}">
<Border x:Name="PART_MainBorder"
BorderBrush="Black"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter/>
</Border>
</ControlTemplate>
I need to change PART_MainBorder.BorderBrush. How can I do this?
I have found this link, but I can't believe there is no other way to do it..
Thanks.
I'm sure there are more elegant ways to do it in XAML but to answer your question template is nothing more but a cookie cuter so you cannot just start changing properties of template objects in code behind. You can modify template controls properties via control to which the template has been applied. In case of ControlTemlate it will be templated control and for DataTemplate it will be ContentPresenter used to generate content. So let's say that you have 2 Labels to which you applied template above:
<Label Content="A" x:Name="Label1"/>
<Label Content="B" x:Name="Label2"/>
an then in the code you can change Border.BorderBrush like this:
(Label1.Template.FindName("PART_MainBorder", Label1) as Border).BorderBrush = new SolidColorBrush(Colors.Red);
(Label2.Template.FindName("PART_MainBorder", Label2) as Border).BorderBrush = new SolidColorBrush(Colors.Orange);
worth noting that 2 Labels will have different BorderBrush color

Collapse ContentControl if Content is Collapsed

I've got a ContentControl which has a style containing a border and other visual decorations. I want these decorations to disappear when the content is collapsed, so I figured I have to set the visibility of the ContentControl to collapsed in this case. I got this style for my ContentControl decoration:
<Style x:Key="DecoratedItem1" TargetType="{x:Type c:DecoratedItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:DecoratedItem}">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="2" CornerRadius="2">
<StackPanel Orientation="Horizontal">
<Image Source="/Images/file.png"/>
<ContentPresenter Name="wContent"/>
</StackPanel>
</Border>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
<DataTrigger.Setters>
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger.Setters>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The DecoratedItem class is just a subclass of ContentControl with additional DependencyProperties which are not relevant to this issue, I just wanted to note that I already have a subclass to which I could add code, if necessary.
This works when the content of the ContentControl is a UIElement, however if the content is generated by a DataTemplate it complains about not being able to find the Visibility property.
<!-- works -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
<!-- doesn't work -->
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
<c:DecoratedItem.Resources>
<DataTemplate DataType="{x:Type clr:String}">
<TextBlock Text="{Binding}" Visibility="Collapsed"/>
</DataTemplate>
</c:DecoratedItem.Resources>
</c:DecoratedItem>
The error for the second case diplayed in the debug output window is:
System.Windows.Data Error: 40 : BindingExpression path error:
'Visibility' property not found on 'object' ''String' (HashCode=-885832486)'.
BindingExpression:Path=Content.Visibility;
DataItem='ContentPresenter' (Name='wContent');
target element is 'DecoratedItem' (Name='');
target property is 'NoTarget' (type 'Object')
I understand why this happens, but don't know how to fix my style to work as I want it. I don't mind adding helper code to the DecoratedItem subclass if necessary. Any idea how to fix this?
[edit 1]
Some more explanation in regard to the proposed answer:
I can't enforce that the Content is always an UIElement. This is a model-view design after all, and of course I simplified the example a lot. In the real project the content is a model selected from the DataContext, which can be of several different types, and the DataTemplate builds a presentation for that model. Some of the DataTemplates decide (depending on model-state) that there is nothing to present and switch Visibility to Collapsed. I would like to propagate that information to the decorating container. The example above really just presents the problem and not the motivation, sorry.
[edit 2]
Not sure how knowing more about the model would help the problem, but here we go. The data in the Content field doesn't have much in common since it can be a lot of things, this DecoratedItem is supposed to be reusable to give a common visual style to items shown on some forms. Content can be stuff like work items whose DataTemplate collapses them if they are disabled; other kinds of Content can be incomplete and get collapsed. Of course other kinds never may get collapsed.
But note that the data model doesn't really have much to do with the question, which still is how to bind against the Visibility of the expanded content element (after possibly exposing it through the subclass in a bindable way).
There are a couple of ways of describing what's wrong. In the first, working example:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}">
<TextBlock Text="ABC" Visibility="Collapsed"/>
</c:DecoratedItem>
the Content property of the ContentControl is set to be a TextBlock, which is a UIElement with a Visibility property. (This assumes that you have not changed the ContentPropertyAttribute of your derived class DecoratedItem to be something other than Content). Thus, your DataTrigger binding can correctly evaluate:
<DataTrigger Binding="{Binding ElementName=wContent, Path=Content.Visibility}" Value="Collapsed">
Contrast the working case with the failing one:
<c:DecoratedItem Style="{StaticResource DecoratedItem1}" Content="ABC">
in which the Content property is set to an instance of a String, which does not have a Visibility property.
The other way to describe what's wrong is to note that, even though you supply a DataTemplate for the case of Content being a String, Content is still a String and still does not have a Visibility property. In other words, the statement that the Content is generated by the DataTemplate is incorrect -- your DataTemplate just told the control how to display Content of type String.
The general answer to your question is that, if you want the DataTrigger in DecoratedItem1 bound with a Path of Content.Visibility, you need to make sure the content you put in it is always a UIElement. Conversely, if you want to be able to put any sort of Content into the control, you need to trigger off of something else.
The specific answer to your question, strictly, relies on your broader intent (in particular, on how the Visibility of the Content of your control will be set/modified). A couple of possibilities:
if you really want your DataTrigger binding of the form, "Content.Visibility", make sure that the Content is always a UIElement. For instance, use the working form of the style and then bind the Text of TextBlock to something appropriate. However, this doesn't fit so well with the idea of your derived control as a ContentControl, so...
your DataTrigger could probably bind to something else. It seems like, from the way the question is formed, that there is some other property or code-behind that will control whether the various content entities are Visible or not.
finally, you could add an additional DataTrigger to the TextBlock. This DataTrigger would set the visibility of its parent based on its own visibility. Then, bind the DataTrigger in style DecoratedItem1 with Path "Visibility" instead of "Content.Visibility", essentially chaining together Visibilities manually.
Edit
Based on what you've described about how you want to use this, it sounds like you need to consider the visual tree. You might augment your DecoratedItem control to have the following functionality: if all its visual children that are UIElments have a visibility of Collapsed (or if it has no visual children), it is also Collapsed (or, whatever logic makes sense for the desired functionality in terms of the Visibility of its visual children). You'd need to use the VisualTreeHelper class from code -- in particular, the GetChildrenCount and GetChild methods. You'd also, in your DecoratedItem class, override OnVisualChildrenChanged (while still calling the base class method) so that you can get UIElement.IsVisibleChanged events for the visible children.

Is Element Binding to a Collapsed UserControl a Sloppy, Cheap Trick?

I'm sure that most of us will agree that it is possible to run out of declarative DataContext sources from parent or self contexts. We then might resort to Binding to a parent Tag and then use Element Binding. So the source might look like this:
<Grid.Tag>
<Binding Path="MyProperty" Source="{StaticResource MySource}" />
</Grid.Tag>
What happens when we can't even do this? My next sloppy trick is to use a collapsed UserControl element:
<UserControl
x:Name="MySloppyControl"
DataContext="{StaticResource YetAnotherSourceInThisCrazyGrid}"
Foreground={Binding CrazyForegroundColor}
Visibility="Collapsed" />
Now I can do this:
<Grid.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Foreground" Value="{Binding CrazyForegroundColor, ElementName=MySloppyControl}" />
</Style>
</Grid.Resources>
Assuming that I explained this adequately, is this collapsed UserControl pattern misguided?
Not that I haven't done it myself, but I would have to say "yes" -- that pattern is misguided.
In those few cases where I chose to do this my code looked like this:
<Control x:Name="Whatever" DataContext="..." />
<Control x:Name="SomethingElse" DataContext="..." />
Since a control is invisible by default this acheives the same effect with less code.
Having said that, let me explain why I think this approach is misguided:
WPF is all about binding to data. If you have some data to bind to, it is either part of your model (or viewmodel) or it is external. If it is part of your model (or viewmodel) and your model's structure is well-defined you should be able to access it through the current DataContext. On the other hand, if it is static you should be able to access it directly from the target.
For example, let's say you want to populate a ComboBox with all the possible WidgetTypes from the model. If your model is well-constructed, the binding can be as simple as:
<ComboBox ItemsSource="{Binding DataSet.AllWidgetTypes}" />
This assumes that your ComboBox's DataContext is a "Widget" and that "Widget" has a "DataSet" property that gives access to other related data. Alternatively if the list of available types might change based on the other details of the Widget, the binding might be simply {Binding AppliableWidgetTypes}.
The other situation in which you might want a "separate DataContext" as you call it is when referencing static objects, such as through x:Static or StaticResource. In these cases there is a better way to do it: Set Binding.Source. It looks like this:
Text="{Binding DefaultFontSize,Source={x:Static ApplicationProperties.Instance}}"
or this
Text="{Binding PropertyName,Source={StaticResource ResourceContainingProperty}}"
If your motivation is to avoid creating a viewmodel to combine multiple model objects, use C# anonymoust types to construct an poor-man's viewmodel. For example:
DataContext = new { something = 123, whatever = "Test" };
Another common pattern is to create properties on the control and use a ControlTemplate and TemplateBinding to bind to them.
This leaves one final situation, which is when you really want a shared value and you want no code-behind at all. In this case I have actually gone ahead and used the invisible <Control> as shown previously. So there are rare situations where it may apply.

StaticResource in Silverlight ControlTemplate?

I'm trying to use a StaticResource in a ControlTemplate for a custom object, and whenever that object is rendered, the application crashes. As you can see in the code below, I define both the template and the resource in App.XAML. I've been doing a bit of searching to see if/why this isn't allowed, but have had no luck so far.
<Color x:Key="PersonBackground">#FF003B00</Color>
<ControlTemplate x:Key="PersonTemplate" TargetType="this:Person">
<Border Background="{StaticResource PersonBackground}" BorderBrush="White"
BorderThickness="2" CornerRadius="10" MinHeight="70" MinWidth="120">
...
</ControlTemplate>
If anyone could explain why this isn't allowed or what I'm doing wrong (or, best yet, a better way to do custom theming in Silverlight), I would greatly appreciate it.
Edit: I feel like I should specify that I'm mostly just interested in being able to set the color scheme in one place; the rest of the theme won't need to change as much.
Instead of Color, can you try using a SolidColorBrush
<SolidColorBrush x:Key="PersonBackground" Color="#FF003B00"/>

Resources