web-dev-qa-db-ja.com

DataTemplateから親DataContextにアクセスする

ViewModelの子コレクションにバインドするListBoxがあります。リストボックスアイテムは、親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のプロパティに自動的にバインドするため、受け入れられません。

コレクション内の親データコンテキストまたは単一のアイテムに関係なく動作するように、スタイル内でバインディング式を指定するにはどうすればよいですか?

97
Marius

Silverlightの相対ソースに問題がありました。検索して読んだ後、追加のバインディングライブラリを使用しないと適切なソリューションが見つかりませんでした。しかし、ここでは親DataContextへのアクセスを取得するための別のアプローチは、データコンテキストを知っている要素を直接参照することです。独自の命名を尊重し、コンポーネント間でtemplates/stylesを頻繁に再利用しない限り、Binding ElementNameを使用して非常にうまく機能します。

<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にアクセスできないと思っていましたが、より良い解決策が見つからなかったので、試したところ、うまくいきました。

142
Juve

次のように、RelativeSourceを使用して親要素を見つけることができます-

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

RelativeSourceの詳細については、 this SO question を参照してください。

43
akjoshi

RelativeSourcevs.ElementName

これら2つのアプローチは同じ結果を達成できますが、

RelativeSrouce

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

このメソッドは、ビジュアルツリーで(この例では)型ウィンドウのコントロールを探し、それが見つかった場合、Path=DataContext....を使用してDataContextにアクセスできます。このメソッドの長所は、名前に縛られる必要がなく、一種の動的であるということですが、ビジュアルツリーに加えられた変更はこのメソッドに影響を与え、場合によっては破損する可能性があります。

ElementName

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

このメソッドは、静的なNameを参照します。したがって、スコープがそれを見ることができる限り、問題ありません。もちろん、このメソッドを壊さないように、命名規則に固執する必要があります。必要なのは、Window/UserControlにName="..."を指定することです。

3つのタイプ(RelativeSource, Source, ElementName)はすべて同じことを実行できますが、次のMSDNの記事によると、それぞれの専門分野で使用する方が適切です。

方法:バインディングソースを指定する

ページの下部にある表で、それぞれの簡単な説明と詳細へのリンクを見つけます。

27
Mehrad

私は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に自動的に設定されるデータコンテキストがあり、このデータコンテキストには2つのプロパティがあります:MyItems-これはコレクションです-、および1つのコマンド 'CustomCommand'。 ItemTemplateDataTemplateを使用しているため、上位レベルのDataContextは直接アクセスできません。次に、親のDCを取得する回避策は、相対パスを使用し、ItemsControlタイプでフィルター処理します。

17
hmadrigal

問題は、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のコンテキストはコントロールではなくアイテムです。つまり、ComboBox自体ではなくComboBoxのComboBoxItemです。この場合、代わりにコントロールのItemContainerStyleを使用する必要があります。

0
MikeT