从 DataTemplate 访问父 DataContext
Posted
技术标签:
【中文标题】从 DataTemplate 访问父 DataContext【英文标题】:Access parent DataContext from DataTemplate 【发布时间】:2011-03-25 04:46:54 【问题描述】:我有一个ListBox
,它绑定到 ViewModel 上的子集合。列表框项在基于父 ViewModel 上的属性的数据模板中设置样式:
<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"
,它就可以工作,但前提是父用户控件的数据上下文是BindingListCollectionView
。这是不可接受的,因为用户控件的其余部分会自动绑定到BindingList
上的CurrentItem
的属性。
如何在样式中指定绑定表达式,以便无论父数据上下文是集合视图还是单个项目,它都能正常工作?
【问题讨论】:
【参考方案1】:你可以使用RelativeSource
来查找父元素,像这样-
Binding="Binding Path=DataContext.CurveSpeedMustBeSpecified,
RelativeSource=RelativeSource AncestorType=x:Type local:YourParentElementType"
有关RelativeSource
的更多详细信息,请参阅this SO question。
【讨论】:
我必须指定Mode=FindAncestor
才能使其工作,但这在 MVVM 场景中工作并且更好,因为它避免了命名控制。 Binding="Binding Path=DataContext.CurveSpeedMustBeSpecified, RelativeSource=RelativeSource Mode=FindAncestor, AncestorType=x:Type local:YourParentElementType"
像魅力一样工作
【参考方案2】:
我在 Silverlight 中遇到了相对来源的问题。在搜索和阅读之后,如果不使用一些额外的 Binding 库,我没有找到合适的解决方案。但是,这是另一种通过直接引用您知道数据上下文的元素来访问父 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>
如果您将按钮放入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
无法从模板项中访问,但由于我没有找到更好的解决方案,我只是尝试了一下,它工作正常。
【讨论】:
我的项目中有这个确切的代码,但它正在泄漏 ViewModels(未调用终结器,命令绑定似乎保留了 DataContext)。您能否验证您是否也存在此问题? @Juve 这行得通,但是否可以这样做,以便为实现相同模板的所有项目控件触发?名称是唯一的,所以我们需要一个单独的模板,除非我遗漏了什么。 @Juve 忽略我的最后一个,我通过将 relativesource 与 findancestor 一起使用并按祖先类型搜索来让它工作(所以除了不按名称搜索之外都是一样的)。在我的例子中,我重复使用 ItemsControls,每个都实现一个模板,所以我的看起来像这样:Command="Binding RelativeSource=RelativeSource FindAncestor, AncestorType=x:Type ItemsControl, Path=DataContext.OpenDocumentBtnCommand" 在我的情况下不起作用,XAML 绑定失败表明 DataContext 为空【参考方案3】:我正在寻找如何在 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
是不能直接访问的。那么获取父级DC的解决方法是使用相对路径并按ItemsControl
类型过滤。
【讨论】:
【参考方案4】:RelativeSource 与 ElementName
这两种方法可以达到同样的效果,
相对来源
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 文章,最好在各自的专业领域中使用每种类型。
How to: Specify the Binding Source
在页面底部的表格中找到每个的简要说明以及指向更详细信息的链接。
【讨论】:
【参考方案5】:问题在于 DataTemplate 不是其应用的元素的一部分。
这意味着如果您绑定到模板,您将绑定到没有上下文的东西。
但是,如果您将一个元素放在模板中,那么当该元素应用于父元素时,它会获得一个上下文,然后绑定就会起作用
所以这行不通
<DataTemplate >
<DataTemplate.Resources>
<CollectionViewSource x:Key="projects" Source="Binding Projects" >
但这很完美
<DataTemplate >
<GroupBox Header="Projects">
<GroupBox.Resources>
<CollectionViewSource x:Key="projects" Source="Binding Projects" >
因为在应用数据模板后,组框被放置在父级中,并且可以访问其上下文
所以你所要做的就是从模板中删除样式并将其移动到模板中的元素中
注意,itemscontrol 的上下文是项目而不是控件 即 ComboBoxItem 的 ComboBoxItem 不是 ComboBox 本身,在这种情况下您应该使用控件 ItemContainerStyle 来代替
【讨论】:
【参考方案6】:是的,您可以按照 Juve 的建议使用 ElementName=Something
解决它。
但是!
如果子元素(您在其上使用这种绑定)是一个用户控件,它使用与您在父控件中指定的元素名称相同的元素名称,那么绑定会转到错误的对象!
我知道这篇文章不是解决方案,但我认为在绑定中使用 ElementName 的每个人都应该知道这一点,因为它可能是运行时错误。
<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>
【讨论】:
以上是关于从 DataTemplate 访问父 DataContext的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 DataTemplate 访问列表框中的特定项目?