Hello, everybody!
I've run into a problem with data-binding inside control template while the property is initialized inside the constructor.
Here is the show-case (you can also download sample solution):
CustomControl1.cs
public class CustomControl1 : ContentControl
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(CustomControl1),
new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public CustomControl1()
{
Content = "Initial"; // comment this line out and everything
// will start working just great
}
}
CustomControl1 style:
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
CustomControl2.cs:
public class CustomControl2 : ContentControl
{
static CustomControl2()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(CustomControl2),
new FrameworkPropertyMetadata(typeof(CustomControl2)));
}
}
CustomControl style:
<Style TargetType="{x:Type local:CustomControl2}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl2}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<local:CustomControl1
Content="{Binding Content,
RelativeSource={RelativeSource
AncestorType=local:CustomControl2}}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Window1.xaml:
<Window x:Class="WpfApplication5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
xmlns:local="clr-namespace:WpfApplication5">
<Grid>
<local:CustomControl2 Content="Some content" />
</Grid>
</Window>
So, the problem is: when you launch the app, the content of CustomControl1 appears to be "Initial" which is set by constructor, not the "Some content" string, which is supposed to be set by binding.
When we remove the initialization from the constructor, the binding starts working.
First of all, let me predict the answer: "you should set the initial value of a dependency property inside its metadata: either at the moment of registration or by means of metadata overriding capabilities". Yeap, you right, but the problem with this method of initialization is that the property is of collection type, so if I'll provide new MyCustomCollection()
as a default value of the property, then every instance of CustomControl1
will share the same instance of that collection and that's obviously not the idea.
I've done some debugging on the problem, here are the results:
- Binding instance is created, when we put it in element-like syntax and assign
x:Name
to it, then it's accessible throughTemplate.FindName("PART_Binding", this)
insideOnApplyTemplate
. - Binding simply isn't set on the property: inside the same
OnApplyTemplate
the codethis.GetBindingExpression(ContentProperty)
returnnull
. - There is nothing wrong with the binding itself: inside
OnApplyTemplate
we can look it up and then we can simply set it on the property like this:this.SetBinding(ContentProperty, myBinding)
- everything will work fine.
Can anyone explain how and why that happens?
Does anyone have a solution for setting non-shared initial value for a dependency property, so the binding wouldn't break?
Thanks in advance!
UPD: The most weird thing is that debug output with highest trace-level is the same for both cases: either when the binding doesn't occur or if it does.
Here it goes:
System.Windows.Data Warning: 52 : Created BindingExpression (hash=18961937) for Binding (hash=44419000)
System.Windows.Data Warning: 54 : Path: 'Content'
System.Windows.Data Warning: 56 : BindingExpression (hash=18961937): Default mode resolved to OneWay
System.Windows.Data Warning: 57 : BindingExpression (hash=18961937): Default update trigger resolved to PropertyChanged
System.Windows.Data Warning: 58 : BindingExpression (hash=18961937): Attach to WpfApplication5.CustomControl1.Content (hash=47980820)
System.Windows.Data Warning: 62 : BindingExpression (hash=18961937): RelativeSource (FindAncestor) requires tree context
System.Windows.Data Warning: 61 : BindingExpression (hash=18961937): Resolve source deferred
System.Windows.Data Warning: 63 : BindingExpression (hash=18961937): Resolving source
System.Windows.Data Warning: 66 : BindingExpression (hash=18961937): Found data context element: <null> (OK)
System.Windows.Data Warning: 69 : Lookup ancestor of type CustomControl2: queried Border (hash=11653293)
System.Windows.Data Warning: 69 : Lookup ancestor of type CustomControl2: queried CustomControl2 (hash=54636159)
System.Windows.Data Warning: 68 : RelativeSource.FindAncestor found CustomControl2 (hash=54636159)
System.Windows.Data Warning: 74 : BindingExpression (hash=18961937): Activate with root item CustomControl2 (hash=54636159)
System.Windows.Data Warning: 104 : BindingExpression (hash=18961937): At level 0 - for CustomControl2.Content found accessor DependencyProperty(Content)
System.Windows.Data Warning: 100 : BindingExpression (hash=18961937): Replace item at level 0 with CustomControl2 (hash=54636159), using accessor DependencyProperty(Content)
System.Windows.Data Warning: 97 : BindingExpression (hash=18961937): GetValue at level 0 from CustomControl2 (hash=54636159) using DependencyProperty(Content): 'Some content'
System.Windows.Data Warning: 76 : BindingExpression (hash=18961937): TransferValue - got raw value 'Some content'
System.Windows.Data Warning: 85 : BindingExpression (hash=18961937): TransferValue - using final value 'Some content'
UPD2: added a link to the sample solution