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

У меня есть 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 автоматически.

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

7 ответов

Решение

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

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

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

Посмотрите этот ТАК вопрос для более подробной информации о RelativeSource,

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="..." для вашего окна /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 тип.

Да, вы можете решить эту проблему с помощью 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>

Также можно сделать через.. С помощью этого оператора вы можете получить доступ кSource. здесь

При необходимости путь с точкой (.) можно использовать для привязки к текущему источнику. Например, Text="{Binding}" эквивалентен Text="{Binding Path=.}".

Например, мне нравится делать так (поместить внутрьTagа затем получить доступ к родительскому DataContext):

ResourceDictionary.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>

MainWindow.xaml:

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

MainWindow.xaml.cs:

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

Проблема в том, что 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

Другие вопросы по тегам