/* Licensed to the .NET Foundation under one or more agreements.
The .NET Foundation licenses this file to you under the MIT license.
See the LICENSE file in the project root for more information.
This file is inspired by the MvvmLight library (lbugnion/MvvmLight) */
using System.Runtime.CompilerServices;
namespace WPFluent.Input;
///
/// A generic command whose sole purpose is to relay its functionality to other objects by invoking delegates. The
/// default return value for the CanExecute method is . This class allows you to accept command
/// parameters in the and callback methods.
///
/// The type of parameter being passed as input to the callbacks.
public class RelayCommand : IRelayCommand
{
///
/// The optional action to invoke when is used.
///
private readonly Predicate? _canExecute;
///
/// The to invoke when is used.
///
private readonly Action _execute;
///
/// Initializes a new instance of the class that can always execute.
///
/// The execution logic.
///
/// Due to the fact that the interface exposes methods that accept a
/// nullable parameter, it is recommended that if is a reference type,
/// you should always declare it as nullable, and to always perform checks within .
///
/// Thrown if is .
public RelayCommand(Action execute)
{
if (execute is null)
{
throw new ArgumentNullException(nameof(execute));
}
_execute = execute;
}
///
/// Initializes a new instance of the class.
///
/// The execution logic.
/// The execution status logic.
///
/// Due to the fact that the interface exposes methods that accept a
/// nullable parameter, it is recommended that if is a reference type,
/// you should always declare it as nullable, and to always perform checks within .
///
/// Thrown if or are .
public RelayCommand(Action execute, Predicate canExecute)
{
if (execute is null)
{
throw new ArgumentNullException(nameof(execute));
}
if (canExecute is null)
{
throw new ArgumentNullException(nameof(canExecute));
}
_execute = execute;
_canExecute = canExecute;
}
///
public event EventHandler? CanExecuteChanged;
///
/// Throws an if an invalid command argument is used.
///
/// The input parameter.
/// Thrown with an error message to give info on the invalid parameter.
internal static void ThrowArgumentExceptionForInvalidCommandArgument(object? parameter)
{
[MethodImpl(MethodImplOptions.NoInlining)]
static Exception GetException(object? parameter)
{
if (parameter is null)
{
return new ArgumentException(
$"Parameter \"{nameof(parameter)}\" (object) must not be null, as the command type requires an argument of type {typeof(T)}.",
nameof(parameter));
}
return new ArgumentException(
$"Parameter \"{nameof(parameter)}\" (object) cannot be of type {parameter.GetType()}, as the command type requires an argument of type {typeof(T)}.",
nameof(parameter));
}
throw GetException(parameter);
}
///
/// Tries to get a command argument of compatible type from an input .
///
/// The input parameter.
/// The resulting value, if any.
/// Whether or not a compatible command argument could be retrieved.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool TryGetCommandArgument(object? parameter, out T? result)
{
// If the argument is null and the default value of T is also null, then the
// argument is valid. T might be a reference type or a nullable value type.
if (parameter is null && default(T) is null)
{
result = default;
return true;
}
// Check if the argument is a T value, so either an instance of a type or a derived
// type of T is a reference type, an interface implementation if T is an interface,
// or a boxed value type in case T was a value type.
if (parameter is T argument)
{
result = argument;
return true;
}
result = default;
return false;
}
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanExecute(T? parameter) { return _canExecute?.Invoke(parameter) != false; }
///
public bool CanExecute(object? parameter)
{
// Special case a null value for a value type argument type.
// This ensures that no exceptions are thrown during initialization.
if (parameter is null && default(T) is not null)
{
return false;
}
if (!TryGetCommandArgument(parameter, out T? result))
{
ThrowArgumentExceptionForInvalidCommandArgument(parameter);
}
return CanExecute(result);
}
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute(T? parameter) { _execute(parameter); }
///
public void Execute(object? parameter)
{
if (!TryGetCommandArgument(parameter, out T? result))
{
ThrowArgumentExceptionForInvalidCommandArgument(parameter);
}
Execute(result);
}
///
public void NotifyCanExecuteChanged() { CanExecuteChanged?.Invoke(this, EventArgs.Empty); }
}