RelayCommand parameter passing in Xamarin

2019-02-18 14:30发布

问题:

I am very new to Xamarin cross-platform and while I did have some experience with WPF and MVVM I am still having issue understanding parameterized RelayCommand invocation using ICommand implementation below. Can someone explain how to properly pass and receive a CommandParameter from my View into my bound RelayCommand as this seems quiet different from a normal WPF version of RelayCommand:

    /// <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 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.
        /// </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>
        /// 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)
        {
            _execute();
        }

        /// <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(this, EventArgs.Empty);
            }
        }
    }

Before in WPF I used to have something like:

<Command="{Binding OpenMenuItemCommand}"
            CommandParameter="{Binding SelectedItem}"/>

and on ViewModel side:

  OpenMenuItemCommand = new RelayCommand(OpenMenuItem);
  ...
  public void OpenMenuItem(object sender, ItemTappedEventArgs args)
  {
  }

So my parameter would come through args.

回答1:

I believe you are getting events and commands confused. Some of the difference between the two are that you need to subscribe to events and events must occur. Commands can be called by anyone and also have the ability to be blocked.

So to get you example to work correctly you should modify your code to allow your RelayCommand to take an action with a parameter. This parameter will define the Type of the parameter. I would use something like MVVMLight which contains a Generic RelayCommand so that you don't have to write your own. Once that is done you should be able to change your code to look like this.

 OpenMenuItemCommand = new RelayCommand<MenuItem>(OpenMenuItem);
  ...
  public void OpenMenuItem(MenuItem item)
  {
  }

I wrote a small blog post that contains a full working project if you want to see a working example.



回答2:

Relay or Delegate Command for Xamarin

That's how I achieve it, I hope it'll be helpful for someone

public class DelegateCommand : ICommand
{
    /// <summary>
    /// The _execute
    /// </summary>
    private readonly Action _execute;

    /// <summary>
    /// The _can execute
    /// </summary>
    private readonly Func<bool> _canExecute;

    /// <summary>
    /// Initializes a new instance of the <see cref="DelegateCommand"/> class.
    /// </summary>
    /// <param name="execute">The execute.</param>
    /// <param name="canExecute">The can execute.</param>
    /// <exception cref="System.ArgumentNullException">execute</exception>
    public DelegateCommand(Action execute, Func<bool> canExecute)
    {
        _execute = execute ?? throw new ArgumentNullException("execute");

        if (canExecute != null)
        {
            this._canExecute = canExecute;
        }
    }

    /// <summary>
    /// Initializes a new instance of the DelegateCommand class that
    /// can always execute.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <exception cref="ArgumentNullException">If the execute argument is null.</exception>
    public DelegateCommand(Action execute)
        : this(execute, null)
    {
    }

    /// <summary>
    /// Occurs when changes occur that affect whether the command should execute.
    /// </summary>
    public event EventHandler CanExecuteChanged;


    /// <summary>
    /// Raises the can execute changed.
    /// </summary>
    public void RaiseCanExecuteChanged()
    {

        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }

    /// <summary>
    /// Defines the method that determines whether the command 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 || _canExecute.Invoke();
    }

    /// <summary>
    /// Defines the method to be called when the command is invoked.
    /// </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 virtual void Execute(object parameter)
    {
        if (CanExecute(parameter))
        {
            _execute.Invoke();
        }
    }
}

/// <summary>
/// This class allows delegating the commanding logic to methods passed as parameters,
/// and enables a View to bind commands to objects that are not part of the element tree.
/// </summary>
/// <typeparam name="T">Type of the parameter passed to the delegates</typeparam>
public class DelegateCommand<T> : ICommand
{
    /// <summary>
    /// The execute
    /// </summary>
    private readonly Action<T> _execute;

    /// <summary>
    /// The can execute
    /// </summary>
    private readonly Predicate<T> _canExecute;

    /// <summary>
    /// Initializes a new instance of the <see cref="DelegateCommand{T}" /> class.
    /// </summary>
    /// <param name="execute">The execute action.</param>
    /// <exception cref="System.ArgumentNullException">execute</exception>
    public DelegateCommand(Action<T> execute)
        : this(execute, null)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="DelegateCommand{T}" /> class.
    /// </summary>
    /// <param name="execute">The execute.</param>
    /// <param name="canExecute">The can execute predicate.</param>
    /// <exception cref="System.ArgumentNullException">execute</exception>
    public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
    {
        _execute = execute ?? throw new ArgumentNullException("execute");

        if (canExecute != null)
        {
            _canExecute = canExecute;
        }
    }

    /// <summary>
    /// Occurs when changes occur that affect whether the command should execute.
    /// </summary>
    public event EventHandler CanExecuteChanged;

    /// <summary>
    /// Raise <see cref="RelayCommand{T}.CanExecuteChanged" /> event.
    /// </summary>
    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }

    /// <summary>
    /// Determines whether this instance can execute the specified parameter.
    /// </summary>
    /// <param name="parameter">The parameter.</param>
    /// <returns><c>true</c> if this instance can execute the specified parameter; otherwise, <c>false</c>.</returns>
    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute.Invoke((T)parameter);
    }

    /// <summary>
    /// Executes the specified parameter.
    /// </summary>
    /// <param name="parameter">The parameter.</param>
    public virtual void Execute(object parameter)
    {
        if (CanExecute(parameter))
        {
            _execute((T)parameter);
        }
    }
}

in Your View

<Button Text="Login Command" Command="{Binding LoginCommand}" 
            CommandParameter="12345" />

in Your View Model

public ICommand LoginCommand { get; }
LoginCommand = new DelegateCommand<object>(
x =>
{
     // x will be containing 12345
     // your code 
});