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


112

私が持っている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。これは許容されないためのプロパティにユーザーコントロールのバインドの残りの部分CurrentItemBindingList自動的に。

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

回答:


161

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、テンプレート化されたアイテム内から親要素のにはアクセスできないと思いましたが、より良い解決策が見つからなかったので、試してみましたが、うまくいきました。


1
私のプロジェクトにはこの正確なコードがありますが、ViewModelsがリークしています(Finalizerが呼び出されず、コマンドバインディングがDataContextを保持しているようです)。この問題があなたにも存在することを確認できますか?
Joris Weimar

@Juveこれは機能しますが、同じテンプレートを実装するすべてのitemscontrolsに対して起動するようにこれを行うことは可能ですか?名前は一意であるため、何か不足している場合を除き、それぞれに個別のテンプレートが必要になります。
クリス

1
@Juveは私の最後のものを無視し、findancestorでrelativesourceを使用し、ancestortypeで検索することで機能しました(名前で検索しないことを除いてすべて同じです)。私の場合、テンプレートを実装するそれぞれのItemsControlsの使用を繰り返すので、私のように見えます:Command = "{Binding RelativeSource = {RelativeSource FindAncestor、AncestorType = {x:Type ItemsControl}}、Path = DataContext.OpenDocumentBtnCommand}"
Chris

48

あなたはRelativeSourceこのように親要素を見つけるために使うことができます-

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

の詳細については、このSOの質問を参照してくださいRelativeSource


10
機能するように指定する必要がありMode=FindAncestorましたが、これは機能し、コントロールの命名を回避するため、MVVMシナリオではるかに優れています。Binding="{Binding Path=DataContext.CurveSpeedMustBeSpecified, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:YourParentElementType}}}"
Aphex 2011

1
チャーム<3のように機能し、モード.net 4.6.1を指定する必要がありませんでした
user2475096

30

RelativeSourceElementName

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

RelativeSrouce

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

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

ElementName

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

このメソッドは、固体静的を参照します Nameを参照するため、スコープがそれを認識できる限り、問題はありません。このメソッドを破らないように、命名規則を守る必要があります。このアプローチは単純で、指定する必要があります。Name="..."Window / UserControlのa 。

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

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

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


18

私は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'です。ののでItemTemplate使用されDataTemplateDataContext上位レベルのは、直接アクセスすることはできません。次に、親のDCを取得するための回避策は、相対パスを使用し、ItemsControlタイプでフィルタリングすることです。


0

問題は、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のコンテキストは、コンボボックスのためのアイテムない制御すなわちComboBoxItemがあることではないあなたがコントロールの代わりにItemContainerStyleを使用する必要があり、その場合、コンボボックス自体


0

はい、あなたはそれを使ってそれを解決することができます ElementName=Something Juveの提案に従って。

だが!

子要素(この種類のバインディングを使用する)が、親コントロールで指定したのと同じ要素名を使用するユーザーコントロールである場合、バインディングは間違ったオブジェクトに移動します。

私はこの投稿が解決策ではないことを知っていますが、バインディングで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>
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.