WPFのユーザーコントロールは依存関係プロパティを作らなくても独自型をバインドできるという話

# 複数のデータをバインドしたいから独自型をつくりたい！
WPFのユーザーコントロールを作るとき、そのユーザーコントロールへのデータバインディングは、ユーザーコントロールの利用者となる親画面の

1. ViewModelに`ReactiveProperty`を生やして
1. Viewに配置したユーザーコントロールのプロパティにバインド

というような実装ができるといいですよね。ユーザーコントロールにバインドするデータ本体は親画面のModelで作るわけなのでViewModelから画面に表示する形に加工してバインドするのが自然です。
しかし、ユーザーコントロールへのバインドは既存の`TextBlock`の`Text`プロパティにセットするのとはワケが違います。ユーザーコントロールは複数の操作を簡略化したり複数のコントロールをまとめるために作るからです。そのため、ユーザーコントロールにバインドしたい型は`int`や`string`のようなちゃちなものではなく、**適切な名前をつけたプロパティ名に対して自分で定義した`class, record`をバインドしたい**と思うのが普通でしょう。
そこでやり方を検索すると「**依存関係プロパティ**を生やしてバインド用のプロパティを作ります」という説明を見かけますが、**別にそんなものなくても独自型のバインドはできます**。本稿では２つのやり方でのバインド方法を説明するとともに、利点と欠点について説明したいと思います。

# 前提
画面の実装は以下とします。

* ユーザーコントロールに依存関係プロパティを作る場合、コードビハインドで実装します。
* 親画面はMVVMです。
* `<Nullable>enable</Nullable>`です。ターゲットフレームワークは`net5.0-windows`です。
* `ReactiveProperty`を使います。本稿は`ReactiveProperty`くんを知ってる前提で書いています

作業の前に、バインドしたいデータの型を作っておきましょう。ここでは簡単のため**ユーザーコントロールはデータを表示するだけ**とします。表示するだけならメンバの変更が発生しないので普通の`record`型で十分です(親のVMで`ReactiveProperty<T>`を持ってバインドすればよいため)。
もしユーザーコントロールで書き込みしたり、親のVMでメンバを書き換えたりする場合は`INotifyPropertyChanged`を実装したクラスにするのを忘れないでください。MVVMフレームワークのPrismを使っているなら`BindableBase`を継承していつものやり方をすればいいです。

```c#:MyControlData.cs
public record MyControlData(string Id, int Count, double? Point);
```
また、画面にバインドするプロパティをViewModelに定義しておきます。

```c#:ViewModel.cs
public ReactiveProperty<MyControlData> MyData { get; } = new(new("", 0, null));
```
# 依存関係プロパティを作る方法
ユーザーコントロールに`DependencyProperty`型のプロパティを作る必要があります。これはボイラープレートコードなので、そのまま以下の形式で覚えてしまえばよいでしょう。
ここではユーザーコントロールを`UserControl1`、画面からバインドする依存関係プロパティの名前を`Value`とします。また、以下のコードはコードビハインド（`UserControl1.xaml.cs`）に書きます。MVVMアプリであっても、極端に複雑なことをしていなければユーザーコントロールのコードはすべてコードビハインドに書いたほうがいいです。

```c#:UserControl1.cs
public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
    }
    // 依存関係プロパティの定義に必要なDependencyProperty(ボイラープレート)
    // わかりやすくするため名前は プロパティ名+Property にする
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register(
            nameof(Value),         // プロパティ名
            typeof(MyControlData), // バインドするデータの型
            typeof(UserControl1),  // 自分自身の型
            new PropertyMetadata(  // 初期値をPropertyMetadata経由でつっこむ
                new MyControlData("", 0, null)) 
        );
    // 実際にバインドするプロパティ
    // 上で定義したDependencyPropertyにあーだこーだしてアクセスさせる
    public MyControlData Value 
    {
        get => (MyControlData)GetValue(ValueProperty); 
        set => SetValue(ValueProperty, value); 
    }
}
```
`UserControl1`上に配置したコントールには`Value`経由で各メンバにアクセスしてバインドします。ただし、`UpdateSourceTrigger`の設定は忘れないでください。これを忘れると、大本の`MyData`プロパティの値を変更してもユーザーコントロールに変更が通知されません。

```xml:UserControl1.xaml
<TextBlock Text="{Binding Value.Id, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type local:UserControl1}}}" />
<TextBlock Text="{Binding Value.Count, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type local:UserControl1}}}" />
<TextBlock Text="{Binding Value.Point, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type local:UserControl1}}}" />
```
ここまでできたら、あとは利用者のViewのxamlでいつもどおりバインドするだけです。

```xml:View.xaml
<!-- 自分で作ったプロパティ(ここではValue)にReactivePropertyをバインド -->
<local:UserControl1 Value="{Binding MyData.Value}" />
```
# もう１つの方法：DataContext
実装量としてはこちらの方法が少ないです。`UserControl2.xaml.cs`のコードビハインドへの実装は完全に不要なためです。まず、バインドする型`MyControlData`を定義後、ユーザーコントロールのxamlで各コントロールに以下のようにバインドを設定します。例によって`UpdateSourceTrigger`の設定は忘れないようにしてください。
なお、混同を防ぐためこの方法のコントロールは`UserControl2`とします。

```xml:UserControl2.xaml
<!-- バインドするMyControlData型の各メンバをそのまま設定する -->
<TextBlock Grid.Row="0" Text="{Binding Id, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="1" Text="{Binding Count, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="2" Text="{Binding Point, UpdateSourceTrigger=PropertyChanged}" />
```
その後、利用者の画面側のViewのxamlに以下のように記述します。

```xml:View.xaml
<Window x:Class="MyNamespace.MainWindow"
        ~~ 名前空間とかいろいろ設定 ~~
    >
    <!-- 先頭でResourcesブロックにバインド内容を登録する -->
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:MyControlData}"> <!-- バインドする独自型 -->
            <local:UserControl2 /> <!-- ユーザーコントロール名 -->
        </DataTemplate>
    </Window.Resources>
    <!-- 依存関係プロパティでは新しく作ったプロパティにバインドしていた -->
    <!-- この方法ではDataContextにバインドする -->
    <local:UserControl2 DataContext="{Binding MyData.Value}" />
</Window>
```
これだけで独自型のバインド設定は完了です。簡単ですね。

※ 利用者のViewとViewModelの接続をどのようにしているかは皆さんの利用しているMVVMフレームワークによると思いますが、もし試しにViewModelを使わずコードビハインドでViewを定義する場合、画面のコンストラクタで`InitializeComponent()`の実行後に`DataContext = this;`とするのを忘れないようにしてください。

# 利点と欠点
方法が複数あるということは、使いどころ、利点と欠点というものが存在します。

* DataContextを使う場合、ユーザーコントロールのxamlのバインドで扱えるのはDataContextに設定する型のみ
    * データを加工した値等を画面表示に使いたい場合、その独自型のメンバとして画面表示用の値を持つプロパティやメソッドを実装し、それをバインドする必要がある。つまり、**「データを画面表示用に加工する」責務はその独自型が担うことになる**
    * DataContextは１こしか設定できないため、違う型を複数指定できない。それらを持つ`object[]`にするとか、複数の型をまとめる型を更に作る必要がある
* 依存関係プロパティは複数作れる
    * 異なる２つの型のデータを別々の依存関係プロパティにすることで、それぞれバインドすることができる
    * **データを画面表示内容に加工する責務はユーザーコントロールのView(コードビハインドで完結させない場合はViewModel)になる**
        * 独自型は単なるデータとして扱い、コードビハインドで`ReactiveProperty`を作って独自型を`INotifyPropertyChanged`にすれば、「コンストラクタでそれぞれの依存関係プロパティを`ObserveProperty().Select(~~).ToReadOnlyReactivePropety()`したプロパティをxamlでバインド」という、普段からよくやっている方法を採れる
    * 実装はやや面倒くさい

# どうやって使い分けるか
私の場合は、**データクラス側をいろんなところで使い回すかどうか**で考えるようにしています。いろんなところで共有する型の場合、依存関係プロパティにしてデータの加工はユーザーコントロールの責任にすることで、**データの利用者(ユーザーコントロール)によって見せ方を変える**といったことができるためです。
もちろん、あるユーザーコントロール専用のデータクラスにするならDataContextで十分でしょう。そのような場合はユーザーコントロールの内部クラスにしたくなりますがxamlでは入れ子のメンバーをバインドできないため内部クラスにはできないことに注意してください。
ユーザーコントロールだろうとなんだろうと必ず表示用データは親画面のViewModelで加工する(#,##0のような低次元のフォーマットはxaml)、というルールとするのもアリだと思います。この場合はユーザーコントロールと画面表示値(`string`や`enum`)の構造体が必ず1:1になり、Modelが管理する生データを親画面のViewModelで構造体に変換してバインドするようなイメージになると思います。

# 余談
~~ユーザーコントロールって単語長すぎ！読みにくいしタイプするの疲れる~~