WPFの添付ビヘイビア

# 添付ビヘイビアとは
MVVMモデルを採用したWPFにおいて、Viewの状態変化に伴って実行される処理を定義したい場合に使用されるテクニックです。
MVVMの思想上View、つまりコードビハインドに処理を書くのはご法度ですが、
Viewで完結する処理をViewModelに書くのもあまりいいとは言えません。

そこで、WPFの**添付**プロパティを自作する処理にまとめてコードビハインドに書きたかった処理を記載することで
Viewの**ビヘイビア**（= 振る舞い）を表現するテクニックになります。

## イメージ
ざっくりとした添付ビヘイビアのイメージは以下の通りです。
![Untitled Diagram.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582190/78dad1f6-b7cc-37f7-57ef-2c3d3e32e03e.jpeg)


# サンプルコード
実際に作成してみたビヘイビアがこちら。

```c#:MessageDialogBehavior.cs
using System.Windows;
using System.Windows.Input;

namespace WPF.Views.Behaviors
{
    internal class MessageDialogBehavior
    {
        // 1. DependencyPropertyインスタンスを生成
        public static readonly DependencyProperty ShowMessageProperty = DependencyProperty.RegisterAttached(
            "ShowMessage",
            typeof(bool),
            typeof(MessageDialogBehavior),
            new PropertyMetadata(false, OnShowMessage)
        );

        // 2. Getterを作成
        public static bool GetShowMessage(DependencyObject target)
            => (bool)target.GetValue(ShowMessageProperty);

        // 3. Setterを作成
        public static void SetShowMessage(DependencyObject target, bool value)
            => target.SetValue(ShowMessageProperty, value);

        // 4. callbackメソッドを定義
        private static void OnShowMessage(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var element = sender as UIElement;
            if (element == null)
            {
                return;
            }

            var newValue = (bool)e.NewValue;
            if ((bool)e.NewValue)
            {
                element.MouseRightButtonDown += ShowMessage;
            } else
            {
                element.MouseRightButtonDown -= ShowMessage;
            }
        }

        private static void ShowMessage(object sender, MouseButtonEventArgs e)
        {
            MessageBox.Show("Behavior Sample.");
        }
    }
}
```

添付プロパティで `True` を指定したコントロールで右クリックするとポップアップが表示されるものになります。

## 1. DependencyPropertyインスタンスを生成
まずは `DependencyProperty.RegisterAttached()` を利用してDependencyPropertyインスタンスを 生成します。
`DependencyProperty.RegisterAttached()` が要求する4つの引数には次の内容を記載します。

|引数||
|---|---|
|第1引数|プロパティ名を `string` 指定します。<br />この値が実際にXAMLで指定するプロパティ名になります。|
|第2引数|プロパティの型を `Type` で指定します。|
|第3引数|プロパティを所有する型を `Type` で指定します。<br />今回の場合は自分自身である `MessageDialogBehavior` になります。|
|第4引数|メタデータを指定します。<br />実際の値としては `PropertyMetadata` のインスタンスになります。|

### PropertyMetadataについて
`DependencyProperty` インスタンスの生成に必要な `PropertyMetadata` インスタンスですが、
デフォルトコンストラクタの他に4種類のオーバーロードで用意されていて、状況に応じて使い分けることになります。
各コンストラクタの引数は以下の通りです。

```c#:System.Windows.PropertyMetadata
// ※一部省略

namespace System.Windows
{
    public class PropertyMetadata
    {
        public PropertyMetadata();
        public PropertyMetadata(object defaultValue);
        public PropertyMetadata(PropertyChangedCallback propertyChangedCallback);
        public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback);
        public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback);
    }
}
```

今回使用したのはこのコンストラクタです。

```c#

public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback);
```

添付プロパティのデフォルト値と、値が変更された際のcallbackを指定しています。
※callbackがどのように使用されるかは後述

## 2. Getterを作成
添付プロパティの値を取得するためのメソッドを定義します。
引数の `DependencyObject` の `GetValue()` メソッドの戻り値を
「1. DependencyPropertyインスタンスを生成」で`RegisterAttached()` の第2引数に指定した
型でキャストして返します。
※第2引数はプロパティの型でした

### メソッド名について
Getterのメソッド名は必ず **Get + プロパティ名** とする必要があります。
今回の場合だとプロパティ名を `ShowMessage` としているので、Geeterのメソッド名は `GetShowMessage` となります。

## 3. Setterを作成
添付プロパティの値を設定するためのメソッドを定義します。
引数の `DependencyObject` の `SetValue()` メソッドを使用して設定を行います。

### メソッド名について
Getterと同じく、Setterのメソッド名も必ず **Set + プロパティ名** とする必要があります。
そのため今回の場合だと、メソッド名は `SetShowMessage` となります。

## 4. callbackメソッドを定義
値が変更されたときに実行するcallbackメソッドを定義しています。
これは「1. DependencyPropertyインスタンスを生成」で登場した
`PropertyMetadata` コンストラクタの第2引数として使用されています。

この中で少し独特だなと感じたのはこの部分でした。

```c#
if ((bool)e.NewValue)
{
    element.MouseRightButtonDown += ShowMessage;
} else
{
    element.MouseRightButtonDown -= ShowMessage;
}
```
`DependencyPropertyChangedEventArgs` の `e` で変更前と変更後の値を取得できるのですが
今回は特に何かしらの処理で値を変更するといった処理は行っていません。
にもかかわらず、変更前・変更後どちらからも値が取得できるようになっていました。
デバッグして確認してみたところ、どうやら値を変更するような処理を行っていなくても
変更前の値である `e.OldValue` にはデフォルト値が設定されているようでした。
で、このデフォルト値がどこで指定されているかというと...

```c#

public static readonly DependencyProperty ShowMessageProperty = DependencyProperty.RegisterAttached(
    "ShowMessage",
    typeof(bool),
    typeof(MessageDialogBehavior),
    new PropertyMetadata(false, OnShowMessage)  // ここの第1引数
);
```
`DependencyProperty` インスタンス作成時に指定していました！
初期値がfalseなので実質**添付プロパティでTrueを指定するとcallbackメソッドが実行される**となっています。

## View側の記述
このビヘイビアを実際に使用するためのXAMLと使用結果は以下の通りです。

```xml:BehaviorSample.xaml
<Window x:Class="WPFP.Views.BehaviorSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFP.Views"
        xmlns:b="clr-namespace:WPFP.Views.Behaviors"
        mc:Ignorable="d"
        Title="BehaviorSample" Height="450" Width="800">
    <StackPanel>
        <TextBlock Text="Trueにするとメッセージボックスが表示される"
                   Margin="5"
                   FontSize="20"
                   b:MessageDialogBehavior.ShowMessage="True" />
        <TextBlock Text="falseだとメッセージボックスが表示されない"
                   Margin="5"
                   FontSize="20"
                   b:MessageDialogBehavior.ShowMessage="False" />
    </StackPanel>
</Window>
```

実行結果はこんな感じ。

![2020-04-19_10h40_25 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582190/1a398021-13cc-58c5-11f3-37de5c5a5ffe.png)|
|:-:|