从DataTemplate访问父DataContext


112

我有一个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"它可以工作,但前提是父用户控件的datacontext是a BindingListCollectionView。这是不可接受的,因为其余的用户控件会自动绑定到CurrentItemon的属性BindingList

如何在样式内部指定绑定表达式,以便无论父数据上下文是集合视图还是单个项目,绑定表达式都可以工作?

Answers:


161

我在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>

这也适用,如果你把该按钮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无法从模板项目中访问父元素,但是由于找不到更好的解决方案,因此我尝试了一下,并且工作正常。


1
我的项目中有这个确切的代码,但是它泄漏了ViewModels(未调用Finalizer,命令绑定似乎保留了DataContext)。您是否也可以验证此问题?
2013年

@Juve可以工作,但是有可能这样做,以便对实现相同模板的所有项控件触发?名称是唯一的,因此我们将需要为每个名称使用单独的模板,除非我遗漏了某些内容。
克里斯

1
@Juve无视我的最后一个,我通过使用带有findancestor的relativesource并按祖先类型进行搜索来工作(除了不按名称搜索之外,其他都一样)。在我的情况下,我重复使用ItemsControls来实现模板,因此我的看起来像这样:Command =“ {Binding RelativeSource = {RelativeSource FindAncestor,AncestorType = {x:Type ItemsControl}},Path = DataContext.OpenDocumentBtnCommand}”
Chris

48

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

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

有关的更多详细信息,请参见此SO问题RelativeSource


10
我必须指定Mode=FindAncestor它可以正常工作,但这在MVVM场景中有效并且更好,因为它避免了命名控件。Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:YourParentElementType}}}"
Aphex

1
就像<3的魅力一样工作,而不必指定模式.net 4.6.1
user2475096

30

RelativeSourceElementName

这两种方法可以达到相同的结果,

相对榨汁

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

该方法在可视树中查找Window类型的控件(在此示例中),当找到它时,您基本上可以DataContext使用来访问它Path=DataContext....。关于此方法的优点是,您无需与名称绑定,并且名称是动态的,但是,对可视化树所做的更改可能会影响此方法,甚至有可能破坏该方法。

元素名称

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

这个方法指的是固态静态对象Name,只要您的示波器可以看到它就可以了。一个Name="..."代表您的Window / UserControl。

尽管这三种类型(RelativeSource, Source, ElementName)都可以做相同的事情,但是根据下面的MSDN文章,每种类型最好在各自的专业领域中使用。

如何:指定绑定源

在页面底部的表中找到每个内容的简要说明以及指向更多详细信息的链接。


18

我正在搜索如何在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使用DataTemplateDataContext无法直接访问上层的。然后,获取父级DC的解决方法是使用相对路径并按ItemsControl类型进行过滤。


0

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

这意味着如果您绑定到模板,那么您将绑定到没有上下文的对象。

但是,如果您将一个元素放在模板中,则当该元素应用于父元素时,它将获得上下文,然后绑定便可以工作

所以这行不通

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

但这很好用

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

因为在应用了datatemplate之后,groupbox放置在父级中,并且可以访问其Context

因此,您要做的就是从模板中删除样式并将其移至模板中的元素中

请注意,itemscontrol的上下文是项目而不是控件,即ComboBox的ComboBoxItem不是ComboBox本身,在这种情况下,您应该使用控件ItemContainerStyle代替


0

是的,您可以使用 ElementName=Something按照Juve的建议。

但!

如果子元素(您在其上使用这种绑定)是一个用户控件,并且该控件使用与您在父控件中指定的元素名称相同的名称,则绑定将转到错误的对象!

我知道这篇文章不是解决方案,但我认为在绑定中使用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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.