RelativeSourceでWPFバインディングを使用するにはどうすればよいですか?


回答:


783

オブジェクトの別のプロパティにバインドする場合:

{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}

祖先のプロパティを取得したい場合:

{Binding Path=PathToProperty,
    RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}

テンプレート化された親のプロパティを取得する場合(したがって、ControlTemplateで双方向のバインディングを実行できます)

{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}

または、より短い(これはOneWayバインディングでのみ機能します):

{TemplateBinding Path=PathToProperty}

15
この「{Binding Path = PathToProperty、RelativeSource = {RelativeSource AncestorType = {x:Type typeOfAncestor}}}」の場合、「AncestorType」の前に「Mode = FindAncestor」が必要なようです
EdwardM

1
どんなテクノロジー?WPFでは、これを指定すると推測されますAncestorType
安倍ハイデブレヒト2017

2
@EdwardMに同意します。FindAncestorAncestorTypeにを省略すると、「RelativeSourceがFindAncestorモードではありません」というエラーが表示されます。(VS2013では、コミュニティバージョン)
kmote 2017

1
@kmote、これは.net 3.0以降で機能しており、kaxamlでこのように機能することをもう一度確認しました...繰り返しますが、どのテクノロジーを使用していますか?XAMLプロセッサはWPF / Silverlight / UWPとは異なるため、テクノロジーによって結果が異なる場合があります。VS Communityについてもおっしゃっていましたが、おそらくIDEの警告ですが、実行時に機能しますか?
安倍ハイデブレヒト2017

6
ここで、RelativeSourceのDataContextのプロパティにバインドする場合は、明示的に指定する必要があることに注意してください{Binding Path=DataContext.SomeProperty, RelativeSource=...。これは、DataTemplate内で親のDataContextにバインドしようとしたときに、初心者としては少し予想外でした。
DrEsperanto 2018年

133
Binding RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ItemType}
}
...

のデフォルト属性RelativeSourceModeプロパティです。有効な値の完全なセットをここに示します(MSDNから):

  • PreviousData表示されているデータ項目のリスト内の前のデータ項目(データ項目を含むコントロールではない)をバインドできます。

  • TemplatedParentテンプレート(データバインド要素が存在する)が適用される要素を参照します。これは、TemplateBindingExtensionの設定に似ており、Bindingがテンプレート内にある場合にのみ適用されます。

  • Selfバインディングを設定する要素を参照し、その要素のあるプロパティを同じ要素の別のプロパティにバインドできるようにします。

  • FindAncestorデータバインドされた要素の親チェーン内の祖先を参照します。これを使用して、特定のタイプまたはそのサブクラスの祖先にバインドできます。これは、AncestorTypeまたはAncestorLevel、あるいはその両方を指定する場合に使用するモードです。


128

以下は、MVVMアーキテクチャのコンテキストでのより視覚的な説明です。

ここに画像の説明を入力してください


19
私は何か見落としてますか?シンプルで明確なグラフィックをどのように考えることができますか?1:左側のボックスの意味は右側のボックスと実際には関連していません(なぜViewModel内に.csファイルがあるのですか)2:これらのDataContext矢印は何を指しているのですか?3:MessageプロパティがViewModel1にないのはなぜですか?そして最も重要な5:TextBlockに同じDataContextがすでにある場合、なぜウィンドウのDataContextに到達するためにRelativeSourceバインディングが必要なのですか?私は明らかにここで何かを見逃しているので、私はかなりばかげているか、このグラフィックは誰もが思うほど単純で明確ではありません!教えてください
MarkusHütter16年

2
@MarkusHütterこの図は、ネストされたビューと対応するViewModelのグループを示しています。View1のDataContextはViewModel1ですが、BaseViewModelのプロパティにバインドする必要があります。BaseViewModelはBaseView(ウィンドウ)のDataContextであるため、ウィンドウである最初の親コンテナーを見つけ、そのDataContextを取得することで、これを行うことができます。
mcargille 2016

6
@MatthewCargille私は非常によく、何を知っているはずの私のポイントではなかったことを意味します。しかし、XAMLとMVVMをよく知らない人の立場に身を置くと、これは単純で明確ではないことがわかります。
MarkusHütter16年

1
@MarkusHütterに同意する必要があります。ちなみに、左側のバインディングは次のように単純にすることができます:({Binding Message}もう少し単純です...)
florien

@florien少なくとも私のユースケースではそうは思いません。(データベースからロードされた)ドロップダウンメニューのオプションのリストを取得するために、MainWindowのDataContext(my viewmodelクラス)を参照する必要があるDataTemplateがあります。DataTemplateは、データベースからも読み込まれるモデルオブジェクトにバインドされていますが、選択されたオプションにのみアクセスできます。Path=DataContext.Messageバインディングを機能させるには、明示的に設定する必要がありました。これは、width / height /などへの相対バインディングを実行できることを考えると、理にかなっています。コントロールの。
DrEsperanto 2018年

47

Bechir Bejaouiは、WPFでのRelativeSourcesの使用例を彼の記事で公開しています。

RelativeSourceは、特定のオブジェクトのプロパティをオブジェクト自体の別のプロパティにバインドしようとするとき、オブジェクトのプロパティをその親の別の親にバインドしようとするときに、特定のバインドの場合に使用されるマークアップ拡張機能です。カスタムコントロール開発の場合、および最後に一連のバインドされたデータの差分を使用する場合に、依存関係プロパティ値をXAMLの一部にバインドするとき。これらの状況はすべて、相対ソースモードとして表されます。これらのケースを1つずつ公開します。

  1. モードセルフ:

この場合を想像してみてください。長方形の高さが常に幅と等しいことを望んでいます。正方形だとしましょう。要素名を使用してこれを行うことができます

<Rectangle Fill="Red" Name="rectangle" 
                Height="100" Stroke="Black" 
                Canvas.Top="100" Canvas.Left="100"
                Width="{Binding ElementName=rectangle,
                Path=Height}"/>

しかし、上記の場合、バインディングオブジェクトの名前、つまり長方形を示す必要があります。RelativeSourceを使用して、同じ目的に異なる方法で到達できます。

<Rectangle Fill="Red" Height="100" 
               Stroke="Black" 
               Width="{Binding RelativeSource={RelativeSource Self},
               Path=Height}"/>

その場合、バインディングオブジェクトの名前について言及する義務はありません。また、高さが変更されるたびに、幅は常に高さに等しくなります。

Widthを高さの半分になるようにパラメーター化したい場合は、バインディングマークアップ拡張機能にコンバーターを追加することでこれを行うことができます。ここで別のケースを想像してみましょう:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
               Path=Parent.ActualWidth}"/>

上記のケースは、特定の要素の特定のプロパティを直接の親プロパティの1つに関連付けるために使用されます。この要素は、Parentと呼ばれるプロパティを保持するためです。これにより、FindAncestorである別の相対ソースモードにつながります。

  1. モードFindAncestor

この場合、特定の要素のプロパティはその親の1つであるOf Corseに関連付けられます。上記のケースとの主な違いは、プロパティを結び付けるために階層内の祖先のタイプと祖先のランクを決定するのはあなた次第であるという事実です。ちなみに、このXAMLで遊んでみてください

<Canvas Name="Parent0">
    <Border Name="Parent1"
             Width="{Binding RelativeSource={RelativeSource Self},
             Path=Parent.ActualWidth}"
             Height="{Binding RelativeSource={RelativeSource Self},
             Path=Parent.ActualHeight}">
        <Canvas Name="Parent2">
            <Border Name="Parent3"
            Width="{Binding RelativeSource={RelativeSource Self},
           Path=Parent.ActualWidth}"
           Height="{Binding RelativeSource={RelativeSource Self},
              Path=Parent.ActualHeight}">
               <Canvas Name="Parent4">
               <TextBlock FontSize="16" 
               Margin="5" Text="Display the name of the ancestor"/>
               <TextBlock FontSize="16" 
                 Margin="50" 
            Text="{Binding RelativeSource={RelativeSource  
                       FindAncestor,
                       AncestorType={x:Type Border}, 
                       AncestorLevel=2},Path=Name}" 
                       Width="200"/>
                </Canvas>
            </Border>
        </Canvas>
     </Border>
   </Canvas>

上記の状況は、一連のボーダー内に埋め込まれた2つのTextBlock要素と、階層的な親を表すcanvas要素の状況です。2番目のTextBlockは、相対ソースレベルで指定された親の名前を表示します。

したがって、AncestorLevel = 2をAncestorLevel = 1に変更して、何が起こるかを確認してください。次に、祖先のタイプをAncestorType = BorderからAncestorType = Canvasに変更して、何が起こるかを確認してください。

表示されるテキストは、祖先のタイプとレベルに応じて変化します。次に、祖先レベルが祖先のタイプに適さない場合はどうなりますか?これは良い質問です。あなたが尋ねようとしているのはわかっています。応答は例外ではなく、TextBlockレベルでは何も表示されません。

  1. TemplatedParent

このモードでは、特定のControlTemplateプロパティを、ControlTemplateが適用されているコントロールのプロパティに関連付けることができます。ここで問題をよく理解するには、以下の例をご覧ください

<Window.Resources>
<ControlTemplate x:Key="template">
        <Canvas>
            <Canvas.RenderTransform>
                <RotateTransform Angle="20"/>
                </Canvas.RenderTransform>
            <Ellipse Height="100" Width="150" 
                 Fill="{Binding 
            RelativeSource={RelativeSource TemplatedParent},
            Path=Background}">

              </Ellipse>
            <ContentPresenter Margin="35" 
                  Content="{Binding RelativeSource={RelativeSource  
                  TemplatedParent},Path=Content}"/>
        </Canvas>
    </ControlTemplate>
</Window.Resources>
    <Canvas Name="Parent0">
    <Button   Margin="50" 
              Template="{StaticResource template}" Height="0" 
              Canvas.Left="0" Canvas.Top="0" Width="0">
        <TextBlock FontSize="22">Click me</TextBlock>
    </Button>
 </Canvas>

特定のコントロールのプロパティをそのコントロールテンプレートに適用する場合は、TemplatedParentモードを使用できます。このマークアップ拡張機能に似たものもあります。これは、最初のものの一種であるTemplateBindingですが、TemplateBindingは、最初の実行直後に評価されるTemplatedParentとは対照的に、コンパイル時に評価されます。下の図で説明したように、背景とコンテンツはボタン内からコントロールテンプレートに適用されます。


私にとって非常に優れた例として、Find Ancestorを使用して、親のデータコンテキスト内のコマンドに関連付けましたListView。親には、そのListView下にさらに2つのレベルがあります。これは私が各後続の各VMにデータを渡す防ぐ助けListViewさんDataTemplate
カレブW.

34

WPF RelativeSourceバインディングでは3つを公開しますpropertiesセットをします。

1.モード:これはenum 4つの値を持つ可能性があります。

a。PreviousData(value=0):propertyバインドされた値に以前の値を割り当てます

b。TemplatedParent(value=1): これは、templates、任意のコントロールのをし、の値/プロパティにバインドするcontrolます。

たとえば、次のように定義しますControlTemplate

  <ControlTemplate>
        <CheckBox IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
 </ControlTemplate>

c。Self(value=2):からバインドしたいときselfまたはaproperty自己の。

例:オンに設定checkboxCommandParameterながら、チェック状態を送信CommandCheckBox

<CheckBox ...... CommandParameter="{Binding RelativeSource={RelativeSource Self},Path=IsChecked}" />

d。FindAncestor( value=3):親からバインドしたいですcontrolVisual Tree

例: if がチェックさcheckboxれているrecords場合にバインドしますgridheader checkbox

<CheckBox IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}, Path=DataContext.IsHeaderChecked, Mode=TwoWay}" />

2. AncestorType: モードがFindAncestor祖先のタイプを定義するとき

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid}}

3. AncestorLevel: モードがFindAncestor祖先のレベルである場合(に同じタイプの親が2つある場合visual tree

RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type iDP:XamDataGrid, AncestorLevel=1}}

上記はすべてのユースケースですRelativeSource binding

こちらが参考リンクです。


2
これは私にとってはうまくいきました:<DataGridCheckBoxColumn Header = "Paid" Width = "35" Binding = "{Binding RelativeSource = {RelativeSource Mode = FindAncestor、AncestorType = {x:Type Window}}、Path = DataContext.SelectedBuyer.IsPaid 、Mode = OneWay} "/>親ウィンドウのselectedbuyer.IsPaidプロパティにバインドしようとしていたところ
Michael K

21

TemplatedParentを忘れないでください。

<Binding RelativeSource="{RelativeSource TemplatedParent}"/>

または

{Binding RelativeSource={RelativeSource TemplatedParent}}


16

ライブラリを作成して、WPFのバインディング構文を簡素化し、RelativeSourceを使いやすくしました。下記は用例です。前:

{Binding Path=PathToProperty, RelativeSource={RelativeSource Self}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
{Binding Path=PathToProperty, RelativeSource={RelativeSource TemplatedParent}}
{Binding Path=Text, ElementName=MyTextBox}

後:

{BindTo PathToProperty}
{BindTo Ancestor.typeOfAncestor.PathToProperty}
{BindTo Template.PathToProperty}
{BindTo #MyTextBox.Text}

メソッドバインディングを簡略化する方法の例を次に示します。前:

// C# code
private ICommand _saveCommand;
public ICommand SaveCommand {
 get {
  if (_saveCommand == null) {
   _saveCommand = new RelayCommand(x => this.SaveObject());
  }
  return _saveCommand;
 }
}

private void SaveObject() {
 // do something
}

// XAML
{Binding Path=SaveCommand}

後:

// C# code
private void SaveObject() {
 // do something
}

// XAML
{BindTo SaveObject()}

ライブラリはここにあります:http : //www.simplygoodcode.com/2012/08/simpler-wpf-binding.html

メソッドバインディングに使用する 'BEFORE'の例でRelayCommandは、最後にチェックしたWPFのネイティブ部分ではないコードを使用してコードが既に最適化されていることに注意してください。それがなければ、「BEFORE」の例はさらに長くなるでしょう。


2
この種の手持ち演習は、XAMLの弱点を示しています。やり方はあまりにも複雑。
dudeNumber4 2017年

16

いくつかの便利な小片:

主にコードでこれを行う方法は次のとおりです。

Binding b = new Binding();
b.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, this.GetType(), 1);
b.Path = new PropertyPath("MyElementThatNeedsBinding");
MyLabel.SetBinding(ContentProperty, b);

コードビハインドのバインディング相対ソースからこれを大幅にコピーしました。

また、例としては、MSDNページは非常に優れています。RelativeSourceクラス


5
私のWPFの漠然とした記憶は、コードでバインディングを実行することは、おそらく通常は最善ではないということです。
ネイサンクーパー


10

すべての回答を読んだわけではありませんが、ボタンの相対的なソースコマンドバインディングの場合に備えて、この情報を追加したいだけです。

で相対ソースを使用する場合Mode=FindAncestor、バインディングは次のようにする必要があります。

Command="{Binding Path=DataContext.CommandProperty, RelativeSource={...}}"

パスにDataContextを追加しないと、実行時にプロパティを取得できません。


9

これは、空のデータグリッドで機能するこのパターンの使用例です。

<Style.Triggers>
    <DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}" Value="0">
        <Setter Property="Background">
            <Setter.Value>
                <VisualBrush Stretch="None">
                    <VisualBrush.Visual>
                        <TextBlock Text="We did't find any matching records for your search..." FontSize="16" FontWeight="SemiBold" Foreground="LightCoral"/>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Setter.Value>
        </Setter>
    </DataTrigger>
</Style.Triggers>

6

要素がビジュアルツリーの一部でない場合、RelativeSourceは機能しません。

この場合、Thomas Levesqueが開拓した別のテクニックを試す必要があります。

彼のブログには、[WPF] DataContextが継承されていない場合にデータにバインドする方法のソリューションがあります。そして、それは絶対に見事に機能します!

万が一、彼のブログがダウンした場合、付録Aには、 彼の記事の

ここにはコメントしないでください。 彼のブログ投稿に直接コメントして

付録A:ブログ投稿のミラー

WPFのDataContextプロパティは、割り当てた要素のすべての子によって自動的に継承されるため、非常に便利です。したがって、バインドする各要素に再度設定する必要はありません。ただし、DataContextにアクセスできない場合もあります。これは、ビジュアルツリーまたは論理ツリーの一部ではない要素で発生します。これらの要素にプロパティをバインドするのは非常に難しい場合があります…

簡単な例で説明しましょう。DataGridに製品のリストを表示します。グリッドで、ViewModelによって公開されたShowPriceプロパティの値に基づいて、Price列を表示または非表示にできるようにします。明らかなアプローチは、列のVisibilityをShowPriceプロパティにバインドすることです。

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding ShowPrice,
                Converter={StaticResource visibilityConverter}}"/>

残念ながら、ShowPriceの値を変更しても効果はなく、列は常に表示されます。なぜですか?Visual Studioの[出力]ウィンドウを見ると、次の行がわかります。

System.Windows.Dataエラー:2:ターゲット要素の管理FrameworkElementまたはFrameworkContentElementが見つかりません。BindingExpression:Path = ShowPrice; DataItem = null; ターゲット要素は 'DataGridTextColumn'(HashCode = 32685253);です。ターゲットプロパティは 'Visibility'(タイプ 'Visibility')です

メッセージはかなり不可解ですが、意味は実際には非常に簡単です。列がDataGridの視覚的または論理的なツリーに属していないため、WPFはDataContextを取得するために使用するFrameworkElementを知りません。

たとえば、RelativeSourceをDataGrid自体に設定することで、バインディングを微調整して目的の結果を得ることができます。

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding DataContext.ShowPrice,
                Converter={StaticResource visibilityConverter},
                RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}"/>

または、ShowPriceにバインドされたCheckBoxを追加し、要素名を指定して列の可視性をIsCheckedプロパティにバインドしようとすることができます。

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding IsChecked,
                Converter={StaticResource visibilityConverter},
                ElementName=chkShowPrice}"/>

しかし、これらの回避策はどれも機能していないようです。常に同じ結果が得られます…

この時点で、実行可能な唯一のアプローチは、コードビハインドで列の可視性を変更することであると思われます。MVVMパターンを使用する場合は、通常、これを避けたいと思います…しかし、少なくともそれほどあきらめたくないでしょう考慮すべき他のオプションがありますが😉

私たちの問題の解決策は実際には非常に単純で、Freezableクラスを利用しています。このクラスの主な目的は、変更可能で読み取り専用の状態のオブジェクトを定義することですが、この場合の興味深い機能は、Freezableオブジェクトがビジュアルツリーまたは論理ツリーにない場合でもDataContextを継承できることです。この動作を可能にする正確なメカニズムはわかりませんが、バインディングを機能させるためにそれを利用します…

アイデアは、Freezableを継承し、Data依存プロパティを宣言するクラス(私はすぐに明らかになるはずの理由でBindingProxyと呼びました)を作成することです。

public class BindingProxy : Freezable
{
    #region Overrides of Freezable

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }

    #endregion

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}

次に、DataGridのリソースでこのクラスのインスタンスを宣言し、Dataプロパティを現在のDataContextにバインドします。

<DataGrid.Resources>
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>

最後のステップは、このBindingProxyオブジェクト(StaticResourceで簡単にアクセス可能)をバインディングのソースとして指定することです。

<DataGridTextColumn Header="Price" Binding="{Binding Price}" IsReadOnly="False"
                Visibility="{Binding Data.ShowPrice,
                Converter={StaticResource visibilityConverter},
                Source={StaticResource proxy}}"/>

パスがBindingProxyオブジェクトに相対するようになったため、バインディングパスの前に「Data」が付いていることに注意してください。

バインディングが正しく機能し、ShowPriceプロパティに基づいて列が適切に表示または非表示になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.