MVVM Passing EventArgs As Command Parameter

115,084

Solution 1

It's not easily supported. Here's an article with instructions on how to pass EventArgs as command parameters.

You might want to look into using MVVMLight - it supports EventArgs in command directly; your situation would look something like this:

 <i:Interaction.Triggers>
    <i:EventTrigger EventName="Navigated">
        <cmd:EventToCommand Command="{Binding NavigatedEvent}"
            PassEventArgsToCommand="True" />
    </i:EventTrigger>
 </i:Interaction.Triggers>

Solution 2

I try to keep my dependencies to a minimum, so I implemented this myself instead of going with EventToCommand of MVVMLight. Works for me so far, but feedback is welcome.

Xaml:

<i:Interaction.Behaviors>
    <beh:EventToCommandBehavior Command="{Binding DropCommand}" Event="Drop" PassArguments="True" />
</i:Interaction.Behaviors>

ViewModel:

public ActionCommand<DragEventArgs> DropCommand { get; private set; }

this.DropCommand = new ActionCommand<DragEventArgs>(OnDrop);

private void OnDrop(DragEventArgs e)
{
    // ...
}

EventToCommandBehavior:

/// <summary>
/// Behavior that will connect an UI event to a viewmodel Command,
/// allowing the event arguments to be passed as the CommandParameter.
/// </summary>
public class EventToCommandBehavior : Behavior<FrameworkElement>
{
    private Delegate _handler;
    private EventInfo _oldEvent;

    // Event
    public string Event { get { return (string)GetValue(EventProperty); } set { SetValue(EventProperty, value); } }
    public static readonly DependencyProperty EventProperty = DependencyProperty.Register("Event", typeof(string), typeof(EventToCommandBehavior), new PropertyMetadata(null, OnEventChanged));

    // Command
    public ICommand Command { get { return (ICommand)GetValue(CommandProperty); } set { SetValue(CommandProperty, value); } }
    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(EventToCommandBehavior), new PropertyMetadata(null));

    // PassArguments (default: false)
    public bool PassArguments { get { return (bool)GetValue(PassArgumentsProperty); } set { SetValue(PassArgumentsProperty, value); } }
    public static readonly DependencyProperty PassArgumentsProperty = DependencyProperty.Register("PassArguments", typeof(bool), typeof(EventToCommandBehavior), new PropertyMetadata(false));


    private static void OnEventChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var beh = (EventToCommandBehavior)d;

        if (beh.AssociatedObject != null) // is not yet attached at initial load
            beh.AttachHandler((string)e.NewValue);
    }

    protected override void OnAttached()
    {
        AttachHandler(this.Event); // initial set
    }

    /// <summary>
    /// Attaches the handler to the event
    /// </summary>
    private void AttachHandler(string eventName)
    {
        // detach old event
        if (_oldEvent != null)
            _oldEvent.RemoveEventHandler(this.AssociatedObject, _handler);

        // attach new event
        if (!string.IsNullOrEmpty(eventName))
        {
            EventInfo ei = this.AssociatedObject.GetType().GetEvent(eventName);
            if (ei != null)
            {
                MethodInfo mi = this.GetType().GetMethod("ExecuteCommand", BindingFlags.Instance | BindingFlags.NonPublic);
                _handler = Delegate.CreateDelegate(ei.EventHandlerType, this, mi);
                ei.AddEventHandler(this.AssociatedObject, _handler);
                _oldEvent = ei; // store to detach in case the Event property changes
            }
            else
                throw new ArgumentException(string.Format("The event '{0}' was not found on type '{1}'", eventName, this.AssociatedObject.GetType().Name));
        }
    }

    /// <summary>
    /// Executes the Command
    /// </summary>
    private void ExecuteCommand(object sender, EventArgs e)
    {
        object parameter = this.PassArguments ? e : null;
        if (this.Command != null)
        {
            if (this.Command.CanExecute(parameter))
                this.Command.Execute(parameter);
        }
    }
}

ActionCommand:

public class ActionCommand<T> : ICommand
{
    public event EventHandler CanExecuteChanged;
    private Action<T> _action;

    public ActionCommand(Action<T> action)
    {
        _action = action;
    }

    public bool CanExecute(object parameter) { return true; }

    public void Execute(object parameter)
    {
        if (_action != null)
        {
            var castParameter = (T)Convert.ChangeType(parameter, typeof(T));
            _action(castParameter);
        }
    }
}

Solution 3

I've always come back here for the answer so I wanted to make a short simple one to go to.

There are multiple ways of doing this:

1. Using WPF Tools. Easiest.

Add Namespaces:

  • System.Windows.Interactivitiy
  • Microsoft.Expression.Interactions

XAML:

Use the EventName to call the event you want then specify your Method name in the MethodName.

<Window>
    xmlns:wi="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">

    <wi:Interaction.Triggers>
        <wi:EventTrigger EventName="SelectionChanged">
            <ei:CallMethodAction
                TargetObject="{Binding}"
                MethodName="ShowCustomer"/>
        </wi:EventTrigger>
    </wi:Interaction.Triggers>
</Window>

Code:

public void ShowCustomer()
{
    // Do something.
}

2. Using MVVMLight. Most difficult.

Install GalaSoft NuGet package.

enter image description here

Get the namespaces:

  • System.Windows.Interactivity
  • GalaSoft.MvvmLight.Platform

XAML:

Use the EventName to call the event you want then specify your Command name in your binding. If you want to pass the arguments of the method, mark PassEventArgsToCommand to true.

<Window>
    xmlns:wi="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:cmd="http://www.galasoft.ch/mvvmlight">

    <wi:Interaction.Triggers>
       <wi:EventTrigger EventName="Navigated">
           <cmd:EventToCommand Command="{Binding CommandNameHere}"
               PassEventArgsToCommand="True" />
       </wi:EventTrigger>
    </wi:Interaction.Triggers>
</Window>

Code Implementing Delegates: Source

You must get the Prism MVVM NuGet package for this.

enter image description here

using Microsoft.Practices.Prism.Commands;

// With params.
public DelegateCommand<string> CommandOne { get; set; }
// Without params.
public DelegateCommand CommandTwo { get; set; }

public MainWindow()
{
    InitializeComponent();

    // Must initialize the DelegateCommands here.
    CommandOne = new DelegateCommand<string>(executeCommandOne);
    CommandTwo = new DelegateCommand(executeCommandTwo);
}

private void executeCommandOne(string param)
{
    // Do something here.
}

private void executeCommandTwo()
{
    // Do something here.
}

Code Without DelegateCommand: Source

using GalaSoft.MvvmLight.CommandWpf

public MainWindow()
{
    InitializeComponent();

    CommandOne = new RelayCommand<string>(executeCommandOne);
    CommandTwo = new RelayCommand(executeCommandTwo);
}

public RelayCommand<string> CommandOne { get; set; }

public RelayCommand CommandTwo { get; set; }

private void executeCommandOne(string param)
{
    // Do something here.
}

private void executeCommandTwo()
{
    // Do something here.
}

3. Using Telerik EventToCommandBehavior. It's an option.

You'll have to download it's NuGet Package.

XAML:

<i:Interaction.Behaviors>
    <telerek:EventToCommandBehavior
         Command="{Binding DropCommand}"
         Event="Drop"
         PassArguments="True" />
</i:Interaction.Behaviors>

Code:

public ActionCommand<DragEventArgs> DropCommand { get; private set; }

this.DropCommand = new ActionCommand<DragEventArgs>(OnDrop);

private void OnDrop(DragEventArgs e)
{
    // Do Something
}

Solution 4

For people just finding this post, you should know that in newer versions (not sure on the exact version since official docs are slim on this topic) the default behavior of the InvokeCommandAction, if no CommandParameter is specified, is to pass the args of the event it's attached to as the CommandParameter. So the originals poster's XAML could be simply written as:

<i:Interaction.Triggers>
  <i:EventTrigger EventName="Navigated">
    <i:InvokeCommandAction Command="{Binding NavigatedEvent}"/>
  </i:EventTrigger>
</i:Interaction.Triggers>

Then in your command, you can accept a parameter of type NavigationEventArgs (or whatever event args type is appropriate) and it will automatically be provided.

Solution 5

I know this is a fairly old question, but I ran into the same problem today and wasn't too interested in referencing all of MVVMLight just so I can use event triggers with event args. I have used MVVMLight in the past and it's a great framework, but I just don't want to use it for my projects any more.

What I did to resolve this problem was create an ULTRA minimal, EXTREMELY adaptable custom trigger action that would allow me to bind to the command and provide an event args converter to pass on the args to the command's CanExecute and Execute functions. You don't want to pass the event args verbatim, as that would result in view layer types being sent to the view model layer (which should never happen in MVVM).

Here is the EventCommandExecuter class I came up with:

public class EventCommandExecuter : TriggerAction<DependencyObject>
{
    #region Constructors

    public EventCommandExecuter()
        : this(CultureInfo.CurrentCulture)
    {
    }

    public EventCommandExecuter(CultureInfo culture)
    {
        Culture = culture;
    }

    #endregion

    #region Properties

    #region Command

    public ICommand Command
    {
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }
    }

    public static readonly DependencyProperty CommandProperty =
        DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommandExecuter), new PropertyMetadata(null));

    #endregion

    #region EventArgsConverterParameter

    public object EventArgsConverterParameter
    {
        get { return (object)GetValue(EventArgsConverterParameterProperty); }
        set { SetValue(EventArgsConverterParameterProperty, value); }
    }

    public static readonly DependencyProperty EventArgsConverterParameterProperty =
        DependencyProperty.Register("EventArgsConverterParameter", typeof(object), typeof(EventCommandExecuter), new PropertyMetadata(null));

    #endregion

    public IValueConverter EventArgsConverter { get; set; }

    public CultureInfo Culture { get; set; }

    #endregion

    protected override void Invoke(object parameter)
    {
        var cmd = Command;

        if (cmd != null)
        {
            var param = parameter;

            if (EventArgsConverter != null)
            {
                param = EventArgsConverter.Convert(parameter, typeof(object), EventArgsConverterParameter, CultureInfo.InvariantCulture);
            }

            if (cmd.CanExecute(param))
            {
                cmd.Execute(param);
            }
        }
    }
}

This class has two dependency properties, one to allow binding to your view model's command, the other allows you to bind the source of the event if you need it during event args conversion. You can also provide culture settings if you need to (they default to the current UI culture).

This class allows you to adapt the event args so that they may be consumed by your view model's command logic. However, if you want to just pass the event args on verbatim, simply don't specify an event args converter.

The simplest usage of this trigger action in XAML is as follows:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="NameChanged">
        <cmd:EventCommandExecuter Command="{Binding Path=Update, Mode=OneTime}" EventArgsConverter="{x:Static c:NameChangedArgsToStringConverter.Default}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

If you needed access to the source of the event, you would bind to the owner of the event

<i:Interaction.Triggers>
    <i:EventTrigger EventName="NameChanged">
        <cmd:EventCommandExecuter 
            Command="{Binding Path=Update, Mode=OneTime}" 
            EventArgsConverter="{x:Static c:NameChangedArgsToStringConverter.Default}"
            EventArgsConverterParameter="{Binding ElementName=SomeEventSource, Mode=OneTime}"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

(this assumes that the XAML node you're attaching the triggers to has been assigned x:Name="SomeEventSource"

This XAML relies on importing some required namespaces

xmlns:cmd="clr-namespace:MyProject.WPF.Commands"
xmlns:c="clr-namespace:MyProject.WPF.Converters"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

and creating an IValueConverter (called NameChangedArgsToStringConverter in this case) to handle the actual conversion logic. For basic converters I usually create a default static readonly converter instance, which I can then reference directly in XAML as I have done above.

The benefit of this solution is that you really only need to add a single class to any project to use the interaction framework much the same way that you would use it with InvokeCommandAction. Adding a single class (of about 75 lines) should be much more preferable to an entire library to accomplish identical results.

NOTE

this is somewhat similar to the answer from @adabyron but it uses event triggers instead of behaviours. This solution also provides an event args conversion ability, not that @adabyron's solution could not do this as well. I really don't have any good reason why I prefer triggers to behaviours, just a personal choice. IMO either strategy is a reasonable choice.

Share:
115,084
Ahmed Ghoneim
Author by

Ahmed Ghoneim

Well done is better than well said.

Updated on December 15, 2020

Comments

  • Ahmed Ghoneim
    Ahmed Ghoneim over 3 years

    I'm using Microsoft Expression Blend 4
    I have a Browser ..,

    [ XAML ] ConnectionView " Empty Code Behind "

            <WebBrowser local:AttachedProperties.BrowserSource="{Binding Source}">
                <i:Interaction.Triggers>
                    <i:EventTrigger>
                        <i:InvokeCommandAction Command="{Binding LoadedEvent}"/>
                    </i:EventTrigger>
                    <i:EventTrigger EventName="Navigated">
                        <i:InvokeCommandAction Command="{Binding NavigatedEvent}" CommandParameter="??????"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </WebBrowser>  
    

    [ C# ] AttachedProperties class

    public static class AttachedProperties
        {
            public static readonly DependencyProperty BrowserSourceProperty = DependencyProperty . RegisterAttached ( "BrowserSource" , typeof ( string ) , typeof ( AttachedProperties ) , new UIPropertyMetadata ( null , BrowserSourcePropertyChanged ) );
    
            public static string GetBrowserSource ( DependencyObject _DependencyObject )
            {
                return ( string ) _DependencyObject . GetValue ( BrowserSourceProperty );
            }
    
            public static void SetBrowserSource ( DependencyObject _DependencyObject , string Value )
            {
                _DependencyObject . SetValue ( BrowserSourceProperty , Value );
            }
    
            public static void BrowserSourcePropertyChanged ( DependencyObject _DependencyObject , DependencyPropertyChangedEventArgs _DependencyPropertyChangedEventArgs )
            {
                WebBrowser _WebBrowser = _DependencyObject as WebBrowser;
                if ( _WebBrowser != null )
                {
                    string URL = _DependencyPropertyChangedEventArgs . NewValue as string;
                    _WebBrowser . Source = URL != null ? new Uri ( URL ) : null;
                }
            }
        }
    

    [ C# ] ConnectionViewModel Class

    public class ConnectionViewModel : ViewModelBase
        {
                public string Source
                {
                    get { return Get<string> ( "Source" ); }
                    set { Set ( "Source" , value ); }
                }
    
                public void Execute_ExitCommand ( )
                {
                    Application . Current . Shutdown ( );
                }
    
                public void Execute_LoadedEvent ( )
                {
                    MessageBox . Show ( "___Execute_LoadedEvent___" );
                    Source = ...... ;
                }
    
                public void Execute_NavigatedEvent ( )
                {
                    MessageBox . Show ( "___Execute_NavigatedEvent___" );
                }
        }
    

    [ C# ] ViewModelBase class Here

    Finally :
    Binding with commands works well and MessageBoxes shown


    My Question :
    How to pass NavigationEventArgs as Command Parameters when Navigated Event occurs ?

  • Ahmed Ghoneim
    Ahmed Ghoneim about 13 years
    then there is no direct method ? as I hate using templates which always have bugs .. etc , so I like coding from scratch
  • H.B.
    H.B. about 13 years
    @Ahmed Adel: That is a rather amusing statement.
  • Andres Scarpone
    Andres Scarpone about 13 years
    Really, just use MVVM Light. It's far simpler and you really only need to use the RelayCommand and EventToCommand classes.
  • Trident D'Gao
    Trident D'Gao over 11 years
    Silverlight/WPF isn't an easy thing in general, is it?
  • jeebs
    jeebs almost 11 years
    An acceptable level of boilerplate code to forgo having to adopt the use of another framework. Works well for me too, cheers!
  • Matthew
    Matthew almost 11 years
    Interesting solution. The only problem I have this with is that it places UI related code in the ViewModel. DragEventArgs is from System.Windows.Forms and ActionCommand is arguably UI related as well. I tend to keep my ViewModels extremely separated in their own assembly without any UI related references. It keeps me from accidentally crossing the 'line'. It's a personal preference and it is up to each developer how strict they want to adhere to the MVVM pattern.
  • Mike Fuchs
    Mike Fuchs almost 11 years
    Matthew, commands are perfectly valid in the MVVM pattern and belong on the ViewModel. It can be argued that EventArgs do not belong there, but if you don't like that you might want to comment it on the question, not on a solution to it. Btw, DragEventArgs is in System.Windows namespace for WPF.
  • Adarsha
    Adarsha over 10 years
    @Matthew I think we could just create a separate project and add EventToCommandBehavior and ActionCOmmand classes there. That way you can use the SYstem.Windows where needed, and avoid reference to System.Windows.Interactivity namespace, which hosts Behaviors.
  • Pete Stensønes
    Pete Stensønes over 9 years
    Can you provide a code (XAML and VM) example of this? As described its not working for me (WPF, .NET 4.5)
  • damccull
    damccull over 9 years
    Perfect solution for me. Awesome.
  • AzzamAziz
    AzzamAziz over 9 years
    Nope, not at all. I've been meaning to use MVVM Light but I have no use of it. This works just fine by itself.
  • AzzamAziz
    AzzamAziz about 9 years
  • AzzamAziz
    AzzamAziz about 9 years
    @DavidNichols the second one depends on MVVM Light.
  • Walter Williams
    Walter Williams over 8 years
    @adabyron Have you ever done this with multiple events? Can I just put multiple instances of this behavior in the xaml?
  • Mike Fuchs
    Mike Fuchs over 8 years
    @WalterWilliams Yes I've done it, that works without a problem.
  • Pompair
    Pompair over 8 years
    Hey, it doesn't seem to work that way. Hmm, that would have been too easy. :)
  • joshb
    joshb over 8 years
    I used this technique for a Windows 10 UWP app, not sure where all it works this way.
  • Conrad
    Conrad almost 8 years
    Using option 1 here just simplifies life incredibly. I disagree with "Using MVVMLight [is the m]ost difficult but best practice." If it adds extra complexity, and MS has already-included MVVM functionality that maintains separation of concerns, why add in 2 more packages if you don't have to?
  • Anton Shakalo
    Anton Shakalo almost 8 years
    It works for Prism InvokeCommandAction msdn.microsoft.com/en-us/library/…
  • AzzamAziz
    AzzamAziz almost 8 years
    Agreed. The "best practices" is regarding PRISM which does not apply here. I will update the answer, thank you!
  • Mr Tangjai
    Mr Tangjai over 7 years
    God damn! Thank you very very much. I can now pass the mouseeventargs to my Relaycommand.
  • AzzamAziz
    AzzamAziz over 7 years
    You got it! Glad i can help! :) @MrTangjai
  • IgorMF
    IgorMF almost 7 years
    You definitely need Prism for that behavior.
  • Paul
    Paul over 6 years
    This works beautifully when the parameter you want can be accessed through binding (OP wanted the EventArgs), and requires nothing else but the Interactivity namespace. Explicitly specifying the binding between CommandParameter and the element's SelectedItem was key for me, because I had tried just entering the string "SelectedItem", which of course didn't work. Cheers!
  • Maxim Gershkovich
    Maxim Gershkovich almost 6 years
    Just want to point out that the Telerik implementation has changed a bit since this post was made. Looks like this now: <telerik:EventToCommandBehavior.EventBindings> <telerik:EventBinding Command="{Binding CellEditEnded}" EventName="CellEditEnded" PassEventArgsToCommand="True" /> </telerik:EventToCommandBehavior.EventBindings>
  • Julian
    Julian over 5 years
    Option 1 is great (I see it like Conrad), and the answer should be the top voted one.
  • Informagic
    Informagic over 5 years
    Once again, Prism rocks. Plus, again, I should’ve gone to the manual before browsing the web. Thank you!
  • Tomas Kosar
    Tomas Kosar almost 5 years
    This seems to be the simplest solution. It may have not been like that always.
  • Paul Gibson
    Paul Gibson almost 5 years
    I'm not following Option 1: is your example ShowCustomer in the window.xaml.cs file? I don't see how this is using any parameter, especially if binding to an ICommand in a viewmodel. Can you clarify this?
  • Stepan Ivanenko
    Stepan Ivanenko over 4 years
    What is "cmd" namespace?
  • luis_laurent
    luis_laurent over 4 years
    The option 1 does not pass arguments to the method, it is incomplete that option or is not possible?
  • S_Mindcore
    S_Mindcore about 3 years
    The cmd namespace is the following : xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly‌​=GalaSoft.MvvmLight.‌​Platform"
  • Aaron. S
    Aaron. S almost 3 years
    this works but its missing PassEventArgsToCommand="True" in the InvokeCommandAction. adding that made it work
  • Eldoïr
    Eldoïr over 2 years
    Thanks for the option 3, saved my life! I'm using Telerik and wasn't aware at all this existed.