当依赖项属性具有RelativeSource绑定时,GetTemplateChild返回null

时间:2014-05-27 13:55:28

标签: c# wpf xaml dependency-properties relativesource

我创建了一个继承自MyTextBox的自定义控件TextBox。它有一个与之关联的样式,包含一个namned控件:

<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有一个依赖项属性,设置后会将其值传播到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);
    }
}

在进行简单绑定时,这样可以正常工作,如下所示:

<MyTextBox MyParameter="{Binding}" />

但是当我使用RelativeSource使用更精美的绑定时,就像这样:

<MyTextBox MyParameter="{Binding DataContext, RelativeSource={RelativeSource
    FindAncestor, AncestorType=ParentView}}"

方法me.GetTemplateChild()返回null。也就是说,找不到SomeControl

为什么?

我所做的一个观察是,当它具有RelativeSource时,MyParameter依赖项属性首先设置为所有依赖项属性。也就是说,如果我做这样的事情:

<MyTextBox
    OtherParameter="{Binding}"
    MyParameter="{Binding DataContext, RelativeSource={RelativeSource
        FindAncestor, AncestorType=ParentView}}"

MyParameter属性(奇怪地)设置在OtherParameter之前。使用简单绑定,它们的设置顺序与声明的顺序相同。

(正如你所看到的,我的代码已经从不相关的东西中删除了。希望我已经包含了所有重要的内容。)

4 个答案:

答案 0 :(得分:3)

最有可能是在应用模板之前设置它。有几种方法可以解决这个问题:

  • GetTemplateChild之前致电ApplyTemplate以强制加载模板。

  • BeginInvokeDispatcherPriority.Loaded一起使用,将操作延迟到以后。

  • 如果没有模板,则允许MyParameterChanged失败,并重复OnApplyTemplate中的逻辑(无论如何,如果在加载后更换模板,您应该这样做(如在Windows主题更改)。

看起来你只是将值传递给子元素。您是否考虑过使用带有价值继承的附属物?

至于为什么它失败了你的RelativeSource FindAncestor绑定,而不是原始的DataContext绑定,我认为这归结为DataContext本身是一个继承属性的事实。假设,假设操作顺序是这样的:

  1. Parent接收DataContext属性
  2. 家长添加孩子
  3. Child评估MyParameter
  4. 儿童适用模板
  5. Child从父
  6. 继承DataContext

    在第一种情况下,(MyParameter="{Binding}"),第3步无法更新MyParameter,因为它还没有要绑定的DataContext,因此不会调用MyParameterChanged并且没有例外。在步骤5之后,当更新子级的DataContext时,它会重新评估MyParameter,此时模板就存在,因此属性更改处理程序可以正常工作。

    在第二种情况下,您专门查找父 存在的父级的DataContext属性,因此在步骤3中调用MyParameterChanged 失败,因为模板尚未应用。

答案 1 :(得分:1)

覆盖OnApplyTemplate方法并在其中调用GetTempladeChild。 我通过阅读这个来解决这个问题: http://www.codeproject.com/Articles/179105/How-to-access-Control-Template-parts-from-Code-Beh

答案 2 :(得分:0)

请注意:

<MyTextBox MyParameter="{Binding}" />

与此相同:

<MyTextBox MyParameter="{Binding DataContext, RelativeSource={RelativeSource
FindAncestor, AncestorType=ParentView}}" />

如果MyTextBox控件位于名为ParentView且其DataContext属性设置的视图中。如果是这种情况,那么它确实不应该像你描述的那样引起任何问题。因此,我只能假设您在UI初始化之前尝试通过SomeControl访问MyParameterProperty对象。

您可以通过为LoadedInitialized事件添加处理程序来对此进行测试。在那里添加一个断点,在MyParameterChanged处理程序中添加一个断点,看看它们引入了哪个顺序。值得注意的是Dependencyproperty可以从Style s或内联XAML设置之前在UI中初始化对象。

答案 3 :(得分:0)

ApplyTemplate();(通常在构造函数中)之后立即调用InitializeComponent();对我有效。覆盖OnApplyTemplate()方法&amp;在这里拨打GetTemplateChild

例如:

        private TextBox PART_TextBox;
        private RepeatButton PART_UpButton;
        private RepeatButton PART_DownButton;

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            PART_TextBox = GetTemplateChild("PART_TextBox") as TextBox;
            PART_UpButton = GetTemplateChild("PART_UpButton") as RepeatButton;
            PART_DownButton = GetTemplateChild("PART_DownButton") as RepeatButton;
        }

这样在设置任何依赖项属性之前应该调用OnApplyTemplate()