'How to send EventArgs when using multiple parameters through InvokeCommandAction?

I would like to use InvokeCommandAction to send MouseButtonEventArgs.ChangedButton and a parameter of my choice.

I can send the arg value using TriggerParameterPath.

I can send multiple parameters using a MultiBinding along with a IMultiValueConverter.

How can I do both?



Solution 1:[1]

Newer Prism versions use the Microsoft.Xaml.Behaviors.Wpf package for the interactivity types, so the InvokeCommandAction type from there will be used in the following. For older versions using the legacy System.Windows.Interactivity types, the same is applicable.

If you inspect the code for the InvokeCommandAction type in the Invoke(object parameter) method, you can see that either the CommandParameter or the event arguments can be passed and if both are specified, the CommandParameter takes precedence. Consequently, by design this is not possible.

What you can do is create your own implementation of the InvokeCommanAction. Unfortunately, the type is marked sealed for some reason, so you are left to copy the implementation and adapt it.

The following serves as an example that you can vary to fit your requirements. Create a type that can hold 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; }
}

Copy the original code of the InvokeCommandAction and adapt the Invoke method like this.

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

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

         if (!string.IsNullOrWhiteSpace(EventArgsParameterPath))
         {
            eventArgs = GetEventArgsPropertyPathValue(parameter);
         }

         if (eventArgs == null && EventArgsConverter != null)
         {
            eventArgs = EventArgsConverter.Convert(parameter, typeof(object), EventArgsConverterParameter, CultureInfo.CurrentCulture);
         }

         if (eventArgs == null && PassEventArgsToCommand)
         {
            eventArgs = parameter;
         }

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

The only difference to the original code is that now an instance of CompositeCommandParameter will be created with the event arguments and the CommandParameter that is passed to your bound command.

Just for lazyness and easy setup, this would be the full command. As a side note, the PassEventArgsToCommand may no longer be needed, as they should always be passed now.

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

   public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CompositeInvokeCommandAction), null);
   public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(CompositeInvokeCommandAction), null);
   public static readonly DependencyProperty EventArgsConverterProperty = DependencyProperty.Register("EventArgsConverter", typeof(IValueConverter), typeof(CompositeInvokeCommandAction), new PropertyMetadata(null));
   public static readonly DependencyProperty EventArgsConverterParameterProperty = DependencyProperty.Register("EventArgsConverterParameter", typeof(object), typeof(CompositeInvokeCommandAction), new PropertyMetadata(null));
   public static readonly DependencyProperty EventArgsParameterPathProperty = DependencyProperty.Register("EventArgsParameterPath", typeof(string), typeof(CompositeInvokeCommandAction), 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
      {
         ReadPreamble();
         return commandName;
      }
      set
      {
         if (CommandName != value)
         {
            WritePreamble();
            commandName = value;
            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)GetValue(CommandProperty); }
      set { 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 GetValue(CommandParameterProperty); }
      set { SetValue(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 (AssociatedObject != null)
      {
         ICommand command = ResolveCommand();

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

            if (!string.IsNullOrWhiteSpace(EventArgsParameterPath))
            {
               eventArgs = GetEventArgsPropertyPathValue(parameter);
            }

            if (eventArgs == null && EventArgsConverter != null)
            {
               eventArgs = EventArgsConverter.Convert(parameter, typeof(object), EventArgsConverterParameter, CultureInfo.CurrentCulture);
            }

            if (eventArgs == null && 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 (Command != null)
      {
         command = Command;
      }
      else if (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 = 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, CommandName, StringComparison.Ordinal))
               {
                  command = (ICommand)propertyInfo.GetValue(AssociatedObject, null);
               }
            }
         }
      }

      return command;
   }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 thatguy