View を支えるビヘイビア

## はじめに

オレオレ解釈の戒め覚え書き　その４

今回はビヘイビアについてです。
※Expression Blend に付属していた所謂 BlendBehavior と呼ばれているビヘイビアについてです。

## 本文

MVVM におけるビヘイビアは、特定の画面要素（あるいはウィンドウ）専用のふるまい（機能）を指します。コードビハインドとは違い、要素からの着脱や複数の要素間でふるまいを共通化することができます。たとえは「ウィンドウのどの位置からでもドラッグ移動を開始できる」というふるまいを持つ Window クラス用のビヘイビアを定義してみます。

```cs:Behavior
namespace TestApp.Views.Behaviors
{
    public class WindowBehavior : Behavior<Window>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.MouseLeftButtonDown += this.AssociatedObject_MouseLeftButtonDown;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.MouseLeftButtonDown -= this.AssociatedObject_MouseLeftButtonDown;
        }

        private void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ButtonState == MouseButtonState.Pressed)
            {
                this.AssociatedObject.DragMove();
                e.Handled = true;
            }
        }
    }
}
```

```xml:View
<Window x:Class="TestApp.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:behaviors="clr-namespace:TestApp.Views.Behaviors">

    <!-- Window クラスにビヘイビアを設定できる -->
    <i:Interaction.Behaviors>
        <behaviors:WindowBehavior/>
    </i:Interaction.Behaviors>
</Window>
```

従来のコードビハインドは様々な機能がごった返した大きな塊になっていましたが、ビヘイビアではふるまい単位で処理が切り離されます。ロジックが View から独立しているため、他の View からでも容易に再利用することができます。

## 余談

冒頭で BlendBehavior の断りを入れましたが、今回の実装とは別にビヘイビアにはもう一つの実装法があります。添付プロパティの仕組みを応用した AttachedBehavior です。（添付プロパティについてはここでは割愛します。）
BlendBehavior が登場するより前から存在していた方法です。プロパティとして指定するためスタイルにも定義でき、様々なプロパティを内包したヘルパークラス等で見かけることがあります。上記の例を AttachedBehavior で書き換えるとこのようになります。

```cs:AttachedBehavior
namespace TestApp.Views.Behaviors
{
    public class WindowAttachedBehavior
    {
        public static readonly DependencyProperty DraggableAnywhereProperty
            = DependencyProperty.RegisterAttached(
                "DraggableAnywhere",
                typeof(bool),
                typeof(WindowAttachedBehavior),
                new PropertyMetadata(OnDraggableAnywhereChanged));
        public static bool GetDraggableAnywhere(DependencyObject obj)
            => (bool)obj.GetValue(DraggableAnywhereProperty);
        public static void SetDraggableAnywhere(DependencyObject obj, bool value)
            => obj.SetValue(DraggableAnywhereProperty, value);

        private static void OnDraggableAnywhereChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (!(sender is Window window))
                return;

            if ((bool)e.OldValue)
                window.MouseLeftButtonDown -= MouseLeftButtonDown;
            if ((bool)e.NewValue)
                window.MouseLeftButtonDown += MouseLeftButtonDown;
        }

        private static void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!(sender is Window window))
                return;
            if (e.ButtonState != MouseButtonState.Pressed)
                return;

            window.DragMove();
            e.Handled = true;
        }
    }
}
```

```xml:View
<Window x:Class="TestApp.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:behaviors="clr-namespace:TestApp.Views.Behaviors"
        behaviors:WindowAttachedBehavior.DraggableAnywhere="True">
        <!-- Window クラスのプロパティの一つとしてを設定できる -->
</Window>
```

## おわりに

ビヘイビアについてまとめました。
以前「無理せずにコードビハインドは使った方がよい」という持論を展開しましたが、共通的な処理はビヘイビアに逃がすことができるため、コードビハインドはその画面やコントロール固有の処理が自然と集まってくるでしょう。
View と ViewModel 間の疎結合はもちろんですが、各層内でもロジックの癒着を抑止することで、より堅牢な MVVM パターンの構築に繋がります。

次回はトリガーアクションについてまとめます。