行为在样式设置器中无法正常工作

本文关键字:常工作 工作 样式 设置 | 更新日期: 2023-09-27 18:35:54

在我的Windows运行时应用程序中,我有一个主题,其样式为双击操作定义了Behavior

以下是 XML 命名空间:

xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"

这是样式:

<Style x:Name="DisplayImage" TargetType="ScrollViewer">
    <Setter Property="VerticalScrollBarVisibility" Value="Hidden" />
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="ZoomMode" Value="Disabled" />
    <Setter Property="i:Interaction.Behaviors">
        <Setter.Value>
            <i:BehaviorCollection>
                <core:EventTriggerBehavior EventName="DoubleTapped">
                    <local:ScrollViewerDoubleTap />
                </core:EventTriggerBehavior>
            </i:BehaviorCollection>
        </Setter.Value>
    </Setter>
</Style>

这是我Behavior

[DefaultEvent(typeof(ScrollViewer),"DoubleTapped")]
public class ScrollViewerDoubleTap : DependencyObject, IAction
{
    public object Execute(object sender, object parameter)
    {
        ScrollViewer sv = (ScrollViewer)sender;
        if (sv.HorizontalScrollBarVisibility == ScrollBarVisibility.Disabled)
        {
            sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
        }
        else
        {
            sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
        }
        return sender;
    }
}

这就是我使用它的方式:

<ScrollViewer Style="{StaticResource Image}" MaxWidth="1067">
    <Border BorderBrush="Black" BorderThickness="1">
        <Image Source="MyImage.png"/>
    </Border>
</ScrollViewer>

当我双击具有此样式的页面上的第一个图像时,它可以完美运行;但是,当我双击页面上的其他图像时,行为代码永远不会运行。 我知道它永远不会运行,因为我使用断点运行它,当我双击第一个图像时它会中断,而不是第二个图像。 我将不胜感激有关为什么会发生这种情况的任何提示。

行为在样式设置器中无法正常工作

这不起作用,因为行为、操作或触发器被设计为附加到单个元素。当您在样式的 setter 中定义它时,就像您尝试将其与多个元素相关联一样,正如您已经看到的,触发器仅在您与具有此样式的第一个元素交互时才被调用。

有一种简单的方法可以解决这个问题。基本上,您需要确保与此样式关联的每个元素都具有您创建的触发器的新实例。您可以将所有这些逻辑包装在附加属性中,然后您的样式只需要引用此属性。

<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>

这就是实现此附加属性的方式。

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);
    }
}

我们可以使用纯 XAML 来做到这一点

。以及可重用的附加属性,该属性接收带有BehaviorCollection(或单个Behavior)的DataTemplate

您不必更改代码隐藏。

<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>

或者,如果您一次需要几个Behaviors

    <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>

附加属性

您甚至不需要更改它。

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);
        }
    }
}

为什么更好?

这是实现这一目标的一种比Justin XL建议的更灵活的方法。

他的代码需要修改代码隐藏才能重用它。每次要重用它时,都需要设置EventName(在此行中EventName = "DoubleTapped")和Behavior(此行eventTriggerBehavior.Actions.Add(new ScrollViewerDoubleTap());)。

和这个答案一样,但对于阿瓦洛尼亚来说,如果有人像我一样需要它。

using System;
using Avalonia;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Metadata;
using Avalonia.Xaml.Interactivity;
namespace Project.Common.Extensions;
public class AttachedBehaviors : AvaloniaObject
{
    static AttachedBehaviors()
    {
        BehaviorsProperty.Changed.Subscribe(x => HandleBehaviorsChanged(x.Sender, x.NewValue.GetValueOrDefault<ObjectTemplate>()));
    }
    public static readonly AttachedProperty<ObjectTemplate> BehaviorsProperty = AvaloniaProperty.RegisterAttached<AttachedBehaviors, Interactive, ObjectTemplate>(
        "Behaviors", default(ObjectTemplate), false, BindingMode.OneTime);

    private static void HandleBehaviorsChanged(IAvaloniaObject element, ObjectTemplate? behaviorTemplate)
    {
        BehaviorCollection collection = null;
        if (behaviorTemplate != null) {
            var value = behaviorTemplate.Build();
            if (value is BehaviorCollection behaviorCollection)
                collection = behaviorCollection;
            else if (value is Behavior behavior)
                collection = new BehaviorCollection { behavior };
            else throw new Exception($"AttachedBehaviors should be a BehaviorCollection or an IBehavior.");
        }
        // collection may be null here, if e.NewValue is null
        Interaction.SetBehaviors(element, collection);
    }
    public static void SetBehaviors(AvaloniaObject element, ObjectTemplate commandValue)
    {
        element.SetValue(BehaviorsProperty, commandValue);
    }

    public static ObjectTemplate GetBehaviors(AvaloniaObject element)
    {
        return element.GetValue(BehaviorsProperty);
    }
}
public class ObjectTemplate
{
    [Content]
    [TemplateContent(TemplateResultType = typeof(object))]
    public object Content { get; set; }
    public object Build(object data = null) => TemplateContent.Load<object>(Content).Result;
}

然后添加样式(在此示例中转发 TextBox 按键事件以验证 ViewModel 中的输入):

    <UserControl.Styles>        
                <Style Selector="TextBox.Double">
                    <Setter Property="common:AttachedBehaviors.Behaviors">
                        <Setter.Value>
                            <common:ObjectTemplate>
                                <i:BehaviorCollection>
                                    <ia:EventTriggerBehavior EventName="KeyDown" SourceObject="{Binding $parent[TextBox]}">
                                    <ia:InvokeCommandAction Command="{Binding DoubleTextBoxKeyDownCommand}"
                                                            PassEventArgsToCommand="True">
                                    </ia:InvokeCommandAction>
                                </ia:EventTriggerBehavior>
                                    <ia:EventTriggerBehavior EventName="KeyUp" SourceObject="{Binding $parent[TextBox]}">
                                    <ia:InvokeCommandAction Command="{Binding DoubleTextBoxKeyDownCommand}"
                                                            PassEventArgsToCommand="True">
                                    </ia:InvokeCommandAction>
                                </ia:EventTriggerBehavior>
                                </i:BehaviorCollection>
                            </common:ObjectTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>    
    </UserControl.Styles>

最后是控件本身:

<TextBox Classes="Double"/>