Behaviors are not working properly inside Style Setter

不羁的心 提交于 2019-12-01 08:44:15

This won't work because behaviors, actions or triggers are designed to be attached to a single element. When you define it inside a style's setter, it's like you are trying to associate it with multiple elements and as you have already seen, the trigger is only called when you interact with the first element with this style.

There's a simple way to fix this. Basically, you need to make sure each element that's associated with this style has a new instance of the trigger you have created. You can have all this logic wrapped inside an attached property and then your style will only need to reference this property.

<Style x:Name="DisplayImage" TargetType="ScrollViewer">
    <Setter Property="VerticalScrollBarVisibility" Value="Hidden" />
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="ZoomMode" Value="Disabled" />
    <Setter Property="local:FrameworkElementEx.AttachBehaviors" Value="True" />
</Style>

This is how this attached property is implemented.

public static class FrameworkElementEx
{
    public static bool GetAttachBehaviors(DependencyObject obj)
    {
        return (bool)obj.GetValue(AttachBehaviorsProperty);
    }

    public static void SetAttachBehaviors(DependencyObject obj, bool value)
    {
        obj.SetValue(AttachBehaviorsProperty, value);
    }

    public static readonly DependencyProperty AttachBehaviorsProperty =
        DependencyProperty.RegisterAttached("AttachBehaviors", typeof(bool), typeof(FrameworkElementEx), new PropertyMetadata(false, Callback));

    private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behaviors = Interaction.GetBehaviors(d);

        var eventTriggerBehavior = new EventTriggerBehavior
        {
            EventName = "DoubleTapped"
        };
        eventTriggerBehavior.Actions.Add(new ScrollViewerDoubleTap());

        behaviors.Add(eventTriggerBehavior);
    }
}

We can do this using pure XAML

...and a reusable attached property, that receives a DataTemplate with a BehaviorCollection (or a single Behavior).

You don't have to change your code-behind.

<Style x:Key="SomeImageStyle" 
       xmlns:Behaviors="using:MyBehaviors"
       xmlns:i="using:Microsoft.Xaml.Interactivity"
       xmlns:core="using:Microsoft.Xaml.Interactions.Core"
       TargetType="Image">
  <Setter Property="local:AttachedBehaviorsEx.AttachedBehaviors">
    <Setter.Value>
            <DataTemplate>
                    <core:EventTriggerBehavior EventName="Click">
                        <Behaviors:SomeAction SomeParameter="someValue" />
                    </core:EventTriggerBehavior>
            </DataTemplate>
    </Setter.Value>
  </Setter>
</Style>

Or if you need a few Behaviors at a time:

    <Setter.Value>
            <DataTemplate>
                <i:BehaviorCollection>
                    <core:EventTriggerBehavior EventName="Click">
                        <Behaviors:SomeAction SomeParameter="someValue" />
                    </core:EventTriggerBehavior>
                    <core:EventTriggerBehavior EventName="Tapped">
                        <Behaviors:SomeAction SomeParameter="someOtherValue" />
                    </core:EventTriggerBehavior>
                </i:BehaviorCollection>
            </DataTemplate>
    </Setter.Value>

The attached property

You don't even need to change it.

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

namespace MyBehaviors
{
    public static class AttachedBehaviorsEx
    {
        public static DataTemplate GetAttachedBehaviors(DependencyObject obj)
        {
            return (DataTemplate)obj.GetValue(AttachedBehaviorsProperty);
        }

        public static void SetAttachedBehaviors(DependencyObject obj, DataTemplate value)
        {
            obj.SetValue(AttachedBehaviorsProperty, value);
        }

        public static readonly DependencyProperty AttachedBehaviorsProperty =
            DependencyProperty.RegisterAttached("AttachedBehaviors", typeof(DataTemplate), typeof(AttachedBehaviorsEx), new PropertyMetadata(null, Callback));

        private static void Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BehaviorCollection collection = null;
            var template = (DataTemplate)e.NewValue;
            if (template != null)
            {
                var value = template.LoadContent();

                if (value is BehaviorCollection)
                    collection = (BehaviorCollection)value;
                else if (value is IBehavior)
                    collection = new BehaviorCollection { value };
                else throw new Exception($"AttachedBehaviors should be a BehaviorCollection or an IBehavior.");
            }
            // collection may be null here, if e.NewValue is null
            Interaction.SetBehaviors(d, collection);
        }
    }
}

Why is it better?

It's a more flexible way to achieve this than Justin XL suggested.

His code requires modifying code-behind in order to re-use it. Each time when you want to re-use it, you will need to set the EventName (in this line EventName = "DoubleTapped") and the Behavior (this line eventTriggerBehavior.Actions.Add(new ScrollViewerDoubleTap());).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!