从 DataTemplate 访问父 DataContext

我有一个 ListBox,它绑定到 ViewModel 上的子集合。基于父 ViewModel 上的属性在 DataTemplate 中设置列表框项的样式:

<Style x:Key="curveSpeedNonConstantParameterCell">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified,
ElementName=someParentElementWithReferenceToRootDataContext}"
Value="True">
<Setter Property="Control.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>

我得到以下输出错误:

System.Windows.Data Error: 39 : BindingExpression path error:
'CurveSpeedMustBeSpecified' property not found on
'object' ''BindingListCollectionView' (HashCode=20467555)'.
BindingExpression:Path=DataContext.CurveSpeedMustBeSpecified;
DataItem='Grid' (Name='nonConstantCurveParametersGrid');
target element is 'TextBox' (Name='');
target property is 'NoTarget' (type 'Object')

因此,如果我将绑定表达式更改为 "Path=DataContext.CurrentItem.CurveSpeedMustBeSpecified",它将工作,但是只有当父用户控件的 DataContext 是 BindingListCollectionView时才能工作。这是不可接受的,因为用户控件的其余部分会自动绑定到 BindingListCurrentItem的属性。

How can I specify the binding expression inside the style so that it works regardless of the parent data context being a collection view or a single item?

130006 次浏览

可以使用 RelativeSource查找父元素,如下所示-

Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified,
RelativeSource={RelativeSource AncestorType={x:Type local:YourParentElementType}}}"

有关 RelativeSource的详细信息,请参阅 这个所以问题

我在 Silverlight 的相关源上遇到了问题。在搜索和阅读之后,我没有找到一个合适的解决方案,除非使用一些附加的绑定库。但是,这里的 获取对父 DataContext 的访问的另一种方法是通过直接引用您知道其数据上下文的元素来实现的。它使用 Binding ElementName并且工作得非常好,只要你尊重你自己的命名并且不在组件之间重复使用 templates/styles:

<ItemsControl x:Name="level1Lister" ItemsSource={Binding MyLevel1List}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content={Binding MyLevel2Property}
Command={Binding ElementName=level1Lister,
Path=DataContext.MyLevel1Command}
CommandParameter={Binding MyLevel2Property}>
</Button>
<DataTemplate>
<ItemsControl.ItemTemplate>
</ItemsControl>

This also works if you put the button into Style/Template:

<Border.Resources>
<Style x:Key="buttonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Button Command={Binding ElementName=level1Lister,
Path=DataContext.MyLevel1Command}
CommandParameter={Binding MyLevel2Property}>
<ContentPresenter/>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Border.Resources>


<ItemsControl x:Name="level1Lister" ItemsSource={Binding MyLevel1List}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding MyLevel2Property}"
Style="{StaticResource buttonStyle}"/>
<DataTemplate>
<ItemsControl.ItemTemplate>
</ItemsControl>

起初,我认为父元素的 x:Names不能从模板项中访问,但是因为我没有找到更好的解决方案,所以我只是尝试了一下,它工作得很好。

我正在搜索如何在 WPF 中做类似的事情,我得到了这个解决方案:

<ItemsControl ItemsSource="{Binding MyItems,Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton
Content="{Binding}"
Command="{Binding Path=DataContext.CustomCommand,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ItemsControl}} }"
CommandParameter="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>

我希望这招对别人管用。我有一个自动设置为 ItemsControls 的数据上下文,这个数据上下文有两个属性: MyItems-这是一个集合-和一个命令“ CustomCommand”。由于 ItemTemplate使用的是 DataTemplate,上层的 DataContext不能直接访问。然后使用相对路径和 ItemsControl类型的过滤器来获得父节点的 DC。

RelativeSource vs. < strong > ElementName

这两种方法可以达到同样的效果,

RelativeSource

Binding="{Binding Path=DataContext.MyBindingProperty,
RelativeSource={RelativeSource AncestorType={x:Type Window}}}"

这个方法在可视化树中查找类型为 Window (在本例中)的控件,当它找到它时,您基本上可以使用 Path=DataContext....访问它的 DataContext。这个方法的优点是你不需要绑定到一个名字,而且它是动态的,然而,对你的可视化树所做的改变可能会影响这个方法,甚至可能破坏它。

元素名称

Binding="{Binding Path=DataContext.MyBindingProperty, ElementName=MyMainWindow}

这个方法引用一个实体静态 Name,只要你的作用域能看到它,你就没问题。当然,你应该坚持自己的变数命名原则,不要打破这种方法。这种方法非常简单,您所需要的只是为 Window/UserControl 指定一个 Name="..."

虽然所有三种类型(RelativeSource, Source, ElementName)都能够做同样的事情,但是根据下面的 MSDN 文章,每一种类型最好用于它们各自的专业领域。

如何: 指定绑定源

在页面底部的表格中可以找到每一个的简短描述以及一个指向详细信息的链接。

问题在于 DataTemplate 不是应用于它的元素的一部分。

这意味着如果你绑定到模板,你绑定到的东西没有上下文。

然而,如果你把一个元素放在模板中,那么当这个元素被应用到父元素时,它就会得到一个上下文,然后绑定就可以工作了

所以这样不行

<DataTemplate >
<DataTemplate.Resources>
<CollectionViewSource x:Key="projects" Source="{Binding Projects}" >

但这个很好用

<DataTemplate >
<GroupBox Header="Projects">
<GroupBox.Resources>
<CollectionViewSource x:Key="projects" Source="{Binding Projects}" >

因为在应用了数据模板之后,群组框被放置在父级中,并且可以访问它的上下文

所以你需要做的就是从模板中移除样式并将它移动到模板中的一个元素中

请注意,item 控件的上下文是 item 而不是 control ie ComboBoxItem for ComboBox not the ComboBox itself in which case you should use the controls ItemContainerStyle instead

是的,你可以用尤文建议的 ElementName=Something来解决这个问题。

但是!

如果子元素(在其上使用此类绑定)是使用与父控件中指定的相同元素名称的用户控件,则绑定到错误的对象!

我知道这篇文章不是一个解决方案,但我认为在绑定中使用 ElementName 的每个人都应该知道这一点,因为它可能是一个运行时 bug。

<UserControl x:Class="MyNiceControl"
x:Name="TheSameName">
the content ...
</UserControl>


<UserControl x:Class="AnotherUserControl">
<ListView x:Name="TheSameName">
<ListView.ItemTemplate>
<DataTemplate>
<MyNiceControl Width="{Binding DataContext.Width, ElementName=TheSameName}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</UserControl>