かずきのBlog@hatena

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

Style内でBehaviorやTriggerを設定したい

今回のお題の元ネタはこちらの記事です。

Style内でBlend SDKのBehaviorを設定できないか?ということなんですが、普通にやるとできません。そのため以下のような仕掛けを作ってやる必要があります。
まず、BehaviorとTriggerBaseを格納するためのコレクションを用意します。

namespace Okazuki.MVVM.PrismSupport.Style
{
    using System.Windows;
    using System.Windows.Interactivity;

    /// <summary>
    /// StyleでBehaviorを設定するために使用するコレクション
    /// </summary>
    public class StyleBehaviorCollection : FreezableCollection<Behavior>
    {
        protected override Freezable CreateInstanceCore()
        {
            return new StyleBehaviorCollection();
        }
    }
}
namespace Okazuki.MVVM.PrismSupport.Style
{
    using System.Windows;

    /// <summary>
    /// StyleでTriggerを設定するためのコレクション
    /// </summary>
    public class StyleTriggerCollection : FreezableCollection<System.Windows.Interactivity.TriggerBase>
    {
        protected override Freezable CreateInstanceCore()
        {
            return new StyleTriggerCollection();
        }
    }
}

そして、これをStyleで設定するための添付プロパティを定義したクラスを作成します。

namespace Okazuki.MVVM.PrismSupport.Style
{
    using System.Windows;
    using System.Windows.Interactivity;

    /// <summary>
    /// Style内でBehaviorを設定するのに使用します。
    /// </summary>
    public static class StyleInteraction
    {
        /// <summary>
        /// Style内でBehaviorを設定します。
        /// </summary>
        public static readonly DependencyProperty BehaviorsProperty =
            DependencyProperty.RegisterAttached(
                "Behaviors",
                typeof(StyleBehaviorCollection),
                typeof(StyleInteraction),
                new PropertyMetadata((sender, e) =>
                {
                    if (e.NewValue == e.OldValue)
                    {
                        return;
                    }

                    var styleBehaviors = e.NewValue as StyleBehaviorCollection;
                    if (styleBehaviors == null)
                    {
                        return;
                    }

                    // 同じビヘイビアを複数個所に設定できないので、クローンを追加する
                    var behaviors = Interaction.GetBehaviors(sender);
                    foreach (var styleBehavior in styleBehaviors)
                    {
                        behaviors.Add((Behavior)styleBehavior.Clone());
                    }
                }));

        /// <summary>
        /// Style内でTriggerを設定します。
        /// </summary>
        public static readonly DependencyProperty TriggersProperty =
           DependencyProperty.RegisterAttached(
               "Triggers",
               typeof(StyleTriggerCollection),
               typeof(StyleInteraction),
               new PropertyMetadata((sender, e) =>
               {
                   if (e.NewValue == e.OldValue)
                   {
                       return;
                   }

                   var styleTriggers = e.NewValue as StyleTriggerCollection;
                   if (styleTriggers == null)
                   {
                       return;
                   }

                   var triggers = Interaction.GetTriggers(sender);
                   foreach (var styleTrigger in styleTriggers)
                   {
                       // TriggerとActionのCloneを作成して追加する
                       var clone = (System.Windows.Interactivity.TriggerBase)styleTrigger.Clone();
                       foreach (var action in styleTrigger.Actions)
                       {
                           clone.Actions.Add((System.Windows.Interactivity.TriggerAction)action.Clone());
                       }

                       triggers.Add(clone);
                   }
               }));

        /// <summary>
        /// Style内でBehaviorを設定するコレクションを取得します。
        /// </summary>
        /// <param name="obj">対象のオブジェクト</param>
        /// <returns>Behaviorを設定するコレクション</returns>
        public static StyleBehaviorCollection GetBehaviors(DependencyObject obj)
        {
            return (StyleBehaviorCollection)obj.GetValue(BehaviorsProperty);
        }

        /// <summary>
        /// Style内でBehaviorを設定するコレクションを設定します。
        /// </summary>
        /// <param name="obj">対象のオブジェクト</param>
        /// <param name="value">設定するコレクション</param>
        public static void SetBehaviors(DependencyObject obj, StyleBehaviorCollection value)
        {
            obj.SetValue(BehaviorsProperty, value);
        }

        /// <summary>
        /// Style内でTriggerを設定するコレクションを取得します。
        /// </summary>
        /// <param name="obj">対象のオブジェクト</param>
        /// <returns>Triggerを設定するコレクション</returns>
        public static StyleTriggerCollection GetTriggers(DependencyObject obj)
        {
            return (StyleTriggerCollection)obj.GetValue(BehaviorsProperty);
        }

        /// <summary>
        /// Style内でTriggerを設定するコレクションを設定します。
        /// </summary>
        /// <param name="obj">対象のオブジェクト</param>
        /// <param name="value">設定するコレクション</param>
        public static void SetTriggers(DependencyObject obj, StyleTriggerCollection value)
        {
            obj.SetValue(BehaviorsProperty, value);
        }
    }
}

ポイントは、StyleInteraction.Behavior/StyleInteraction.Triggers添付プロパティの値が変更されたときにBehaviorやTriggerのCloneを対象オブジェクトのInteraction.Behaviors/Interaction.Triggers添付プロパティに設定しているところです。こうすることで、StyleからBehaviorやTriggerを設定することが出来ます。

因みに、この実装ではStyleを当てなおした場合の動作は考慮していないので、Styleを動的に当てなおす場合には、もう一工夫必要になります。

このクラスを使用してStyle内で設定するコードは以下のようになります。

<TextBlock Name="textBlock1" Text="About" Margin="2"  VerticalAlignment="Center" Foreground="Blue" TextTrimming="None">
    <TextBlock.Style>
        <!-- StyleでTriggerを設定する例 -->
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="style:StyleInteraction.Triggers">
                <Setter.Value>
                    <style:StyleTriggerCollection>
                        <i:EventTrigger EventName="MouseLeftButtonDown">
                            <Okazuki_MVVM_PrismSupport_Interactivity:ShowMessageBoxAction
                                Title="バージョン情報"
                                Content="SampleApplicationバージョン1.1" />
                        </i:EventTrigger>
                    </style:StyleTriggerCollection>
                </Setter.Value>
            </Setter>
        </Style>
    </TextBlock.Style>
</TextBlock>