2009-09-23 21 views
39

Por lo tanto, en esta implementación particular de MVVM que estoy haciendo, necesito varios comandos. Realmente me cansé de implementar las clases de ICommand una a una, así que se me ocurrió una solución, pero no sé lo bueno que es, por lo que la contribución de cualquier experto de WPF será muy apreciada. Y si pudiera proporcionar una mejor solución, ¡aún mejor!Implementación WPF ICommand MVVM

Lo que hice fue una sola clase de ICommand y dos delegados que toman un objeto como parámetro, un delegado es nulo (para OnExecute), 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 si esta es una mala manera de hacerlo, o si hay una mejor manera. A continuación se muestra el código completo, cualquier contribución será muy apreciada, incluso negativa, pero sea constructivo.

Gracias!

ViewModel:

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"); 
    } 
} 

ICommand:

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 
} 
+2

Consulte la implementación de Karl Shifflet de RelayCommand: http://www.codeproject.com/KB/WPF/ExploringWPFMVVM.aspx –

Respuesta

48

Esta es casi idéntica a cómo Karl Shifflet demostró una RelayCommand, donde Execute incendios un predeterminado Action<T>. Una excelente solución, si me preguntas.

public class RelayCommand : ICommand 
{ 
    private Predicate<object> _canExecute; 
    private Action<object> _execute; 

    public RelayCommand(Predicate<object> canExecute, Action<object> execute) 
    { 
     this._canExecute = canExecute; 
     this._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 podría ser utilizado 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; 
     } 
    } 
} 
+0

Tiene un aspecto similar al mío. Sería interesante conocer los pros y los contras de usar esto. ¿Tienes un enlace al artículo o blog donde lees esto? – Carlo

+2

Utilizo este enfoque porque estoy trabajando con MVVM y funciona como un encanto;) – japf

+0

También lo estoy usando, lo único que puedo encontrar es que no tiene un atajo de teclado asignado al comando. ¿algunas ideas? –

11

que acaba de crear un poco de example mostrando cómo implementar comandos en la convención sobre el estilo de configuración. Sin embargo, requiere que Reflection.Emit() esté disponible. El código de soporte puede parecer un poco raro, pero una vez escrito puede usarse muchas veces.

Teaser:

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 parecen existir algunas librerías como http://www.codeproject.com/Articles/101881/Executing-Command-Logic-in-a-View-Model que resuelve el problema de código repetitivo ICommand.

11

He escrito esto article sobre la interfaz ICommand.

La idea: crear un comando universal que requiere dos delegados: uno se llama cuando se invoca ICommand.Execute (object param), el segundo comprueba el estado de si se puede ejecutar el comando (ICommand.CanExecute (object param)).

Requiere el método para cambiar el evento CanExecuteChanged. Se llama desde los elementos de la interfaz de usuario para cambiar el comando de estado CanExecute().

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; 
} 
1

Copyright: @Carlo me gusta mucho su aplicación a esto, pero quería compartir mi versión y cómo usarlo en mi modelo de vista

Primera implementar ICommand

public class Command : ICommand 
{ 
    public delegate void ICommandOnExecute(); 
    public delegate bool ICommandOnCanExecute(); 

    private ICommandOnExecute _execute; 
    private ICommandOnCanExecute _canExecute; 

    public Command(ICommandOnExecute onExecuteMethod, ICommandOnCanExecute onCanExecuteMethod = null) 
    { 
     _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() ?? true; 
    } 

    public void Execute(object parameter) 
    { 
     _execute?.Invoke(); 
    } 

    #endregion 
} 

Aviso He quitado el parámetro de ICommandOnExecute y ICommandOnCanExecute y agregó un valor nulo al constructor

entonces para usar en el modelo de vista

public Command CommandToRun_WithCheck 
{ 
    get 
    { 
     return new Command(() => 
     { 
      // Code to run 
     },() => 
     { 
      // Code to check to see if we can run 
      // Return true or false 
     }); 
    } 
} 

public Command CommandToRun_NoCheck 
{ 
    get 
    { 
     return new Command(() => 
     { 
      // Code to run 
     }); 
    } 
} 

acabo de encontrar de esta manera más limpia, ya que no necesita asignar variables de instancia y luego, todo hecho de una sola vez.

+0

¡Gracias por compartir esto! Definitivamente es interesante ver otras formas de resolver esto. Desde que leí acerca de RelayCommand, decidí adoptar ese patrón. No he hecho WPF en AÑOS, pero definitivamente utilicé el RelayCommand durante unos años antes de que la tendencia en mi empresa cambiara a la web. – Carlo