XAML绑定以主UI为数据源时失效,为何代码后置设置可解决?
问题:RelativeSource绑定DataContext失效,代码直接赋值却正常?
我定义了如下CustomClass控件:
public class CustomClass : Control { public TabControl TabControl1 { get { return (TabControl)GetValue(TabControl1Property); } set { SetValue(TabControl1Property, value); } } public static readonly DependencyProperty TabControl1Property = DependencyProperty.Register("TabControl1", typeof(TabControl), typeof(CustomClass), new PropertyMetadata(null)); }
在MainWindow.xaml中,尝试通过RelativeSource给TabItem绑定MainWindow作为DataContext:
<Window x:Class="SampleWithoutSFControl.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:SampleWithoutSFControl" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <local:CustomClass > <local:CustomClass.TabControl1> <TabControl> <TabItem x:Name="tabItem" Header="Item1" DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:MainWindow}}}"> <Label Content="{Binding Name1}"/> </TabItem> </TabControl> </local:CustomClass.TabControl1> </local:CustomClass> </Window>
MainWindow.xaml.cs中定义了Name1属性:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); // tabItem.DataContext = this; //取消注释可解决问题! } public string Name1 => "Sync"; }
运行后TabItem内的Label无法显示“Sync”,但取消注释代码里的tabItem.DataContext = this;后绑定正常生效。为什么两种设置DataContext的方式会有差异?
原因分析
核心问题出在WPF逻辑树/可视化树的结构上:
- 你通过
<local:CustomClass.TabControl1>赋值的TabControl,只是作为CustomClass的一个依赖属性值存在,并没有被加入到MainWindow的可视化树或逻辑树中。默认情况下,Control类的模板并没有预留位置来显示TabControl1这个属性的内容,所以这个TabControl不属于MainWindow的树结构分支。 RelativeSource Mode=FindAncestor的原理是在当前元素的逻辑树/可视化树中向上遍历查找匹配类型的父元素,既然TabControl不在MainWindow的树里,自然找不到MainWindow,绑定就会失效。
而代码里直接tabItem.DataContext = this;是直接把MainWindow的实例引用赋值给TabItem的DataContext,完全不依赖树结构查找,所以绑定能直接访问到Name1属性,自然正常生效。
解决方案
可以根据需求选择以下方式:
1. 让TabControl进入可视化树
修改CustomClass的默认模板,把TabControl1作为可视化子元素加入。比如在Generic.xaml中定义CustomClass的模板:
<Style TargetType="{x:Type local:CustomClass}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CustomClass}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <!-- 加入ContentPresenter显示TabControl1 --> <ContentPresenter Content="{TemplateBinding TabControl1}"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
这样TabControl就会成为CustomClass的可视化子元素,进而属于MainWindow的树结构,RelativeSource绑定就能找到MainWindow了。
2. 使用ElementName绑定
给MainWindow设置x:Name,然后通过ElementName直接引用:
<Window x:Class="SampleWithoutSFControl.MainWindow" ... x:Name="MainRoot"> <local:CustomClass > <local:CustomClass.TabControl1> <TabControl> <TabItem Header="Item1" DataContext="{Binding ElementName=MainRoot}"> <Label Content="{Binding Name1}"/> </TabItem> </TabControl> </local:CustomClass.TabControl1> </local:CustomClass> </Window>
3. 保留代码赋值的方式
直接在后台代码中设置DataContext,这种方式简单直接,不需要修改树结构。
内容的提问来源于stack exchange,提问作者Graviton
相关产品推荐
相关产品推荐

