使用 InvokeCommandAction 将额外参数传递给命令
Pass extra argument to command with InvokeCommandAction
有没有办法将额外的参数(连同默认参数)传递给来自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>
这里我想一起传递 OtherElement
和 MouseButtonEventArgs
。有没有办法指定 MouseButtonEventArgs
参数?
InvokeCommandAction
只支持一个 CommandParameter
,它是事件参数或绑定的命令参数。如果您尝试同时执行这两项操作,命令参数将优先。由于 XAML 行为是开源的,您可以在 Github.
上的 class 的 Invoke
方法中自行查看
为了同时通过这两项,您必须编写自己的操作。如果您可以简单地创建 InvokeCommandAction
的派生类型并覆盖 Invoke
,这将是一项简单的任务,但不幸的是它是 sealed
。这意味着,您必须复制 InvokeCommandAction
的代码并进行调整。
首先,创建一个封装事件参数和命令参数的小 class。
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;
}
}
有没有办法将额外的参数(连同默认参数)传递给来自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>
这里我想一起传递 OtherElement
和 MouseButtonEventArgs
。有没有办法指定 MouseButtonEventArgs
参数?
InvokeCommandAction
只支持一个 CommandParameter
,它是事件参数或绑定的命令参数。如果您尝试同时执行这两项操作,命令参数将优先。由于 XAML 行为是开源的,您可以在 Github.
Invoke
方法中自行查看
为了同时通过这两项,您必须编写自己的操作。如果您可以简单地创建 InvokeCommandAction
的派生类型并覆盖 Invoke
,这将是一项简单的任务,但不幸的是它是 sealed
。这意味着,您必须复制 InvokeCommandAction
的代码并进行调整。
首先,创建一个封装事件参数和命令参数的小 class。
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;
}
}