wpf 从数据模板访问父数据上下文

ua4mk5z4  于 2023-02-13  发布在  其他
关注(0)|答案(7)|浏览(235)

我有一个ListBox绑定到ViewModel上的一个子集合。列表框项在DataTemplate中的样式基于父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是BindingListCollectionView。这是不可接受的,因为用户控件的其余部分将自动绑定到BindingList上的CurrentItem的属性。

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

gt0wga4j

gt0wga4j1#

我在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不能从模板项中访问,但是由于我没有找到更好的解决方案,我只是尝试了一下,它工作得很好。

jqjz2hbq

jqjz2hbq2#

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

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

有关RelativeSource的更多详细信息,请参见this SO question

agxfikkp

agxfikkp3#

相对源元素名称

这两种方法可以实现相同的结果,

相对源

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
在页面底部的表格中找到每个选项的简要说明以及指向更详细选项的链接。

k4emjkb1

k4emjkb14#

我正在搜索如何在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类型筛选。

hvvq6cgz

hvvq6cgz5#

问题是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表示ComboBox而不是ComboBox本身,在这种情况下,您应该使用控件ItemContainerStyle

yhqotfr8

yhqotfr86#

是的,你可以按照尤文的建议使用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>
4si2a6ki

4si2a6ki7#

也可以通过.执行。使用此操作符,您可以访问Source。此处
可以选择使用句点(.)路径绑定到当前源。例如,Text="{Binding}”等效于Text="{Binding Path=.}"。
例如,我喜欢这样做(将DataContext放入Tag中,然后访问parentDataContext):

资源字典.xaml

<Style x:Key="SomeControl" TargetType="ItemsControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <Grid Tag="{Binding .}">
                        <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=Tag.DataContext.SomeName}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

主窗口.xaml

<ItemsControl Style="{StaticResource SomeControl}"/>

主窗口.xaml.cs

public partial class MainWindow : Window
{
   public MainWindow()
   {
      //...
      SomeName = "Your value";
   } 
   public string SomeName { get; set; }
}

相关问题