使用 InvokeCommandAction 将额外参数传递给命令

问题描述 投票:0回答:2

有没有办法通过 InvokeCommandAction 中的

Microsoft.Xaml.Behaviors.Wpf
将额外参数(
与默认参数一起)传递给命令?

像下面这样:

<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
 参数?

c# wpf xaml multibinding
2个回答
3
投票

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; } }
接下来,从 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; } }
    

0
投票
您可以使用

EventArgsConverter

EventArgsConverterParameter
 属性来创建一个简单的 
IValueConverter
,而不是发明自己的行为,它结合了原始事件参数和通常作为 
CommandParameter
 传递的对象:

public class ContextualEventArgs : EventArgs { public object? Context { get; } public EventArgs OriginalEventArgs { get; } public ContextualEventArgs(EventArgs original, object? context) : base() { Context = context; OriginalEventArgs = original; } }
public class ContextualEventArgsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is EventArgs args)
        {
            return new ContextualEventArgs(args, parameter);
        }

        return Binding.DoNothing;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
然后您可以将通常用作 

CommandParameter

 的任何内容放入 
EventArgsConverterParameter
 中。我在具有 
ItemsControl.ItemTemplate
ComboBox
 中使用它,将项目的 
DataContext
 添加到 
SelectionChanged
 事件参数:

<!-- In Resources: <conv:ContextualEventArgsConverter x:Key="ContextualEventArgsConverter" /> --> <ComboBox ItemsSource="{Binding Source={StaticResource Modes}}" SelectedItem="{Binding Mode, Mode=OneWay}"> <i:Interaction.Triggers> <i:EventTrigger EventName="SelectionChanged"> <i:InvokeCommandAction Command="{Binding CmdItemModeSelectionChanged, Source={StaticResource CommandContainer}}" EventArgsConverter="{StaticResource ContextualEventArgsConverter}" EventArgsConverterParameter="{Binding .}" /> </i:EventTrigger> </i:Interaction.Triggers> </ComboBox>
// In ctor:
//     CmdItemModeSelectionChanged = new DelegateCommand<ContextualEventArgs>(ExecuteItemModeSelectionChanged);

private void ExecuteItemModeSelectionChanged(ContextualEventArgs args)
{
    if (args.Context is MyItem item && args.OriginalEventArgs is SelectionChangedEventArgs sc)
    {
        if (sc.AddedItems.Count == 1 && sc.AddedItems[0] is Mode newMode && item.Mode != newMode)
        {
            ChangeItemMode(item, newMode);
        }
    }
}
    
© www.soinside.com 2019 - 2024. All rights reserved.