かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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>