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

Posted

技术标签:

【中文标题】是否可以使用带有参数的 CallMethodAction 交互?【英文标题】:Is it possible to use the CallMethodAction interaction with an argument? 【发布时间】:2016-02-12 15:06:58 【问题描述】:

鉴于以下情况:

<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

【问题讨论】:

【参考方案1】:

CallMethodAction 只能用于调用不带参数的方法或具有两个参数的方法,其中第一个参数是 object 类型,第二个参数可以分配给 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 的使用猜测)。如果这不合适,那么您需要根据需要调整绑定。

【讨论】:

我会试试这个。我将不得不制作另一个它也可以调用 Remove 的方法......但我可能已经找到了另一种使用我尚未测试过的数据绑定的更好方法......但这似乎最接近我想要的。跨度> 我的第一选择行不通。但这会的,我敢肯定。我会创建另一个帐户只是为了再次支持这个答案。【参考方案2】:

您可以尝试使用 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 交互?的主要内容,如果未能解决你的问题,请参考以下文章

是否可以保存带有参数的函数指针以供以后使用?

是否可以有一个带有可选参数的范围?

是否可以有一个带有 1 个未声明类型的输入参数的构造函数?

带有裁剪参数的拇指上传

传递带有参数的函数作为参数?

如何在带有命令行参数的 vbscript 中调用函数?