Доступ к родительскому DataContext из DataTemplate - PullRequest
96 голосов
/ 04 августа 2010

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

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

Ответы [ 5 ]

142 голосов
/ 23 августа 2010

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

42 голосов
/ 04 августа 2010

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

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

См. этот вопрос для получения более подробной информации о RelativeSource.

26 голосов
/ 05 марта 2015

RelativeSource против 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 каждый из них лучше использовать в своей области специализации.

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

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

17 голосов
/ 03 июня 2011

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

0 голосов
/ 08 июня 2018

проблема в том, что DataTemplate не является частью элемента, к которому он применяется.

это означает, что если вы привязываетесь к шаблону, вы привязываетесь к чему-то, что не имеет контекста.

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

, поэтому это не будет работать

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

, ноэто прекрасно работает

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

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

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

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

...