【问题标题】:Pass extra argument to command with InvokeCommandAction使用 InvokeCommandAction 将额外参数传递给命令
【发布时间】:2021-06-02 12:24:28
【问题描述】:

有没有办法将一个额外的参数(默认参数一起)传递给来自Microsoft.Xaml.Behaviors.Wpf 的带有InvokeCommandAction 的命令?

如下:

<behaviors:Interaction.Triggers>
    <behaviors:EventTrigger EventName="MouseDown">
        <behaviors:InvokeCommandAction Command="{Binding Command, Mode=OneWay}" PassEventArgsToCommand="True" />
    </behaviors:EventTrigger>
</behaviors:Interaction.Triggers>

这里传递的参数是MouseButtonEventArgs:

<behaviors:Interaction.Triggers>
    <behaviors:EventTrigger EventName="MouseDown">
        <behaviors:InvokeCommandAction Command="{Binding Command, Mode=OneWay}" PassEventArgsToCommand="True">
            <behaviors:InvokeCommandAction.CommandParameter>
                <MultiBinding Converter="{StaticResource ResourceKey=CommandConverter}">
                    <Binding ElementName="OtherElement" Mode="OneWay" />
                </MultiBinding>
            </behaviors:InvokeCommandAction.CommandParameter>
        </behaviors:InvokeCommandAction>
    </behaviors:EventTrigger>
</behaviors:Interaction.Triggers>

在这里我想将OtherElementMouseButtonEventArgs 一起传递。有没有办法指定MouseButtonEventArgs 参数?

【问题讨论】:

  • 在您的 MultiBinding 中 - 您可以添加任意数量的参数 - 它不起作用吗?
  • 只有当您没有明确设置CommandParameter 时,PassEventArgsToCommand 才会受到尊重。如果除了任何其他参数之外还需要事件参数,则需要实现自己的行为/附加属性。你有没有尝试过任何东西?问的问题太宽泛了。请参阅 stackoverflow.com/questions/6205472/… 了解许多不同的示例,其中一些可以根据您的明显需求进行调整。

标签: c# wpf xaml multibinding


【解决方案1】:

InvokeCommandAction 只支持一个CommandParameter,它可以是事件参数,也可以是绑定的命令参数。如果您尝试同时执行这两种操作,命令参数将优先。由于 XAML 行为是开源的,您可以在 Github 上的类的Invoke 方法中自己查看。

为了实现两者都通过,您必须编写自己的操作。如果您可以简单地创建InvokeCommandAction 的派生类型并覆盖Invoke,这将是一件容易的事,但不幸的是它是sealed。这意味着,您必须复制 InvokeCommandAction 的代码并进行修改。

首先,创建一个封装事件参数和命令参数的小类。

public class CompositeCommandParameter
{
   public CompositeCommandParameter(EventArgs eventArgs, object parameter)
   {
      EventArgs = eventArgs;
      Parameter = parameter;
   }

   public EventArgs EventArgs { get; }

   public object Parameter { get; }
}

接下来,复制code from GitHub。本质上,您必须用您的自定义类型替换对InvokeCommandAction 类型的显式引用,这里是AdvancedInvokeCommandAction,当然还要调整Invoke 方法,以便它创建一个CompositeCommandParameter 实例并使用它调用命令。

public sealed class AdvancedInvokeCommandAction : TriggerAction<DependencyObject>
{
   private string commandName;

   public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(AdvancedInvokeCommandAction), null);
   public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(AdvancedInvokeCommandAction), null);
   public static readonly DependencyProperty EventArgsConverterProperty = DependencyProperty.Register("EventArgsConverter", typeof(IValueConverter), typeof(AdvancedInvokeCommandAction), new PropertyMetadata(null));
   public static readonly DependencyProperty EventArgsConverterParameterProperty = DependencyProperty.Register("EventArgsConverterParameter", typeof(object), typeof(AdvancedInvokeCommandAction), new PropertyMetadata(null));
   public static readonly DependencyProperty EventArgsParameterPathProperty = DependencyProperty.Register("EventArgsParameterPath", typeof(string), typeof(AdvancedInvokeCommandAction), new PropertyMetadata(null));

   // ...other code.

   public object CommandParameter
   {
      get { return this.GetValue(AdvancedInvokeCommandAction.CommandParameterProperty); }
      set { this.SetValue(AdvancedInvokeCommandAction.CommandParameterProperty, value); }
   }

   // ...other code.

   protected override void Invoke(object parameter)
   {
      if (this.AssociatedObject != null)
      {
         ICommand command = this.ResolveCommand();

         if (command != null)
         {
            object eventArgs = null;
            object commandParameter = this.CommandParameter;

            //if no CommandParameter has been provided, let's check the EventArgsParameterPath
            if (!string.IsNullOrWhiteSpace(this.EventArgsParameterPath))
            {
               eventArgs = GetEventArgsPropertyPathValue(parameter);
            }

            //next let's see if an event args converter has been supplied
            if (eventArgs == null && this.EventArgsConverter != null)
            {
               eventArgs = this.EventArgsConverter.Convert(parameter, typeof(object), EventArgsConverterParameter, CultureInfo.CurrentCulture);
            }

            //last resort, let see if they want to force the event args to be passed as a parameter
            if (eventArgs == null && this.PassEventArgsToCommand)
            {
               eventArgs = parameter;
            }

            if (command.CanExecute(commandParameter))
            {
               var compositeCommandParameter = new CompositeCommandParameter((EventArgs) eventArgs, commandParameter);
               command.Execute(compositeCommandParameter);
            }
         }
      }
   }
   
   // ...other code.
}

在您的 XAML 代码中,您现在可以同时使用两者。由于您很可能只对这两个参数使用此操作,因此您可以进一步自定义操作以删除 PassEventArgsToCommand 参数。

<b:Interaction.Triggers>
   <b:EventTrigger EventName="MouseDown">
      <local:AdvancedInvokeCommandAction Command="{Binding Command, Mode=OneWay}"
                                         PassEventArgsToCommand="True"
                                         CommandParameter="{Binding ElementName=OtherElement}" />
   </b:EventTrigger>
</b:Interaction.Triggers>

在您的视图模型中,该命令现在将获取CompositeCommandParameter 类型的对象。


这是AdvancedInvokeCommandAction的完整代码。

public sealed class AdvancedInvokeCommandAction : TriggerAction<DependencyObject>
{
   private string commandName;

   public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(AdvancedInvokeCommandAction), null);
   public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(AdvancedInvokeCommandAction), null);
   public static readonly DependencyProperty EventArgsConverterProperty = DependencyProperty.Register("EventArgsConverter", typeof(IValueConverter), typeof(AdvancedInvokeCommandAction), new PropertyMetadata(null));
   public static readonly DependencyProperty EventArgsConverterParameterProperty = DependencyProperty.Register("EventArgsConverterParameter", typeof(object), typeof(AdvancedInvokeCommandAction), new PropertyMetadata(null));
   public static readonly DependencyProperty EventArgsParameterPathProperty = DependencyProperty.Register("EventArgsParameterPath", typeof(string), typeof(AdvancedInvokeCommandAction), new PropertyMetadata(null));

   /// <summary>
   /// Gets or sets the name of the command this action should invoke.
   /// </summary>
   /// <value>The name of the command this action should invoke.</value>
   /// <remarks>This property will be superseded by the Command property if both are set.</remarks>
   public string CommandName
   {
      get
      {
         this.ReadPreamble();
         return this.commandName;
      }
      set
      {
         if (this.CommandName != value)
         {
            this.WritePreamble();
            this.commandName = value;
            this.WritePostscript();
         }
      }
   }

   /// <summary>
   /// Gets or sets the command this action should invoke. This is a dependency property.
   /// </summary>
   /// <value>The command to execute.</value>
   /// <remarks>This property will take precedence over the CommandName property if both are set.</remarks>
   public ICommand Command
   {
      get { return (ICommand)this.GetValue(CommandProperty); }
      set { this.SetValue(CommandProperty, value); }
   }

   /// <summary>
   /// Gets or sets the command parameter. This is a dependency property.
   /// </summary>
   /// <value>The command parameter.</value>
   /// <remarks>This is the value passed to ICommand.CanExecute and ICommand.Execute.</remarks>
   public object CommandParameter
   {
      get { return this.GetValue(AdvancedInvokeCommandAction.CommandParameterProperty); }
      set { this.SetValue(AdvancedInvokeCommandAction.CommandParameterProperty, value); }
   }

   /// <summary>
   /// Gets or sets the IValueConverter that is used to convert the EventArgs passed to the Command as a parameter.
   /// </summary>
   /// <remarks>If the <see cref="Command"/> or <see cref="EventArgsParameterPath"/> properties are set, this property is ignored.</remarks>
   public IValueConverter EventArgsConverter
   {
      get { return (IValueConverter)GetValue(EventArgsConverterProperty); }
      set { SetValue(EventArgsConverterProperty, value); }
   }

   /// <summary>
   /// Gets or sets the parameter that is passed to the EventArgsConverter.
   /// </summary>
   public object EventArgsConverterParameter
   {
      get { return (object)GetValue(EventArgsConverterParameterProperty); }
      set { SetValue(EventArgsConverterParameterProperty, value); }
   }

   /// <summary>
   /// Gets or sets the parameter path used to extract a value from an <see cref= "EventArgs" /> property to pass to the Command as a parameter.
   /// </summary>
   /// <remarks>If the <see cref="Command"/> propert is set, this property is ignored.</remarks>
   public string EventArgsParameterPath
   {
      get { return (string)GetValue(EventArgsParameterPathProperty); }
      set { SetValue(EventArgsParameterPathProperty, value); }
   }

   /// <summary>
   /// Specifies whether the EventArgs of the event that triggered this action should be passed to the Command as a parameter.
   /// </summary>
   /// <remarks>If the <see cref="Command"/>, <see cref="EventArgsParameterPath"/>, or <see cref="EventArgsConverter"/> properties are set, this property is ignored.</remarks>
   public bool PassEventArgsToCommand { get; set; }

   /// <summary>
   /// Invokes the action.
   /// </summary>
   /// <param name="parameter">The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference.</param>
   protected override void Invoke(object parameter)
   {
      if (this.AssociatedObject != null)
      {
         ICommand command = this.ResolveCommand();

         if (command != null)
         {
            object eventArgs = null;
            object commandParameter = this.CommandParameter;

            //if no CommandParameter has been provided, let's check the EventArgsParameterPath
            if (!string.IsNullOrWhiteSpace(this.EventArgsParameterPath))
            {
               eventArgs = GetEventArgsPropertyPathValue(parameter);
            }

            //next let's see if an event args converter has been supplied
            if (eventArgs == null && this.EventArgsConverter != null)
            {
               eventArgs = this.EventArgsConverter.Convert(parameter, typeof(object), EventArgsConverterParameter, CultureInfo.CurrentCulture);
            }

            //last resort, let see if they want to force the event args to be passed as a parameter
            if (eventArgs == null && this.PassEventArgsToCommand)
            {
               eventArgs = parameter;
            }

            if (command.CanExecute(commandParameter))
            {
               var compositeCommandParameter = new CompositeCommandParameter((EventArgs) eventArgs, commandParameter);
               command.Execute(compositeCommandParameter);
            }
         }
      }
   }

   private object GetEventArgsPropertyPathValue(object parameter)
   {
      object commandParameter;
      object propertyValue = parameter;
      string[] propertyPathParts = EventArgsParameterPath.Split('.');
      foreach (string propertyPathPart in propertyPathParts)
      {
         PropertyInfo propInfo = propertyValue.GetType().GetProperty(propertyPathPart);
         propertyValue = propInfo.GetValue(propertyValue, null);
      }

      commandParameter = propertyValue;
      return commandParameter;
   }

   private ICommand ResolveCommand()
   {
      ICommand command = null;

      if (this.Command != null)
      {
         command = this.Command;
      }
      else if (this.AssociatedObject != null)
      {
         // todo jekelly 06/09/08: we could potentially cache some or all of this information if needed, updating when AssociatedObject changes
         Type associatedObjectType = this.AssociatedObject.GetType();
         PropertyInfo[] typeProperties = associatedObjectType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

         foreach (PropertyInfo propertyInfo in typeProperties)
         {
            if (typeof(ICommand).IsAssignableFrom(propertyInfo.PropertyType))
            {
               if (string.Equals(propertyInfo.Name, this.CommandName, StringComparison.Ordinal))
               {
                  command = (ICommand)propertyInfo.GetValue(this.AssociatedObject, null);
               }
            }
         }
      }

      return command;
   }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-07
    • 1970-01-01
    • 2011-03-06
    • 2021-02-27
    • 2012-01-04
    • 1970-01-01
    相关资源
    最近更新 更多