Доступ к родительскому DataContext из DataTemplate

у меня есть ListBox который привязывается к дочерней коллекции на ViewModel. Элементы listbox стилизуются в 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. Это недопустимо, так как остальная часть пользовательского элемента управления привязывается к свойствам CurrentItem на BindingList автоматически.

как я могу указать выражение привязки внутри стиля, чтобы оно работало независимо от контекста родительских данных, являющегося представлением коллекции или одним элементом?

5 ответов


у меня были проблемы с относительным источником в 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 родительских элементов недоступны из шаблонного элемента, но поскольку я не нашел лучшего решения, я просто попытался, и он работает нормально.


можно использовать RelativeSource чтобы найти родительский элемент, вот так -

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

посмотреть это так вопрос подробнее о RelativeSource.


RelativeSource и Имяэлемента

эти два подхода могут достичь одного и того же результата,

RelativeSrouce

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

этот метод ищет элемент управления окна типа (в этом примере) в визуальном дереве, и когда он находит его, вы в основном можете получить доступ к нему DataContext С помощью Path=DataContext..... Плюсы этого метода в том, что вам не нужно привязываться к имени, и это своего рода динамический, однако изменения, внесенные в визуальное дерево, могут повлиять на этот метод и, возможно, нарушить его.

Имяэлемента

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

этот метод относится к твердому статическому Name до тех пор, пока ваша область может видеть это, вы в порядке.Конечно, вы должны придерживаться своего соглашения об именах, чтобы не нарушать этот метод.Подход совсем простой и все, что вам нужно, это указать Name="..." для вашего окна / UserControl.

хотя все три типы (RelativeSource, Source, ElementName) способны делать то же самое, но, согласно следующей статье MSDN, каждый из них лучше использовать в своей собственной области специальности.

как: укажите источник привязки

найти краткое описание каждого плюс ссылка на более подробную информацию в таблице в нижней части страницы.


Я искал, как сделать что-то подобное в 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 тип.


проблема в том, что 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 помещается в родительский и будет иметь доступ к его контексту

поэтому все, что вам нужно сделать, это удалить стиль из шаблона и переместить его в элемент в шаблоне

Примечание что контекст для itemscontrol является элементом, а не элементом управления ie ComboBoxItem для ComboBox не сам ComboBox, и в этом случае вы должны использовать элементы управления ItemContainerStyle вместо