简体   繁体   中英

Pass extra argument to command with InvokeCommandAction

Is there a way to pass an extra argument ( alongside the default argument) to a command with InvokeCommandAction from Microsoft.Xaml.Behaviors.Wpf ?

Like the following:

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

Here the passed argument is 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>

And here I want to pass the OtherElement and MouseButtonEventArgs together. Is there a way to specify the MouseButtonEventArgs argument?

The InvokeCommandAction supports exactly one CommandParameter , which is either the event arguments or the bound command parameter. If you try to do both, the command parameter will take precedence . Since the XAML behaviors are open source, you can see it yourself in the Invoke method of the class on Github.

In order to achieve passing both, you will have to write your own action. This would be an easy task, if you could simply create a derived type of InvokeCommandAction and override Invoke , but unfortunately it is sealed . That means, you have to copy the code for InvokeCommandAction and adapt it.

To start off, create a small class that encapsulates both the event arguments and the command parameter.

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

   public EventArgs EventArgs { get; }

   public object Parameter { get; }
}

Next, copy the code from GitHub . In essence, you have to replace explicit references to the InvokeCommandAction type with your custom type, here AdvancedInvokeCommandAction and of course adapt the Invoke method so that it creates a CompositeCommandParameter instance and calls the command with it.

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

In your XAML code, you can now use both. Since you will most probably use this action only with both parameters, you could further customize the action to remove the PassEventArgsToCommand parameter.

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

In your view model, the command will now get an object of type CompositeCommandParameter .


Here is the complete code for the 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;
   }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM