WPF自定义控件绑定报错:TestValue正常但Inner属性绑定失败
现象描述
TestControl的TestValue属性绑定HelloButton的Width可正常工作,但SomePropertyClass的Inner属性进行相同绑定时出现如下错误:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Width; DataItem=null; target element is 'SomePropertyClass' (HashCode=63183526); target property is 'Inner' (type 'Int32')
相关代码
1. MainWindow.xaml
<StackPanel> <Button x:Name="HelloButton" Width="100">Hello</Button> <lib:TestControl Width="100" TestValue="{Binding ElementName=HelloButton,Path=Width}"> <lib:TestControl.Some> <lib:SomePropertyClass Inner="{Binding ElementName=HelloButton,Path=Width}"/> </lib:TestControl.Some> </lib:TestControl> </StackPanel>
2. TestControl.cs
public class TestControl : Control { static TestControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TestControl), new FrameworkPropertyMetadata(typeof(TestControl))); } public static readonly DependencyProperty TestValueProperty = DependencyProperty.Register("TestValue", typeof(int), typeof(TestControl)); public int TestValue { get => (int)GetValue(TestValueProperty); set => SetValue(TestValueProperty, value); } public static readonly DependencyProperty SomeProperty = DependencyProperty.Register("Some", typeof(SomePropertyClass), typeof(TestControl)); public SomePropertyClass Some { get => (SomePropertyClass)GetValue(SomeProperty); set => SetValue(SomeProperty, value); } } public class SomePropertyClass : DependencyObject { public static readonly DependencyProperty InnerProperty = DependencyProperty.Register("Inner", typeof(int), typeof(SomePropertyClass)); public int Inner { get => (int)GetValue(InnerProperty); set => SetValue(InnerProperty, value); } }
3. TestControl.xaml
<Style TargetType="{x:Type local:TestControl}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:TestControl}"> <StackPanel> <Button Content="{TemplateBinding TestValue}"/> <Button Content="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Some.Inner}"/> </StackPanel> </ControlTemplate> </Setter.Value> </Setter> </Style>
原因分析
WPF中使用ElementName进行绑定时,需要绑定目标所在的对象能够找到主导的FrameworkElement/FrameworkContentElement(即绑定上下文载体),这个载体必须属于WPF的可视化树或逻辑树,具备命名范围解析能力。
TestControl继承自Control(最终继承FrameworkElement),属于可视化树的一部分,能访问所在的命名范围,因此可以正常解析ElementName=HelloButton的绑定。SomePropertyClass仅继承DependencyObject,它没有被纳入可视化树或逻辑树,也没有关联任何FrameworkElement作为上下文,因此无法找到HelloButton对应的命名范围,导致绑定失败。
解决方案
最直接的方法是让SomePropertyClass继承Freezable而非DependencyObject。Freezable作为DependencyObject的子类,具备自动关联到所在FrameworkElement上下文的能力,能继承父元素的命名范围和DataContext,从而正常解析ElementName绑定:
public class SomePropertyClass : Freezable { // Freezable必须重写该方法 protected override Freezable CreateInstanceCore() { return new SomePropertyClass(); } public static readonly DependencyProperty InnerProperty = DependencyProperty.Register("Inner", typeof(int), typeof(SomePropertyClass)); public int Inner { get => (int)GetValue(InnerProperty); set => SetValue(InnerProperty, value); } }
修改后,SomePropertyClass的实例会自动关联到TestControl的上下文,就能成功绑定HelloButton的Width属性。
内容的提问来源于stack exchange,提问作者Developer333

