当依赖项属性具有 RelativeSource 绑定时,GetTemplateChild 返回 null
本文关键字:定时 GetTemplateChild 返回 null 绑定 RelativeSource 依赖 属性 | 更新日期: 2023-09-27 18:05:35
>我创建了一个自定义控件,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
。
为什么?
我所做的一个观察是,当它有一个RelativeSource
时,MyParameter
依赖属性首先设置所有依赖属性。也就是说,如果我做这样的事情:
<MyTextBox
OtherParameter="{Binding}"
MyParameter="{Binding DataContext, RelativeSource={RelativeSource
FindAncestor, AncestorType=ParentView}}"
MyParameter
属性(奇怪地(设置在OtherParameter
之前。使用简单绑定,它们的设置顺序与声明的顺序相同,正如预期的那样。
(如您所见,我的代码已从不相关内容中删除。希望我已经包含了所有重要的内容。
很可能是在应用模板之前设置的。有几种方法可以解决此问题:
-
在
GetTemplateChild
之前调用应用模板以强制加载模板。 -
使用 BeginInvoke with
DispatcherPriority.Loaded
将操作延迟到以后。 -
如果没有模板,则允许
MyParameterChanged
失败,并在 OnApplyTemplate 中重复逻辑(无论如何,您都应该这样做,以防在加载后替换模板(如在 Windows 主题更改中(。
看起来您只是将值传递给子元素。是否考虑过将附加属性与值继承一起使用?
至于为什么它对于您的RelativeSource FindAncestor
绑定而不是原始 DataContext 绑定失败,我认为这归结为DataContext
本身是一个继承属性的事实。假设操作顺序如下:
- 父级接收数据上下文属性
- 父添加子项
- 子项评估我的参数
- 子应用模板
- 子级从父级继承数据上下文
在第一种情况下,(MyParameter="{Binding}"
(,步骤 3 无法更新MyParameter
,因为它还没有要绑定到的 DataContext,因此不会调用MyParameterChanged
并且没有异常。在步骤 5 之后,当子项的 DataContext 更新时,它会重新评估MyParameter
,此时模板已存在,因此属性更改处理程序正常工作。
在第二种情况下,您专门查找父级的 DataContext 属性,该属性确实存在,因此在步骤 3 中调用MyParameterChanged
并失败,因为尚未应用模板。
重写 OnApplyTemplate 方法并在其中调用 GetTempladeChild。我通过阅读以下内容解决了这个问题:http://www.codeproject.com/Articles/179105/How-to-access-Control-Template-parts-from-Code-Beh
请注意:
<MyTextBox MyParameter="{Binding}" />
只是和这个一样:
<MyTextBox MyParameter="{Binding DataContext, RelativeSource={RelativeSource
FindAncestor, AncestorType=ParentView}}" />
如果 MyTextBox
控件位于名为 ParentView
的视图中,该视图设置了其 DataContext
属性。如果是这种情况,那么它真的不应该像您描述的那样引起任何问题。因此,我只能假设您正在尝试在初始化 UI 之前通过MyParameterProperty
访问SomeControl
对象。
可以通过为 Loaded
或 Initialized
事件添加处理程序来测试这一点。在其中放置一个断点,并在MyParameterChanged
处理程序中放置一个额外的断点,并查看它们的引发顺序。值得注意的是,在 UI 中初始化对象之前,可以从 Style
s 或内联 XAML 设置Dependencyproperty
s。
在InitializeComponent();
之后立即调用ApplyTemplate();
(通常在构造函数中(对我有用。重写 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()
。