Tuesday 14 April 2015

WinRt Relay Command with Parameter

If you start using the Out-of-The-Box RelayCommand in WinRt that in a phone project gets added to the Common folder and you start binding commands you quickly realize that even though there is an execute method that takes in a parameter:

/// <summary>
/// Executes the <see cref="RelayCommand"/> on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed,
/// this object can be set to null.
/// </param>
public void Execute(object parameter)
{
    _execute();
}

this method doesn't actually call the Action delegate "_execute()" with the Parameter, so why the hell is it there? seriously if someone knows please tell me.

What I did to get around this kerfuffle, is I added a private Action<object> delegate to store my command that expects a parameter.

private readonly Action<object> _executeWithParameter;

I then added added two constructors one that would accept my Action<object> delegate and a second that would accept both my Action<object> delegate and a func<bool> delegate for canExecute, much like the logic that is auto generated for you.

/// <summary>
/// Creates a new command that expects a parameter
/// </summary>
/// <param name="execute"></param>
public RelayCommand(Action<object> execute) : this(execute, null) { }


/// <summary>
/// Creates a new command that expects a parameter
/// </summary>
/// <param name="execute"></param>
/// <param name="canExecute"></param>
public RelayCommand(Action<object> execute, Func<bool> canExecute)
{
    if (execute == null)
        throw new ArgumentNullException("execute");
    _executeWithParameter = execute;
    _canExecute = canExecute;
}

now finally I modified the Execute Method to call the parameter-less _execute delegate if either the parameter or my _executeWithParameter delegate is null, otherwise I call my _executeWithParameter Delegate.

/// <summary>
/// Executes the <see cref="RelayCommand"/> on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
public void Execute(object parameter)
{
    if (parameter == null || _executeWithParameter == null)
        _execute();
    else
        _executeWithParameter(parameter);
}

Now I'm not 100% sure if this is the best way to skin this cat, but it's what I did and it's working, if someone has a better solution that doesn't involve installing a mvvm toolkit, I'm all ears. 

The Final class looks like the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace pc.activFitnes.phone81.Common
{
/// <summary>
/// A command whose sole purpose is to relay its functionality
/// to other objects by invoking delegates.
/// The default return value for the CanExecute method is 'true'.
/// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
/// <see cref="CanExecute"/> is expected to return a different value.
/// </summary>
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Action<object> _executeWithParameter;
private readonly Func<bool> _canExecute;

/// <summary>
/// Raised when RaiseCanExecuteChanged is called.
/// </summary>
public event EventHandler CanExecuteChanged;

/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute) : this(execute, null) { }

/// <summary>
/// Creates a new command that expects a parameter
/// </summary>
/// <param name="execute"></param>
public RelayCommand(Action<object> execute) : this(execute, null) { }

/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
    if (execute == null)
        throw new ArgumentNullException("execute");
    _execute = execute;
    _canExecute = canExecute;
}

/// <summary>
/// Creates a new command that expects a parameter
/// </summary>
/// <param name="execute"></param>
/// <param name="canExecute"></param>
public RelayCommand(Action<object> execute, Func<bool> canExecute)
{
    if (execute == null)
        throw new ArgumentNullException("execute");
    _executeWithParameter = execute;
    _canExecute = canExecute;
}

/// <summary>
/// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, 
/// this object can be set to null.
/// </param>
/// <returns>true if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter)
{
    return _canExecute == null ? true : _canExecute();
}

/// <summary>
/// Executes the <see cref="RelayCommand"/> on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, 
/// this object can be set to null.
/// </param>
public void Execute(object parameter)
{
    if (parameter == null || _executeWithParameter == null)
        _execute();
    else
        _executeWithParameter(parameter);
}

/// <summary>
/// Method used to raise the <see cref="CanExecuteChanged"/> event
/// to indicate that the return value of the <see cref="CanExecute"/>
/// method has changed.
/// </summary>
public void RaiseCanExecuteChanged()
{
    var handler = CanExecuteChanged;
    if (handler != null)
    {
        handler(thisEventArgs.Empty);
    }
}
}
}