目次
View とデータの連携のこと。
View をいじれば通知が飛んでデータが書き換わり、データの構造によって View が変化する(ツリー構造や階層構造とか)。
2021 / 05 / 01 修正と追記
基本的な使い方は下記のとおり。
<WindowResources>
<localsampleViewMode xKey="sampleVM"/>
</WindowResources>
<Grid DataContext="{StaticResource sampleVM}">
<TextBlock Text="{Binding DisplayText}" />
</Grid>
DataContext にビューモデルのインスタンスを割り当てておくことでバインディング時に「どのビューモデルか」っていうところを省略できる。
他のバインディングをまとめておく。
表中に出てくるバインディングターゲットとバインディングソースは後述する。
また、バインディングターゲットを「ターゲット」、バインディングソースを「ソース」と略記する。
バインディング記法 |
意味 |
{Binding ElementName=sampleVM txtblk Path=Display Text} |
sampleVM の DisplayText プロパティをバインディングする。明示的にビューモデルを指定する書き方。 View 要素である TextBlock (txtblk はインスタンス名)のプロパティ Text をバインディングする。 |
{Binding Source={StaticResource sampleVM}, Path=Display} |
sampleVM の DisplayText プロパティをバインディングする。明示的に ViewModel を指定する書き方。 |
{Binding DisplayText Mode=Default/OneTime/ OneWay/OneWayToSource/TwoWay} |
DisplayText をバインディングし、そのバインディング方向を指定する。ViewModel は DataContext としてすでにバインドされたものを暗黙的に対象としている。 ・Default:デフォルト。OneWay と同じ。 ・OneTime:アプリが始まったとき、または DataContext が変更されたときにターゲットのプロパティを更新する。 ・OneWay:ソースのプロパティが変更された場合にのみ、ターゲットのプロパティを更新する。 ・OneWayToSource:ターゲットのプロパティが変更されたときに、ソースのプロパティを更新する。 ・TwoWay:ターゲット、またはソースのプロパティのいずれかが変更されたとき、ターゲット、またはソースのプロパティを更新する。 |
{Binding DisplayText, Converter={StaticResource converter}, ConverterParameter=X2} |
DisplayText をバインディングする。バインディング時、指定した Converter によって値を変換できる。また、変換時の引数を ConverterParameter で指定できる。本例では X2 という文字列を渡している。想定しているのは、long.ToString("X2") という変換である。 |
上記の例で暗黙的に ViewModel を対象にしたりしているが、別に明示的に ViewModel を指定しても、ViewModel じゃなくて View のプロパティが対象でもバインディング方向や Converter は使用できる。
2021 / 04 / 17 追記
データバインディングの Converter についてまとめた。
backacheengineer.hatenablog.com
2021 / 05 / 01 追記
バインディング時の RelativeSource について勉強したので更新する。こいつは書くこと多いんで章を設ける。
バインディングにおける RelativeSource
バインディングターゲットからの相対的な位置にいるプロパティをバインディングソースにしたいときに使う。(ターゲットとかソースについては次章)
下記がその記法である。RelativeSouce には Mode が存在するため、一つの記法に4種類の書き方がある。
バインディング記法 |
意味 |
{Binding RelativeSource={RelativeSource Mode=Self}, Path=Value} |
自分自身のプロパティ「Value」を対象にバインディングする。(いつ使うんだこれ) |
{Binding RelativeSource={RelativeSource Mode=PreviousData}} |
以前のデータ項目をバインディングする(っぽい)。使った例すら見たことなく、詳細は不明。そんな使わなさそうなので放置してよさげ。 |
{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Value} |
ControlTemplate の対象になっている親タイプのプロパティ「Value」を対象にバインディングする。 これは ControlTemplate じゃないと使えないので注意。DataTemplate など他の Template だとソースが見つからないとバインディングエラーとなる。なお、MSDNに制約の明記はない。 |
{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=HOGE}, Path=Value |
自身の先祖にあたる HOGE というクラスを探して、そのプロパティ「Value」を対象にバインディングする。探し方は AncestorLevel という階層指定も可能。いずれか、または両方を指定すればよい。なお、AncestorType や AncestorLevel は Mode が FindAncestor じゃないと使用できない。 |
PreviousData だけ情報がないに等しいが、使用頻度の高さでいえば最後の「FindAncestor」が圧倒的になると考えられるので、とりあえず良しとする。
この RelativeSource だが、使用する機会は Template をいじくるときのため、「デザインを作りこむ」となると必ず必要といっても過言ではないくらい大事なものになる。こいつがわかってこれば、必ず力になる。デザインはユーザにとって最も重要なので、プログラマだからといって勉強しないという選択肢はない。
TemplatedParent と TemplateBinding について
これは勉強中につっかかりまくったのでここでまとめておく。前述した制約「ControlTemplate」じゃないと使えないなど・・・。
この TemplatedParentだが、これは {TemplateBinding Value} と似た意味である。下記がこの二つの差分になる。
項目 |
説明 |
TemplatedParent |
下記のように、バインディング方向を指定できる。 {Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Mode=TwoWay ... } |
TemplateBinding |
バインディング方向が指定できない。OneWayに固定。 |
TemplateBinding におけるバインディング方向が OneWay のみという制約は MSDN に明記されている。詳細は下記を参照する。TemplateBinding はControlTemplate のまとめで触れたいため。
TemplateBinding Markup Extension - WPF .NET Framework | Microsoft Docs
バインディングする際に注意して覚えたいのがバインディングターゲットとバインディングソースである。
よくこんがらがるのでまとめる。
- バインディングターゲット:こっちがビュー側。つまり、TextBlock や Label などのこと。
- バインディングソース:こっちがビューモデル側。データの基になる。
ソース(データの基)の値をターゲット( View )に反映するのが基本である。
そして、バインディング方向はデフォルトで OneWay なので、「データの基が変わったら、表示も切り替わる」ということになる。
その逆、「表示を変えたら、データも変わる」にする場合、バインディング方向を OneWayToSource か、TwoWay にする必要がある。
バインディング方向によって更新されるものが変化するため、方向は プロパティごとにしっかり考えて決める必要がある。大枠は下記と考えられる。
- 編集可能なコントロール:OneWayToSource
- 編集不可なコントロール:OneWay
編集可能ということは、ユーザの入力を受け取るということである。つまり、その入力はデータ側に渡されることが多い。
よって、ターゲット( View )のプロパティが変更されたなら、ソース(データの基)の値が書き換わってほしいのだから、「表示を変えたら、データも変わる」OneWayToSource が適している。
編集不可なコントロールに値がバインディングされているということは、データの基を常に表示してほしいものである。
編集可能なコントロールによってソースの値が変更され、そのソースの値が更新されたことによって値が書き換わるソースが該当する。
例えば、フィルタリング機能のチェックボックスにチェックを入れたら表示一覧がフィルタリングされるときの「表示一覧」側である。
表示一覧は常にデータの基を表示してほしく、別の値によって中身が変化することが多い。よって、「データの基が変わったら、表示も切り替わる」OneWay が適している。
2021 / 04 / 17 追記
TwoWay を使うときは View でいじくったデータを保存、および復元したいとき。
データを保存するだけなら OneWayToSource でいいが、その後に View で復元する際は TwoWay じゃないと View 側が更新されない。
依存関係プロパティ
データバインディングで知っておくべき言葉である「依存関係プロパティ」。英語だと「DependencyProperty」。
実は、
バインディングターゲットは依存関係プロパティでないといけない、という制約がある。
なお、バインディングソース側は別に依存関係プロパティでなくてよい。つまり、ただのプロパティでよい。
カスタムコントロールやユーザーコントロールを作って表示対象にバインドさせたいときは依存関係プロパティを用意する必要がある。
割と定型的に定義できるのでまとめておく(サンプルはユーザーコントロールを想定)。
<summary>
</summary>
public static DependencyProperty SampleProperty { get; private set; }
<summary>
</summary>
public string Sample
{
get => (string)GetValue(SampleProperty);
set => SetValue(SampleProperty, value);
}
<summary>
</summary>
static Constructor()
{
SampleProperty = DependencyProperty.Register(nameof(Sample), typeof(string), typeof(Constructor), new PropertyMetadata(null, onSampleChange));
}
<summary>
</summary>
public Constructor()
{
InitializeComponent();
}
<summary>
</summary>
static void onSampleChange(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
(obj as Constructor).border.Child = (string)args.NewValue;
}
上記はもはやテンプレートに近いと思っている。PropetyMetadata の初期値とそのコールバック関数だけは都度違うので、そこは開発するものによって柔軟に変えていく。
依存関係プロパティとして登録する名前が重複したり、デフォルト値が許容できない値(依存関係プロパティの型が bool なのに初期値が null )はエラーとなる。