Implementación de ICommand MVVM
Entonces, en esta implementación particular de MVVM que estoy haciendo, necesito varios comandos. Realmente me cansé de implementar las clases ICommand una por una, así que se me ocurrió una solución, pero no sé qué tan buena es, por lo que el aporte de cualquier experto en WPF aquí será muy apreciado. Y si pudieras ofrecer una solución mejor, aún mejor.
Lo que hice fue una única clase ICommand y dos delegados que toman un objeto como parámetro, un delegado es nulo (para OnExecute) y el otro bool (para OnCanExecute). Entonces, en el constructor de mi ICommand (que es llamado por la clase ViewModel) envío los dos métodos, y en cada método ICommand invoco los métodos de los delegados.
Funciona muy bien, pero no estoy seguro de si es una mala forma de hacerlo o si hay una mejor. A continuación se muestra el código completo. Cualquier aportación será muy apreciada, incluso negativa, pero sea constructivo.
Ver modelo:
public class TestViewModel : DependencyObject
{
public ICommand Command1 { get; set; }
public ICommand Command2 { get; set; }
public ICommand Command3 { get; set; }
public TestViewModel()
{
this.Command1 = new TestCommand(ExecuteCommand1, CanExecuteCommand1);
this.Command2 = new TestCommand(ExecuteCommand2, CanExecuteCommand2);
this.Command3 = new TestCommand(ExecuteCommand3, CanExecuteCommand3);
}
public bool CanExecuteCommand1(object parameter)
{
return true;
}
public void ExecuteCommand1(object parameter)
{
MessageBox.Show("Executing command 1");
}
public bool CanExecuteCommand2(object parameter)
{
return true;
}
public void ExecuteCommand2(object parameter)
{
MessageBox.Show("Executing command 2");
}
public bool CanExecuteCommand3(object parameter)
{
return true;
}
public void ExecuteCommand3(object parameter)
{
MessageBox.Show("Executing command 3");
}
}
Yo ordeno:
public class TestCommand : ICommand
{
public delegate void ICommandOnExecute(object parameter);
public delegate bool ICommandOnCanExecute(object parameter);
private ICommandOnExecute _execute;
private ICommandOnCanExecute _canExecute;
public TestCommand(ICommandOnExecute onExecuteMethod, ICommandOnCanExecute onCanExecuteMethod)
{
_execute = onExecuteMethod;
_canExecute = onCanExecuteMethod;
}
#region ICommand Members
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute.Invoke(parameter);
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
#endregion
}
Esto es casi idéntico a cómo Karl Shifflet demostró un RelayCommand
, donde Execute
dispara un predeterminado Action<T>
. Una solución de primer nivel, en mi opinión.
public class RelayCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public RelayCommand(Predicate<object> canExecute, Action<object> execute)
{
_canExecute = canExecute;
_execute = execute;
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
Esto luego podría usarse como...
public class MyViewModel
{
private ICommand _doSomething;
public ICommand DoSomethingCommand
{
get
{
if (_doSomething == null)
{
_doSomething = new RelayCommand(
p => this.CanDoSomething,
p => this.DoSomeImportantMethod());
}
return _doSomething;
}
}
}
Leer más:
Josh Smith (introductor de RelayCommand
): Patrones: aplicaciones WPF con el patrón de diseño MVVM
He escrito este artículo sobre la interfaz ICommand.
La idea es crear un comando universal que requiera dos delegados: uno se llama cuando ICommand.Execute (object param)
se invoca y el segundo verifica el estado y determina si se puede ejecutar el comando (ICommand.CanExecute (object param))
.
Requiere el método para cambiar de evento CanExecuteChanged
. Se llama desde los elementos de la interfaz de usuario para cambiar el CanExecute()
comando de estado.
public class ModelCommand : ICommand
{
#region Constructors
public ModelCommand(Action<object> execute)
: this(execute, null) { }
public ModelCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute != null ? _canExecute(parameter) : true;
}
public void Execute(object parameter)
{
if (_execute != null)
_execute(parameter);
}
public void OnCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
#endregion
private readonly Action<object> _execute = null;
private readonly Predicate<object> _canExecute = null;
}
Acabo de crear un pequeño ejemplo que muestra cómo implementar comandos en forma convencional sobre el estilo de configuración. Sin embargo, requiere que Reflection.Emit() esté disponible. El código de soporte puede parecer un poco extraño, pero una vez escrito se puede utilizar muchas veces.
Rompecabezas:
public class SampleViewModel: BaseViewModelStub
{
public string Name { get; set; }
[UiCommand]
public void HelloWorld()
{
MessageBox.Show("Hello World!");
}
[UiCommand]
public void Print()
{
MessageBox.Show(String.Concat("Hello, ", Name, "!"), "SampleViewModel");
}
public bool CanPrint()
{
return !String.IsNullOrEmpty(Name);
}
}
}
ACTUALIZACIÓN : ahora parece que existen algunas bibliotecas como http://www.codeproject.com/Articles/101881/Executing-Command-Logic-in-a-View-Model que resuelven el problema del código repetitivo de ICommand.