是否可以使用带有参数的 CallMethodAction 交互?

Is it possible to use the CallMethodAction interaction with an argument?

鉴于以下情况:

<Storyboard x:Key="Foo" AutoReverse="True" RepeatBehavior="3x">
    <Storyboard.Children/>
</Storyboard>

<DoubleAnimationUsingKeyFrames x:Key = "Bar"/>

<ei:DataTrigger
    Binding="{
        Binding SomeVar,
        ElementName=SomeElement,
        FallbackValue=False,
        Mode=OneWay}"
    Value="True">
    <ei:CallMethodAction
        TargetObject="{
            Binding Mode=OneWay,
            Path=Children,
            Source={StaticResource Foo}}"
        MethodName="Add"/>
</ei:DataTrigger>

有什么方法可以将 Bar 作为参数传递给方法调用 Children.Add

您可以尝试使用来自 Interactivity

InvokeCommandAction
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

<ei:DataTrigger
        Binding="{
        Binding SomeVar,
        ElementName=SomeElement,
        FallbackValue=False,
        Mode=OneWay}"
    Value="True">
    <i:InvokeCommandAction Command="{Binding SomeCommand, Source={StaticResource SomeViewModel}}" CommandParameter="Bar"/>
</ei:DataTrigger>

CallMethodAction 只能用于调用不带参数的方法或具有两个参数的方法,其中第一个参数是对象类型,第二个可以分配给 EventArgs 类型的变量。

鉴于此,您将无法使用 CallMethodAction 做您想做的事。但是,您可以制作自己的触发操作,该操作将调用您的方法并传入您指定的值。我只对此做了一些简单的测试,但它应该非常接近您的需要。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Interactivity;

namespace LocalActions
{
    public class CallUnaryMethodAction : TargetedTriggerAction<DependencyObject>
    {
        // The name of the method to invoke.
        public static readonly DependencyProperty MethodNameProperty =
            DependencyProperty.Register( "MethodName",
                typeof( string ),
                typeof( CallUnaryMethodAction ),
                new PropertyMetadata( OnNeedsMethodInfoUpdated ) );

        public string MethodName
        {
            get { return (string)GetValue( MethodNameProperty ); }
            set { SetValue( MethodNameProperty, value ); }
        }

        // Flag that lets us determine if we want to search non-public methods in our target object.
        public static readonly DependencyProperty AllowNonPublicMethodsProperty =
            DependencyProperty.Register( "AllowNonPublicMethods",
                typeof( bool ),
                typeof( CallUnaryMethodAction ),
                new PropertyMetadata( OnNeedsMethodInfoUpdated ) );

        public bool AllowNonPublicMethods
        {
            get { return (bool)GetValue( AllowNonPublicMethodsProperty ); }
            set { SetValue( AllowNonPublicMethodsProperty, value ); }
        }

        // Parameter we want to pass to our method. If this has not been set, then the value passed
        // to the trigger action's Invoke method will be used instead.
        public static readonly DependencyProperty ParameterProperty =
            DependencyProperty.Register( "Parameter",
                typeof( object ),
                typeof( CallUnaryMethodAction ) );

        public object Parameter
        {
            get { return GetValue( ParameterProperty ); }
            set { SetValue( ParameterProperty, value ); }
        }

        private static void OnNeedsMethodInfoUpdated( DependencyObject d, DependencyPropertyChangedEventArgs e )
        {
            var action = d as CallUnaryMethodAction;
            if( action != null )
                action.UpdateMethodInfo();
        }

        protected override void OnAttached()
        {
            UpdateMethodInfo();
        }

        protected override void OnTargetChanged( DependencyObject oldTarget, DependencyObject newTarget )
        {
            UpdateMethodInfo();
        }

        protected override void Invoke( object parameter )
        {
            object target = this.TargetObject ?? this.AssociatedObject;
            if( target == null )
                return;

            // Determine what we are going to pass to our method.
            object methodParam = ReadLocalValue( ParameterProperty ) == DependencyProperty.UnsetValue ?
                parameter : this.Parameter;

            // Pick the best method to call given the parameter we want to pass.
            Method methodToCall = m_methods.FirstOrDefault( method =>
                (methodParam != null) && method.ParameterInfo.ParameterType.IsAssignableFrom( methodParam.GetType() ) );

            if( methodToCall == null )
                throw new InvalidOperationException( "No suitable method found." );

            methodToCall.MethodInfo.Invoke( target, new object[] { methodParam } );
        }

        private void UpdateMethodInfo()
        {
            m_methods.Clear();
            object target = this.TargetObject ?? this.AssociatedObject;
            if( target == null || string.IsNullOrEmpty( this.MethodName ) )
                return;

            // Find all unary methods with the given name.
            BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
            if( this.AllowNonPublicMethods )
                flags |= BindingFlags.NonPublic;

            foreach( MethodInfo methodInfo in target.GetType().GetMethods( flags ) )
            {
                if( methodInfo.Name == this.MethodName )
                {
                    ParameterInfo[] parameters = methodInfo.GetParameters();
                    if( parameters.Length == 1 )
                        m_methods.Add( new Method( methodInfo, parameters[0] ) );
                }
            }

            // Order the methods so that methods with most derived parameters are ordered first.
            // This will help us pick the most appropriate method in the call to Invoke.
            m_methods = m_methods.OrderByDescending<Method, int>( method =>
            {
                int rank = 0;
                for( Type type = method.ParameterInfo.ParameterType; type != typeof( object ); type = type.BaseType )
                    ++rank;
                return rank;
            } ).ToList<Method>();
        }

        private List<Method> m_methods = new List<Method>();

        // Holds info on the list of possible methods we can call.
        private class Method
        {
            public Method( MethodInfo methodInfo, ParameterInfo paramInfo )
            {
                this.MethodInfo = methodInfo;
                this.ParameterInfo = paramInfo;
            }

            public MethodInfo MethodInfo { get; private set; }
            public ParameterInfo ParameterInfo { get; private set; }
        }
    }
}

然后您可以在 XAML 中使用它,就像通常在 CallMethodAction 中一样。您只需要引入适当的 XAML 命名空间。

...
xmlns:local="clr-namespace:LocalActions"
...

<ei:DataTrigger
    Binding="{
        Binding SomeVar,
        ElementName=SomeElement,
        FallbackValue=False,
        Mode=OneWay}"
    Value="True">
    <local:CallUnaryMethodAction
        TargetObject="{
            Binding Mode=OneWay,
            Path=Children,
            Source={StaticResource Foo}}"
        MethodName="Add"
        Parameter="{StaticResource Bar}"/>
</ei:DataTrigger>

这是假设您的 DoubleAnimationUsingKeyFrames 确实是一种资源(我根据您对 x:Key 的使用进行猜测)。如果这不合适,那么您需要根据需要调整绑定。