[WPF]UserControlに対してBindingする

WPFにおけるUserControlについて、依存関係プロパティに対しBindingを実施する方法の（暫定）ベストプラクティス。

## 前提知識

### 依存関係プロパティ `Dependency Property`
**`Binding Target`として公開できる**プロパティ。
`Binding Target`とは、下で言う`Text`のこと。

``` xml
<TextBlock Text="{Binding WarningText}" />
```

ちなみに、`"{Binding WarningText}"`の箇所は`Binding Source`と呼称する。

依存関係プロパティは **UserControlのDataContext**（通常、コードビハインドでよい）にて以下のように定義する。
えげついボイラープレートだが、VisualStudioなら`propdp`と入力すればスニペットが使用できる。

``` cs
public string Text
{
    get { return (string)GetValue(TextProperty); }
    set { SetValue(TextProperty, value); }
}

public static readonly DependencyProperty TextProperty =
    DependencyProperty.Register(nameof(Text), typeof(string), typeof(ToggleAndCheckButton));

```

ここで言う`Text`は、下に定義する依存関係プロパティ`TextProperty`に対する操作を包んだラッパーであり、また外部からxaml上などで呼び出される場合に公開されるプロパティとなる。
先述の通り、`Binding Target`として機能させるためには、`DependencyProperty`としての登録が必要なため、このような記述を要する（WPFのBindingシステムに依るもの）。
* CLRプロパティ（いわゆる、通常のプロパティ）が`BindingTarget`として指定されると実行時例外になる。

依存関係プロパティについての詳しい解説は割愛するのでググってください。

## 本題
今回作成したいUserControlは以下のようなものである。
![Pasted image 20230905151539.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777804/5f2e7922-1a0d-a350-b6a0-ee1a44aac150.png)


一見何の変哲もない「チェックボックス付きボタン」だが、裏ではややこしいことをしている。XAMLが以下。

``` xml
<ToggleButton Style="{StaticResource MaterialDesignOutlinedButton}"
                  IsChecked="{Binding IsChecked}"
                  Command="{Binding Command}">
        <CheckBox IsChecked="{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ToggleButton}}"
                  Command="{Binding Command}">
            <TextBlock Text="{Binding Text}" />
        </CheckBox>
</ToggleButton>
```

ざっくり流れを追うと、
* このUserControlは`ToggleButton`と`CheckBox`の組み合わさったものである。
* `ToggleButton`と`CheckBox`は、`IsChecked`の状態を共有する。
* `ToggleButton`と`CheckBox`は、共通のCommandを叩く。
* `Text`プロパティに設定されたものが、「ボタンのテキスト」として表示される。

以上の点を踏まえると、このUserControlは、依存関係プロパティとして以下の三項を外部へ公開し、Bindingを受け付けたいということになる。
* `bool IsChecked`
* `ICommand Command`
* `string Text`

また、親の画面からは以下のように呼び出せることが期待されている。

``` xml
<Window>
	<local:MyControl IsChecked="{Binding MyControlIsChecked}"
					Command="{Binding AlphaCommand}"
					Text="{Binding AlphaText}"/>
</Window>
```

親の画面からUserControlに対し値を受け渡す、というのが一点。
その値が親のVMとBindingされている、というのがもう一点である。
特に、`IsChecked`については双方向Bindingになることが予想される。

`IsChecked`に焦点を当てて現状をざっくり図にするとこんな感じ。
![binding.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2777804/0daaa292-26b8-1780-29bd-09d55717e8b4.jpeg)


### UserControlの実装 ー XAML
いきなり重要なポイントが有る。一旦XAMLをご覧ください。

``` xml
<UserControl 
			 //中略//
			 
			  x:Name="Root">    
    <ToggleButton Style="{StaticResource MaterialDesignOutlinedButton}"
                  IsChecked="{Binding IsChecked, ElementName=Root}"
                  Command="{Binding Command}"
                  CommandParameter="{Binding IsChecked, ElementName=Root}">
        <CheckBox IsChecked="{Binding IsChecked, RelativeSource={RelativeSource AncestorType=ToggleButton}}"
                  Command="{Binding Command, ElementName=Root}"
                  CommandParameter="{Binding IsChecked, ElementName=Root}">
            <TextBlock Text="{Binding Text, ElementName=Root}" />
        </CheckBox>
    </ToggleButton>
</UserControl>
```

先述の通り、例えば`IsChecked`には、後ほど公開予定の依存関係プロパティ（のラッパー）をBindingしている。
重要なのは、**UserControlに`x:Name="Root"`と名前をつけること（名前はなんでもいい）**、**Bindingの`ElementName`にそれを指定すること**の二点。

抜粋
``` xml
<UserControl x:Name="Root">    
    <ToggleButton IsChecked="{Binding IsChecked, ElementName=Root}"
```

UserControlはDataContextを明示的に指示しない場合、呼び出し元から階層的に継承されているDataContextを遡って使用する（仮にそれを`ParentDataContext`と呼ぶ）。なのでElementName、つまりBindingSourceの参照先を指定しないと「`ParentDataContext`に`IsChecked`というプロパティはありません」と言われる。

しかし一方で、DataContextを自身に設定してしまうと（これを仮に`ChildDataContext`と呼ぶ）、今度は呼び出し側で不都合が起きる。
仮に以下のように親側で呼び出しを行っていた時、「`ChildDataContext`に`MyControlIsChecked`というプロパティはありません」と言われてしまうのである。

``` xml
<local:MyControl IsChecked="{Binding MyControlIsChecked}" />
```

要は、
* `MyControlIsChecked`は親のDataContextに存在するので、継承されている親のDataContextをそのまま参照して貰う必要がある。
* 一方、UserControl内部でBindingされている`IsChecked`はUserControl自身のDataContext（今回はコードビハインド）が保持しているため、参照先を切り替えなくてはならない

というわけで、DataContextとしては親のものを継承しつつ、UserControl内のBindingSourceの捜索先だけをUserControlのDataContextに切り替えるために、上記の手続きを踏む必要がある。

### UserControlの実装 ー コードビハインド
正直、ここは依存関係プロパティを定義するだけなので何も面白いことはない。
強いて強調するなら、繰り返しになるが**依存関係プロパティとはBindingTargetであることをWPFのバインディングシステムに登録するためのもの**ということを抑えておくといいかもしれないかも（僕は結構混乱した）。

``` cs
public partial class MyControl : UserControl
{
    public bool IsChecked
    {
        get { return (bool)GetValue(IsCheckedProperty); }
        set { SetValue(IsCheckedProperty, value); }
    }
    public static readonly DependencyProperty IsCheckedProperty =
        DependencyProperty.Register(nameof(IsChecked), typeof(bool), typeof(MyControl));

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }
    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register(nameof(Command), typeof(ICommand), typeof(MyControl));

    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register(nameof(Text), typeof(string), typeof(MyControl));

    public MyControl()
    {
        InitializeComponent();
    }
}
```

### 親側
あとは何も特別なことはない。普段通りBindingしてやればいいだけ。

``` xml
<Window>
	<local:MyControl IsChecked="{Binding MyControlIsChecked}"
					Command="{Binding AlphaCommand}"
					Text="{Binding AlphaText}"/>
</Window>
```

VMなりコードビハインドなりで、`INotifyPropertyChanged`を実装したプロパティを書いてあげればいいと思います。