TUTORIAL WINDOWS PHONE – 4. INTRODUCCIÓN MVVM [PARTE 4]

¡Hola chicos!

Hoy voy a explicaros un tema que tenía pendiente aquí desde hace meses. Es la parte de los Commands en Windows. Cuando empezamos a ver MVVM, nos faltaba una parte fundamental para ver: enlazar las interacciones que tenemos en la View a la ViewModel con comandos.

En la View en algunos Controles como Button, MenuItem, etc., tenemos directamente la propiedad "Command" y "Command Parameter" si queremos pasar un parámetro al comando. Si queremos utilizar un Control, por ejemplo un Stackpanel, Grid, etc., que no poseen la propiedad Command, debemos de utilizar la assembly Interactivity. Este paquete no es mas que una exportación del behavior de Blend para soporte de entradas táctiles. Con esto podemos captar los "Taps" a los controles de nuestros dedos e interactuar con un Command, que estará esperando en nuestra ViewModel.

Para poder utilizar dichos comandos, tenemos que tener una clase llamada DelegateCommand o RelayCommand. Es heredada de ICommand. Aquí va un ejemplo de ello:

/// <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'.  This class does not allow you to accept command parameters in the /// Execute and CanExecute callback methods. /// </summary> public class DelegateCommand : ICommand { private Action execute; private Func<bool> canExecute; /// <summary> /// Initializes a new instance of the <see cref="DelegateCommand"/> class. /// </summary> /// <param name="exec">The execute.</param> public DelegateCommand(Action exec) : this(exec, null) { } /// <summary> /// Initializes a new instance of the <see cref="DelegateCommand"/> class. /// </summary> /// <param name="exec">The execute.</param> /// <param name="canExec">The can execute.</param> public DelegateCommand(Action exec, Func<bool> canExec) { this.execute = exec; this.canExecute = canExec; } /// <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) { if (canExecute == null) { return true; } return canExecute(); } public event EventHandler CanExecuteChanged; /// <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 void Execute(object parameter) { if (execute != null) { execute(); } } public void RaiseCanExecuteChanged() { if (CanExecuteChanged != null) { CanExecuteChanged(null, new EventArgs()); } } } /// <summary> /// 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 'true'. This class allows you to accept command parameters in the /// Execute and CanExecute callback methods. /// </summary> /// <typeparam name="T">The type of the command parameter.</typeparam> public class DelegateCommand<T> : ICommand { private Action<T> execute; private Func<T, bool> canExecute; /// <summary> /// Initializes a new instance of the <see cref="DelegateCommand{T}"/> class. /// </summary> /// <param name="exec">The execute.</param> /// <param name="canExec">The can execute.</param> public DelegateCommand(Action<T> exec, Func<T, bool> canExec) { this.execute = exec; this.canExecute = canExec; } /// <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) { if (canExecute == null) return true; return canExecute((T)parameter); } public event EventHandler CanExecuteChanged; /// <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 void Execute(object parameter) { if (execute != null) execute((T)parameter); } /// <summary> /// Raises the can execute changed. /// </summary> public void RaiseCanExecuteChanged() { if (CanExecuteChanged != null) CanExecuteChanged(null, new EventArgs()); } }

Una vez implementada la clase, vamos a nuestra ViewModel y declaramos nuestra ICommand con un nombre específico, el que tenemos que utilizar luego en nuestra View. Hay varias formas de hacer esto, así que os daré dos formas de hacerlo. Siempre hay maneras de implementarlo mejor o peor así que os animo dar otra alternativa :D

De la manera que utilizaba antes:

public ICommand TestCommand { get { return new DelegateCommand(TestCommandExec,TestCommandCanExec);} } private bool TestCommandCanExec() { return true; } private void TestCommandExec() { MessageBox.Show("Soy un comando! yuhu!"); }

De la manera que utilizo ahora:

public MainViewModel() { TestCommand = new DelegateCommand(TestCommandExec, TestCommandCanExec); } public ICommand TestCommand { get; private set; } private bool TestCommandCanExec() { return true; } private void TestCommandExec() { MessageBox.Show("Soy un comando! yuhu!"); }

Aparte de la diferencia que hay de tener que escribir un poquito más declarando el DelegateCommand en el Constructor, así nos ahorramos que, cada vez que llamamos al commando, de la primera forma se crearía siempre una nueva instancia del Delegate, donde puede causar un poco más de recursos. Además de perder el control del Delegate ya que cada vez que llamamos será una nueva instancia.

Vemos que podemos optar a tener dos métodos cuando un Icommand es ejecutada desde la View.

Métodos CanExecute y Execute.

CanExecute: es el encargado de revisar si esta interacción con nuestros dedo puede realizarse o no. La lógica que podamos escribir en ella es totalmente libre.Execute: Lo que debe ejecutar nuestra App cuando el usuario toque dicho control.

Una Vez terminada en la ViewModel, vamos a nestra View, quien tenemos vinculado con la ViewModel correspondiente visto en los tutoriales anteriores, y Bindeamos nuestro Command a un Control. Os pongo de las dos formas en que podemos enlazarlo, bien sea por un Control soportado la propiedad Command o bien sea por Interactivity quien soporta los behaviors con la propiedad Command. Para utilizar Interactivity, debemos combrobar que tenemos anadida la referencia "System.Windows.Interactivity", sino la tenemos que anadir desde NuGet.

Un Button, que tiene soporte de la Propiedad Command:

<Button Content="Soy un Command!" Command="{Binding TestCommand}"></Button>

Un UserControl, que no soporta Command Implícitamente. Pero gracias a System.Windows.Interactivity podemos optar a sus entradas por gesto convirtiéndolo en Commands:

<UserControl> <i:Interaction.Triggers> <i:EventTrigger EventName="Tap"> <i:InvokeCommandAction Command="{Binding TestCommand}"/> </i:EventTrigger> </i:Interaction.Triggers> </UserControl>

Ahora, si ejecutamos la App, dicho sea de paso queda igual en WPF, Windows Phone 7, Windows Phone 8 o Windows 8. Os saldrá un MessageBox cuando ejecutaréis uno de los commandos.

Espero haberos aclarado algo el uso de los Commands, si tenéis alguna duda, aquí abajo tenéis los comentarios que gustosamente responderé si puedo.

Si os ha gustado y os ha sido útil, compartid para que llegue a más gente.

¡Un saludo!