1

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

<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找不到。

为什么?

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

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

MyParameter属性(奇怪地)设置在之前OtherParameter。使用简单绑定,它们按照声明的顺序设置,正如预期的那样。

(如您所见,我的代码已从不相关的内容中删除。希望我已经包含了所有重要的内容。)

4

4 回答 4

5

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

  • 在强制加载模板之前调用ApplyTemplate 。GetTemplateChild

  • 使用BeginInvokeDispatcherPriority.Loaded操作延迟到以后。

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

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

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

  1. 父级接收 DataContext 属性
  2. 父添加子
  3. 孩子评估 MyParameter
  4. 孩子应用模板
  5. 子级从父级继承 DataContext

在第一种情况下,(MyParameter="{Binding}"),第 3 步更新失败,MyParameter因为它还没有要绑定的 DataContext,所以MyParameterChanged没有被调用,也没有异常。在第 5 步之后,当子项的 DataContext 更新时,它会重新评估MyParameter,此时模板已存在,因此属性更改处理程序可以工作。

在第二种情况下,您正在专门查找父级的 DataContext 属性,该属性确实存在,因此在步骤 3MyParameterChanged 调用,但由于尚未应用模板而失败。

于 2014-05-27T14:17:58.570 回答
1

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

于 2015-08-08T10:54:01.703 回答
0

请注意:

<MyTextBox MyParameter="{Binding}" />

仅与此相同:

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

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

Loaded您可以通过为orInitialized事件添加处理程序来测试这一点。在其中放置一个断点,并在您的MyParameterChanged处理程序中放置一个额外的断点,然后查看它们的顺序。值得注意的是,在 UI 中初始化对象之前Dependencyproperty,可以从Styles 或内联 XAML设置 s。

于 2014-05-27T14:14:14.087 回答
0

ApplyTemplate();之后调用InitializeComponent();(通常在构造函数中)对我有用。覆盖OnApplyTemplate()方法并在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()应该在设置任何依赖属性之前调用。

于 2015-12-01T19:21:36.450 回答