Доступ к родительскому 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.

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

Ответ 1

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

Ответ 2

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

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

Подробнее о RelativeSource см. этот вопрос SO.

Ответ 3

RelativeSource vs. ElementName

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

RelativeSrouce

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

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

ElementName

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

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

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

Как: указать источник привязки

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

Ответ 4

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

Ответ 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}" >

потому что после применения набора данных групповой пакет помещается в родительский элемент и будет иметь доступ к его контексту

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

обратите внимание, что контекст для itemcontrol - это элемент, а не элемент управления, т.е. ComboBoxItem для ComboBox, а не сам ComboBox, в этом случае вы должны использовать элементы управления ItemContainerStyle вместо