KatsuYuzuのブログ

.NET の開発をメインとした日記です。

独自のTriggerBehaviorを作る方法 #win8dev_jp

先日の記事とアドベントカレンダーで連携した記事でビヘイビアーとトリガーアクションにしか触れていなくて"トリガー"について触れていませんでした。*1

そこで独自のトリガー(ここではMyTriggerBehaviorとします)を順を追って作成します。

TriggerBehaviorに必要な要素

まず、Windows ストアアプリ標準ライブラリに実装されているEventTriggerBhaviorとDataTriggerBehaviorからトリガーとは何かを見てみましょう。
オブジェクトブラウザーのスクショを並べて加工しました。
f:id:KatsuYuzu:20131227000313p:plain
共通点がめちゃんこ少ないですね。ここで更に独自のビヘイビアーのクラス作成の初期状態を見てみましょう。
f:id:KatsuYuzu:20131227001622p:plain
おわかりいただけただろうか。
Actionsプロパティを追加すればいいことがわかりますね。

Behaviorの作成

Actionsプロパティを追加する前にビヘイビアーとしての最低限を実装しておきます。

public DependencyObject AssociatedObject
{
    get { return this.associatedObject; }
}
private DependencyObject associatedObject;

public void Attach(DependencyObject associatedObject)
{
    this.associatedObject = associatedObject;
}

public void Detach()
{
    this.associatedObject = null;
}

Actionsプロパティの実装

オブジェクトブラウザーによるとActionCollection型のプロパティでgetterのみです。XAMLで取り扱うので依存関係プロパティとして定義します。スニペットがあるのでpropdpでTabキー叩いてください。そしてsetterを消す。

public ActionCollection Actions
{
    get { return (ActionCollection)GetValue(ActionsProperty); }
}

public static readonly DependencyProperty ActionsProperty =
    DependencyProperty.Register(
        "Actions", 
        typeof(ActionCollection), 
        typeof(MyTriggerBehavior), 
        new PropertyMetadata(null));

とりあえずこんな状態。
ひとまず、ここまででトリガーアクションを配下に置けるTriggerBehaviorが出来ました。Blendでぽとぺたぽちぽちしてみると確認できます。

<Interactivity:Interaction.Behaviors>
	<local:MyTriggerBehavior>
		<local:MyTriggerBehavior.Actions>
			<Core:ChangePropertyAction/>
			<Core:InvokeCommandAction/>
		</local:MyTriggerBehavior.Actions>
	</local:MyTriggerBehavior>
</Interactivity:Interaction.Behaviors>

Triggerの実装

何をトリガーにしてActionsが実行されるのかを定義します。ここでは親オブジェクトがButtonだと想定してClickイベントをトリガーとします。

private void RegisterTrigger()
{
    var button = this.associatedObject as Button;
    if (button == null)
    {
        return;
    }
    button.Click += OnClick;
}

private void UnRegisterTrigger()[...]

private void OnClick(object sender, RoutedEventArgs args)
{
    foreach (var item in this.Actions)
    {
        var action = (IAction)item;
        action.Execute(sender, args);
    }
}

RegisterとUnRegisterをAttachとDetachでそれぞれ呼んでください。OnClickではトリガーアクションを一つ一つ実行しています。色々漁ってるとMicrosoft.Xaml.Interactivity名前空間にInteraction.ExecuteActionsという補助があることに気づいたりして、簡単に書けます。

private void OnClick(object sender, RoutedEventArgs args)
{
    Interaction.ExecuteActions(sender, this.Actions, args);
}

実行

いよいよ実行です。先日と同じくMessageNotifyAction*2を置いて実行してみます。

<Button Content="トリガー">
    <Interactivity:Interaction.Behaviors>
        <local:MyTriggerBehavior>
            <local:MyTriggerBehavior.Actions>
                <local:MessageNotifyAction Message="アクション実行"/>
            </local:MyTriggerBehavior.Actions>
        </local:MyTriggerBehavior>
    </Interactivity:Interaction.Behaviors>
</Button>

何やら実行時例外がおきました。
f:id:KatsuYuzu:20131227012530p:plain
collection propertyがnullだとおっしゃってます。最低限で突貫してきたツケです。collection propertyと言えばActionsですのでget時にきちんとインスタンスを返すようにします。

public ActionCollection Actions
{
    get
    {
        var actionCollection = (ActionCollection)GetValue(ActionsProperty);

        if (actionCollection == null)
        {
            actionCollection = new ActionCollection();
            SetValue(MyTriggerBehavior.ActionsProperty, actionCollection);
        }

        return actionCollection;
    }
}

えいっ!
f:id:KatsuYuzu:20131227013047p:plain
できました!

隠し味

一応、完成しましたがXAMLの書き味がよろしくないのでContentProperty属性を追加します。

[ContentProperty(Name = "Actions")]
public class MyTriggerBehavior : DependencyObject, IBehavior
<local:MyTriggerBehavior>
    <local:MessageNotifyAction Message="アクション実行"/>
</local:MyTriggerBehavior>

まとめ

IBehaviorの実装に加えて、ActionCollection型のプロパティ、実行の起点、つまり、トリガーを実装することで独自のTriggerBehaviorを作ることが出来ました。
gistに全文を置いときましたので参照してください。

おまけ

Visual StudioでALT + ドラッグで範囲選択できますよ。盲点。
f:id:KatsuYuzu:20131227001216p:plain

*1:Twitterで当日に突っ込まれてました

*2:MessageNotifyActionについては当記事冒頭のリンクを参照