2

I'm trying to create an instance of a RelayCommand with parameters dynamically:

public class RelayCommand<T> : ICommand
{
    #region Declarations

    private readonly Predicate<T> _canExecute;
    private readonly Action<T> _execute;

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the <see cref="RelayCommand&lt;T&gt;"/> class and the command can always be executed.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    public RelayCommand(Action<T> execute)
        : this(execute, null)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="RelayCommand&lt;T&gt;"/> class.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <param name="canExecute">The execution status logic.</param>
    public RelayCommand(Action<T> execute, Predicate<T> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");
        _execute = execute;
        _canExecute = canExecute;
    }

I have a ViewModel with multiple methods, for now I'll just list:

public void MyMethod(object parameter);
public bool CanMyMethod(object parameter);

I want to hook them dynamically to an instance of RelayCommand as follows:

ICommand command = new RelayCommand<ViewModel>((x)=>myviewmodel.MyMethod(myparameter),(x)=> myviewmodel.CanExecuteMyMethod(myparameter));

The previous line works, however, my Method names are passed in at runtime so I need to achieve the same thing but dynamically.

EDIT: Just Some Clarification: In my scenario I CANNOT refer to my methods by name directly. The Method Name that I will use to create a RelayCommand WILL BE PASSED AS STRING.

SOLUTION:

Here is my final solution, using @ZafarYousafi suggestion. Notice how I use a generic 'object' type for my RelayCommand and for Action and Predicate since that is the type of my methods parameters (object myparameter):

object myparameter = //Some value gets assigned here.
                Delegate td1 = null, td2 = null;
                MethodInfo method1 = myviewmodel.GetType().GetMethod("MyMethodNameAsString");

                if (tmethod1 != null)
                    td1 = Delegate.CreateDelegate(typeof(Action<object>), myviewmodel, method1);

                MethodInfo tmethod = viewmodel.GetType().GetMethod("Can" + "MyMethodNameAsString");
                if (method2 != null)
                    d2 = Delegate.CreateDelegate(typeof(Predicate<object>), myviewmodel, method2);

                if (d1 != null && d2 != null)
                {
                    item.Command = new RelayCommand<object>(obj => ((Action<object>) td1)(myparameter), obj => ((Predicate<object>)td2)(myparameter));
                }

Which should be equivalent to:

item.Command = new RelayCommand<object>(param=>myviewmodel.MyMethod(myparameter),param=>myviewmodel.CanMyMethod(myparameter));

IMPORTANT NOTE: As @DanC pointed out, the RelayCommand class created by Josh Smith is not intended to receive parameters at time of creation. In a well-architected MVVM Solution the RelayCommand parameter would be passed through XAML binding of CommandParameter property. So if you have a button.Command bound to a RelayCommand you also need to bind the button.CommandParameter as explained here: MVVM RelayCommand with parameters

OLD unsuccessful Attempt: This is what I have so far:

                Delegate d1 = null, d2 = null;
                MethodInfo method1 = myviewmodel.GetType().GetMethod("MyMethodNameAsString");
                if (method1 != null)
                    d1 = Delegate.CreateDelegate(typeof(Action<ViewModel>), myviewmodel, method1);

                MethodInfo method2 = myviewmodel.GetType().GetMethod("Can" + "MyMethodNameAsString");
                if (method2 != null)
                    d2 = Delegate.CreateDelegate(typeof(Predicate<ViewModel>), myviewmodel, method2);

                if (d1 != null && d2 != null)
                {
                    item.Command = new RelayCommand<ViewModel>((Action<ViewModel>)d1, (Predicate<ViewModel>)d2);
                }

That runs fine, no compilation or run-time errors, however I don't find how to pass my parameter through the RelayComand constructor parameters.

Any advice will be very appreciated,

Thanks

Related to my previous question

Community
  • 1
  • 1
Adolfo Perez
  • 2,834
  • 4
  • 41
  • 61

4 Answers4

1

According the code posted in the MVVM article by Josh Smith. You would use the lambda variable param to pass the parameter. In your example you are not using the "x" lambda variable at all. This variable should be the parameter for you Execute and CanExecute methods.

RelayCommand _saveCommand;
public ICommand SaveCommand
{
    get
    {
        if (_saveCommand == null)
        {
            _saveCommand = new RelayCommand(param => this.Save(),
                param => this.CanSave );
        }
        return _saveCommand;
    }
}

Assuming the command is created within the ViewModel then you would initialize it as follows.

ICommand command = new RelayCommand<MyParameterType>((myparameter)=>this.MyMethod(myparameter),(myparameter)=> this.CanExecuteMyMethod(myparameter));

Since your not able to use a lamba to construct the command your code would be as follows.

Delegate d1 = null, d2 = null;
            MethodInfo method1 = myviewmodel.GetType().GetMethod("MyMethodNameAsString");
            if (method1 != null)
                d1 = Delegate.CreateDelegate(typeof(Action<YourParameterType>), myviewmodel, method1);

            MethodInfo method2 = myviewmodel.GetType().GetMethod("Can" + "MyMethodNameAsString");
            if (method2 != null)
                d2 = Delegate.CreateDelegate(typeof(Predicate<YourParameterType>), myviewmodel, method2);

            if (d1 != null && d2 != null)
            {
                item.Command = new RelayCommand<YourParameterType>((Action<YourParameterType>)d1, (Predicate<YourParameterType>)d2);
            }

Now after the command has been assigned to the MenuItem object, which is the ICommandSource in this case, it will invoke your two delegates (d1,d2) with the CommandParameter.

Dan Carlstedt
  • 311
  • 2
  • 9
  • Understood, but again, I cannot refer to this.MyMethod directly because the method name "MyMethod" will come from my database (as a string) so I can't hard-code the reference to it... – Adolfo Perez Jun 21 '12 at 18:06
  • 1
    Can you show me the RelayCommand constructor? If its accepts just Action,Predicate then you can substitute Object for the generic type instead of your ViewModel. Then when RelayCommand actually calls the Execute and CanExecute it will pass the parameter to Action and Predicate. – Dan Carlstedt Jun 21 '12 at 19:10
  • 1
    Sorry, just noticed it does accept those param types. Since RelayCommand accepts Action, Predicate you don't need to pass it a parameter when you construct the command. When the RelayCommand object needs to invoke those delegates it should pass the parameter to them. In order for this to work properly I think you want to use Object as your generic type argument instead of ViewModel. – Dan Carlstedt Jun 21 '12 at 19:17
  • Thanks @DanC, I tried that but my parameter is sent as null when I don't specify it explicitly as @Zafar_Yousafi suggested: `item.Command = new RelayCommand(obj => ((Action) td1)(myparameter), obj => ((Predicate)td2)(myparameter));` But thanks for your valuable help – Adolfo Perez Jun 21 '12 at 20:09
  • I added some notes to the Solution based on your feedback. You're totally right. I did some tweaking and I didn't even have to send parameters anymore at time of creation! Thanks again – Adolfo Perez Jun 21 '12 at 20:34
0

It appears that on the site where RelayCommand instance is constructed, you've got everything you need to pass delegates from the methods of instance of myviemodel.

item.command = new RelayCommand<ViewModel>(
    myviemodel.MyMethod, myviewmodel.CanExecuteMyMethod)

The scenario you are describing is probably a job for Delegate.DynamicInvoke but I don't see a need for that in your snippet...

Paul Michalik
  • 4,331
  • 16
  • 18
-1

Just define a method on your RelayCommand class to execute the command like this:

public void Execute(T model)
    {
        if(_canExecute(model))
            _execute(model);
    }
Ventsyslav Raikov
  • 6,882
  • 1
  • 25
  • 28
  • Yeah, but that's kind of what I came here trying to avoid. In general I like .NET, but I do find that it tries to force one to make methods and even whole new methods in situations where that really shouldn't be necessary. Many of these situations seem to involve enumeration of comparison. – user1172763 Feb 24 '15 at 18:47
-4

u have typed cast the delegate into action and now u have full freedom to pass parameters ((Action<ViewModel>)d1)(yourparameter)

ZafarYousafi
  • 8,640
  • 5
  • 33
  • 39
  • This is not working for me, it says that: 'Argument of type 'type of my parameter' is not assignable to parameter type ViewModel' – Adolfo Perez Jun 21 '12 at 18:04
  • Ok, I got it working. My method parameter type is 'object' so I had to set that for the type of Action and Predicate RelayCommand Parameters. I will mark yours as the answer since it pointed me to the right solution. Thanks again @Zafar_Yousafi – Adolfo Perez Jun 21 '12 at 19:44
  • I am happy I am able to help you. – ZafarYousafi Jun 22 '12 at 08:23