かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Windows 8.1のBehaviorでTriggerBehaviorを作る時の注意点2

Windows 8.1 RTM + Visual Studio 2013 RCの段階での情報です。

TriggerBehaviorには必ず以下のようなActionsプロパティが必要になります。

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

public ActionCollection Actions
{
    get
    {
        var actions = (ActionCollection)GetValue(ActionsProperty);
        if (actions == null)
        {
            actions = new ActionCollection();
            this.SetValue(ActionsProperty, actions);
        }
        return actions;
    }
}

毎回実装するのがめんどくさいので、という理由で基本クラスにまとめるという発想が出てくるので、以下のようなクラスを実装します。

using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;

namespace App11
{
    public class TriggerBehaviorBase : DependencyObject, IBehavior
    {
        // 毎回実装するのもめんどくさいし、基本クラスで定義しておきますか。
        public static readonly DependencyProperty ActionsProperty =
            DependencyProperty.Register(
                "Actions",
                typeof(ActionCollection),
                typeof(TriggerBehaviorBase),
                new PropertyMetadata(null));

        public ActionCollection Actions
        {
            get 
            { 
                var value = (ActionCollection)this.GetValue(ActionsProperty); 
                if (value == null)
                {
                    value = new ActionCollection();
                    this.SetValue(ActionsProperty, value);
                }

                return value;
            }
        }

        // ついでだし、既存のビヘイビアっぽく実装できるようにしただけで、今回の本題ではない。
        public DependencyObject AssociatedObject { get; private set; }

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

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

        protected virtual void OnAttached()
        {
        }

        protected virtual void OnDetaching()
        {
        }
    }
}

こうしておけば、TriggerBehaviorの実装も楽になります。例えばボタンのクリックをトリガーにする場合は以下のような感じです。あぁ楽ちん。

using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace App11
{
    public class ButtonClickTriggerBehavior : TriggerBehaviorBase
    {
        protected override void OnAttached()
        {
            var btn = (Button)this.AssociatedObject;
            btn.Click += this.ButtonClick;
        }

        protected override void OnDetaching()
        {
            var btn = (Button)this.AssociatedObject;
            btn.Click -= this.ButtonClick;
        }

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

落とし穴

このTriggerBehaviorに、ActionをBlendからドロップしようとしてもできません。以下のようになります。

f:id:okazuki:20130921172828j:plain

Actionsプロパティは存在するので、プロパティウィンドウからコレクションエディタを使ってActionを追加できますが、これじゃぁ快適に開発できません。

f:id:okazuki:20130921173040j:plain

解決策?(いまいちすぎる…)

とりあえず、TriggerBehaviorクラス自身(親ではなく自分自身)にActionsという名前のプロパティがあればActionをドロップできるみたいなので、newなりなんなりを使って強引にでも定義してやればドロップできるようになります。

using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace App11
{
    public class ButtonClickTriggerBehavior : TriggerBehaviorBase
    {
        /// <summary>
        /// えっ・・・。
        /// </summary>
        public new ActionCollection Actions
        {
            get { return base.Actions;  }
        }

        protected override void OnAttached()
        {
            var btn = (Button)this.AssociatedObject;
            btn.Click += this.ButtonClick;
        }

        protected override void OnDetaching()
        {
            var btn = (Button)this.AssociatedObject;
            btn.Click -= this.ButtonClick;
        }

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

これでプロジェクトをリビルドするとデザイナからぽとぺたでActionを追加できるようになります。

f:id:okazuki:20130921173402j:plain

でも、Actionsプロパティが2個表示されて、とってもイマイチな見た目になってしまいます。

f:id:okazuki:20130921173448j:plain

ううむ…。