Automatic ICommand activation in WPF

By Mirek on (tags: comanding, DelegateCommand, mvvm, WPF, categories: code)

Today I will present you my solution for automatic command activation in WPF MvvM application.

Standard command implementation that can be binded in XAML looks similar to this

public ICommand DoSomethingCommand { get; protected set; }
 
private void DoSomethingExecute(string parameter)
{
    //perform command action here 
}
 
private bool CanDoSomethingExecute(string parameter)
{
    //decide if command can be invoked here
    return false;
}

then you have to create the instance of the command and assign it. I usually use the DelegateCommand which is a ICommand implementation with Actions inside.

DoSomethingCommand = new DelegateCommand<string>(DoSomethingExecute, CanDoSomethingExecute);

Now since the first part is necessary, because XAML must see the property of type ICommand on view model, and we have to put some logic for the command execution in above methods, but the second part is quite trivial. Creation of the command may be done automatically based on the signatures and types of the rest. For that we for sure need a Reflection.

 

public static void ActivateCommands(object instance)
       {
           Type instanceType = instance.GetType();
           //find all properties of type ICommand
           var commands = instanceType.GetProperties().Where(c => c.PropertyType.Equals(typeof(ICommand))).ToList();
 
           foreach (var commandProp in commands)
           {
               //skip command if it is already instanciated
               if (commandProp.GetValue(instance) != null) continue;
 
               string commandName = commandProp.Name;
               string fullCommName = commandName;
               //remove 'Command' suffix according to convention
               commandName = commandName.Remove(commandName.LastIndexOf("Command"));
 
               //find matching required execute method
               string methodName = commandName + "Execute";
 
               MethodInfo methInfo = null;
               try
               {
                   methInfo = instanceType.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
               }
               catch (AmbiguousMatchException ex)
               {
                   throw new ApplicationException("More than one matching method found for command '" + fullCommName + "' !", ex);
               }
               catch (Exception ex)
               {
                   throw new ApplicationException("Could not find a execute method for command '" + fullCommName + "' !", ex);
               }
 
               if (methInfo == null)
                   throw new ApplicationException("Could not find a execute method for command '" + fullCommName + "' !");
 
               //Validate the 'execute' method signature
               var methParams = methInfo.GetParameters();
               if (methParams.Count() > 1)
                   throw new ApplicationException("Command execute method may have zero or one parameter only!");
 
               //check the existence of parameter, there can be only one parameter for command
               bool isParametrized = methParams.Count() == 1;
               Type paramType = null;
               if (isParametrized)
                   paramType = methParams.First().ParameterType;
 
               //find matching optional 'Can execute' method
               string canMethodName = "Can" + commandName + "Execute";
               var canMethInfo = instanceType.GetMethod(canMethodName, BindingFlags.NonPublic | BindingFlags.Public
                                                             | BindingFlags.Instance);
               //Validate the 'can execute method' signature
               if (canMethInfo != null)
               {
                   if (canMethInfo.ReturnType != typeof(bool))
                       throw new ApplicationException("Command's 'can execute' method must return a boolean value!");
 
                   if (isParametrized)
                   {
                       if (canMethInfo.GetParameters().Count() != 1)
                           throw new ApplicationException("Command's 'can execute' method must have one parameter for '" + fullCommName + "' !");
                       if (canMethInfo.GetParameters().First().ParameterType != paramType)
                           throw new ApplicationException("Command's 'can execute' method parameter must match the one from execute method for '" + fullCommName + "' !");
                   }
                   else
                   {
                       if (canMethInfo.GetParameters().Count() > 0)
                           throw new ApplicationException("Command's 'can execute' method must not have a parameter for '" + fullCommName + "' !");
                   }
               }
 
               ICommand commInst = null;
 
               //create corresponding 'Can execute', 'Execute' methods and activate the command
               if (isParametrized)
               {
                   var execActionType = typeof(Action<>).MakeGenericType(paramType);
                   var commandType = typeof(DelegateCommand<>).MakeGenericType(paramType);
                   var execAction = Action.CreateDelegate(execActionType, instance, methodName);
 
                   if (canMethInfo == null)
                   {
                       commInst = (ICommand)Activator.CreateInstance(commandType, execAction);
                   }
                   else
                   {
                       var canExecFuncType = typeof(Func<,>).MakeGenericType(paramType, typeof(bool));
                       var canExecFunc = Action.CreateDelegate(canExecFuncType, instance, canMethodName);
                       commInst = (ICommand)Activator.CreateInstance(commandType, execAction, canExecFunc);
                   }
 
               }
               else
               {
                   var execAction = Action.CreateDelegate(typeof(Action), instance, methodName);
 
                   if (canMethInfo == null)
                       commInst = (ICommand)Activator.CreateInstance(typeof(DelegateCommand), execAction);
                   else
                   {
                       var canExecFuncType = typeof(Func<>).MakeGenericType(typeof(bool));
                       var canExecFunc = Action.CreateDelegate(canExecFuncType, instance, canMethodName);
                       commInst = (ICommand)Activator.CreateInstance(typeof(DelegateCommand), execAction, canExecFunc);
                   }
               }
 
               //set instance of the command to command property
               commandProp.SetValue(instance, commInst);
 
           }
 
       }

What is going on here? Well the first thing is a naming convention. Our assumption is that every command has ‘Command’ suffix in its name. Every execution method’s name is constructed of command name and ‘Execute’ suffix and every method that implements if command can be invoked  has name constructed of prefix ‘Can’ then command name and suffix ‘Execute’. So simply having above command defined as DoSomethingCommand we need to have DoSomethingExecute and CanDoSomethingExecute methods.

The logic of above activation method is based on reflection. First we find all properties of type ICommand then for each found command we try to find and match corresponding methods according to the naming convention. In addition we check if the execution method allows parameters which means that command may be called with parameter. Finally we try to create the instance of DelegateCommand with appropriate actions assigned.

That’s it. now we just need to define a command and implement its methods, the creation of the command is made automatically.

 

Greets