I have created a custom control, MyTextBox
, that inherits from TextBox
. It has a style associated with it that contains a namned control:
<Style x:Key="{x:Type MyTextBox}" TargetType="{x:Type MyTextBox}">
<!-- ... -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MyTextBox}">
<!-- ... -->
<SomeControl x:Name="PART_SomeControl" />
<!-- ... -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MyTextBox
has a dependency property that, when set, propagates its value to SomeControl
:
public class MyTextBox : TextBox
{
// ...
public static new readonly DependencyProperty MyParameterProperty =
DependencyProperty.Register(
"MyParameter",
typeof(object),
typeof(MyTextBox),
new PropertyMetadata(default(object), MyParameterChanged));
private static void MyParameterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var me = (MyTextBox)d;
var someControl = (SomeControl)me.GetTemplateChild("PART_SomeControl");
someControl.SetValue(SomeControl.MyParameterProperty, e.NewValue);
}
}
This works fine when doing a simple binding, like this:
<MyTextBox MyParameter="{Binding}" />
But when I use a more fancy binding using RelativeSource, like this:
<MyTextBox MyParameter="{Binding DataContext, RelativeSource={RelativeSource
FindAncestor, AncestorType=ParentView}}"
the method me.GetTemplateChild()
returns null
. That is, SomeControl
cannot be found.
Why?
One observation I have made is that, when it has a RelativeSource
, the MyParameter
dependency property is set first of all dependency properties. That is, if I do something like this:
<MyTextBox
OtherParameter="{Binding}"
MyParameter="{Binding DataContext, RelativeSource={RelativeSource
FindAncestor, AncestorType=ParentView}}"
the MyParameter
property is (strangely) set before OtherParameter
. Using the simple binding they are set in the same order as declared, just as expected.
(As you can see, my code has been stripped from unrelevant stuff. Hopefully, I have included all that is important.)
Most likely it is being set before the template is applied. There are a few ways you could work around that:
Call ApplyTemplate before GetTemplateChild
to force the template to load.
Use BeginInvoke with DispatcherPriority.Loaded
to delay the operation until later.
Allow MyParameterChanged
to fail if there is no template, and repeat the logic in OnApplyTemplate (you should be doing this anyway, in case the template is replaced after load (as in a Windows theme change).
It looks like you're just passing on the value to a child element. Have you considered using an attached property with value inheritance?
As for why it fails for your RelativeSource FindAncestor
binding, and not the raw DataContext binding, I think this comes down to the fact that DataContext
itself is an inherited property. Hypothetically, assume the order of operations is this:
In the first case, (MyParameter="{Binding}"
), step 3 fails to update MyParameter
because it doesn't yet have a DataContext to bind to, so MyParameterChanged
is not called and there is no exception. After step 5, when the child's DataContext is updated, it re-evaluates MyParameter
, and by that point the template exists so the property change handler works.
In the second case, you are specifically looking up the DataContext property of the parent, which does exist, so MyParameterChanged
is called at step 3 and fails because the template isn't applied yet.