WPFのビヘイビア


ビヘイビアとは

言葉の意味は「振る舞い」。WPFでMVVMに準拠する際、Viewの状態の変化をきっかけにして、Viewで実行される処理の実装方法のことを指す。MVVMに準拠するとコードビハインドが使えないので、その代替手段ということになる。

実装方法

添付プロパティ

添付プロパティの実装に、振る舞いを含めてしまう。

コールバック

添付プロパティは初期化時にコールバックが登録できる。そこに実行したい処理を記述することで、プロパティの値の変更をきっかけとしたビヘイビアが作れる。

添付プロパティのコールバックを使用したビヘイビア
using System.Windows;

namespace Sample {
    public class AttachedXXX {
        public static DependencyProperty XXXProperty
            = DependencyProperty.RegisterAttached(
                "XXX",
                typeof(bool),
                typeof(AttachedXXX),
                new PropertyMetadata(XXX_PropertyChanged) //コールバックを登録
            );

        public static void SetXXX(DependencyObject obj, bool value)
            => obj.SetValue(XXXProperty, value);

        public static bool GetXXX(DependencyObject obj)
            => (bool)obj.GetValue(XXXProperty);

        //コールバック
        private static void XXX_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            if (e.NewValue != null) {
                //値の変更に対する振る舞いを記述する
            }
        }
    }
}
XAML
<Window x:Class="Sample.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Sample"
        Title="MainView" Height="300" Width="300">
    <Grid>
        <TextBox local:AttachedXXX.XXX="{Binding XXX}" />
    </Grid>
</Window>

これだけではできることが限られるため、実際は次項の添付ビヘイビアとして利用されることが多い。

添付ビヘイビア

コールバック内でViewのイベントハンドラを登録する。この方法は「添付ビヘイビア」と呼ばれており、ビヘイビアの実装方法としてはメジャーだが、Microsoftのサイトには解説がない。

添付ビヘイビア
using System.Windows;

namespace Sample {
    public class AttachedXXX {
        public static DependencyProperty XXXProperty
            = DependencyProperty.RegisterAttached(
                "XXX",
                typeof(bool),
                typeof(AttachedXXX),
                new PropertyMetadata(XXX_PropertyChanged) //コールバックを登録
            );

        public static void SetXXX(DependencyObject obj, bool value)
            => obj.SetValue(XXXProperty, value);

        public static bool GetXXX(DependencyObject obj)
            => (bool)obj.GetValue(XXXProperty);

        //コールバック
        private static void XXX_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            var textBox = d as TextBox;
            if (textBox == null) return;

            //イベントハンドラの登録
            if (e.NewValue != null) {
                textBox.PreviewTextInput += TextBox_PreviewTextInput;
            } else {
                textBox.PreviewTextInput -= TextBox_PreviewTextInput;
            }
        }

        //イベントハンドラ
        private static void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e) {
            //振る舞い
        }
    }
}

コールバックでは、イベントハンドラの登録/解除のみ行う。プロパティと振る舞いが連動しないため、何をしているのかわかりにくいコードになる。

この方法は、ビヘイビアがプロパティを必要としない場合には使えない。一方で、プロパティなのでスタイル指定できるというメリットがある。

Blend SDK

Blend SDKのSystem.Windows.Interactivity.dllに用意された専用のクラスを使う。

Behaviorクラス

ビヘイビアを実装するための専用のクラス。このクラスを継承してビヘイビアを実装する。

Behaviorクラスを使用したビヘイビア
using System;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace Sample {
    public class YYYBehavior : Behavior<Control> {
        protected override void OnAttached() {
            base.OnAttached();

            AssociatedObject.Loaded += AssociatedObject_Loaded;
        }

        protected override void OnDetaching() {
            base.OnDetaching();

            AssociatedObject.Loaded -= AssociatedObject_Loaded;
        }

        //イベントハンドラ
        private void AssociatedObject_Loaded(object sender, RoutedEventArgs e) {
            //振る舞い
        }
    }
}
XAML
<Window x:Class="Sample.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:Sample"
        Title="MainView" Height="300" Width="300">
    <Grid>
        <Button Content="OK">
            <i:Interaction.Behaviors>
                <local:YYYBehavior/>
            </i:Interaction.Behaviors>
        </Button>
    </Grid>
</Window>

添付ビヘイビアとは違い、専用のクラスなのでわかりやすいコードになるが、xamlの構造が複雑になるというデメリットがある。また、添付ビヘイビアと違いスタイルでの指定ができない。(工夫すればできるが、xamlがより複雑になる。

TriggerBaseクラスとTriggerActionクラス

今までの方法はきっかけと振る舞いを同時に定義しているが、両者を個別に指定する方法が用意されており、きっかけは「トリガー」、振る舞いは「アクション」と呼ばれる。主なトリガーとアクションが最初から用意されており、これらを組み合わせることで、単純なビヘイビアなら簡単に実現できる。

既存のトリガーとアクションを組み合わせた例
<Window x:Class="Sample.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:Sample"
        Title="MainView" Height="300" Width="300">
    <Grid>
        <Label Content="OK">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseDown">
                    <i:InvokeCommandAction Command="{Binding XXXCommand}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
    </Grid>
</Window>

自作する場合は、TriggerBaseクラスを継承してトリガーを、TriggerActionクラスを継承してアクションを実装する。

PrismのInteractionRequestTriggerを使えばViewModel側からアクションを実行できるので、MVVMを守りながらViewを操作する手段として重宝する。(例:MVVMでファイルダイアログを使用する