Tags

, , ,

El 8 de Octubre de 2005, Jhon Gossman, uno de los ingenieros responsables del desarrollo de WPF, daba a conocer un nuevo patrón de diseño de la capa de presentación, con las siglas MVVM, Model/View/ViewModel.

MVVM es una evolución del patrón MVC (Model/View/Controller), intenta facilitar el trabajo en paralelo entre alguien encargado en diseñar la interfaz de usuario y otra persona encargada de generar el código que la sustentará.

Este patrón consta de 3 partes bien diferenciadas:

  • La View se expresará en XAML (la interfaz de usuario).
  • La ViewModel contendrá el lenguaje que usaremos para desarrollar la lógica de presentación (VB.NET, C# o C++).
  • El Model será el encargado del acceso a datos y la lógica de negocio.

Algo que debemos tener presente es que las diferentes capas no se comunican todas con todas, la View y la ViewModel tienen una comunicación bidireccional, mientras que el Model sólo recibe órdenes de la ViewModel.

MVVM

Antes de entrar en materia de como aplicar este patrón lo que vamos hacer es recuperar el código del ejemplo que vimos en el post Mi primera aplicación.

OlaKAse.zip

A pesar de que este ejemplo es excesivamente sencillo, nos valdrá para ilustrar la facilidad de implementar el patrón MVVM y obtener los conceptos básicos para llevar a cabo este fin.

Tenéis que tener presente que este patrón a pesar de que lo estemos explicando en Windows Phone 8 (ya que tenemos el código de ejemplo hecho) funciona exactamente igual en cualquier plataforma que use XAML.

Lo primero que haremos es crear una serie de carpetas para ver como se estructurará el proyecto, empezaremos por la carpeta ViewModels y una nueva carpeta dentro que se llamara Base, de esta forma vamos a poder tener ciertos componentes que son básicos para las ViewModels y que se comparten entre ellos o que nos ayudan a emparejar una ViewModel con su View. Bien lo que vamos hacer es sacar toda nuestra lógica que implementamos en nuestra MainPage.xaml.cs a una clase externa, la nomenclatura que se sigue para nombrar las clases que irán en las ViewModels es usar el prefijo ViewModelNombre_de_la_página, VMMainPage.cs. Dentro de esta clase vamos añadir el siguiente código:

VMMainPage.cs
  1. public class VMMainPage : VMBase
  2. {
  3.     private string message;
  4.  
  5.     public string Message
  6.     {
  7.         get { return this.message; }
  8.         set
  9.         {
  10.             this.message = value;
  11.         }
  12.     }
  13.  
  14.     public VMMainPage()
  15.     {
  16.         Message = "MVPWay says hello!";
  17.     }
  18. }

En el XAML correspondiente a nuestra View (MaingPage) modificamos el TextBox.

MainPage.xaml
<TextBox Name="txtHola"
                     Text="{Binding Message, Mode=TwoWay}" />

La propiedad Mode del enlace a datos admite tres modos distintos, en WIndows Phone y Windows 8:

  • OneWay: El elemento obtiene el valor del enlace cada vez que hay un cambio en el origen, pero no lo modificia.
  • OneTime: Igual que OneWay, pero sólo se lleva a cabo una vez.
  • TwoWay: El elemento es capaz de obtener el valor del enlace y de asignar el valor de la propiedad fuente a la que enlaza.

De esta forma, como hemos colocado la propiedad Mode en TwoWay, si escribimos algo en el TextBox se enviará a la propiedad Message cuando pierda el foco.

Así, indicando el enlace de una propiedad tenemos evitamos mucho código que anteriormente usábamos para asignar y recuperar valores. Pero como comentamos anteriormente necesitamos algún mecanismo para que nuestra ViewModel se pueda comunicar con nuestra View. Aquí entra en escena el interfaz INotifyPropertyChanged, pero no directamente en cada ViewModel, ya que nos crea parte de código y como tendríamos que hacerlo en cada una de las ViewModels, es más sencillo crearnos una ViewModel Base (VMBase).

VMBase.cs
  1. public class VMBase : INotifyPropertyChanged
  2. {
  3.     public event PropertyChangedEventHandler PropertyChanged;
  4.  
  5.     public void RaisePropertyChanged(string propertyName)
  6.     {
  7.         var handler = PropertyChanged;
  8.  
  9.         if (handler != null)
  10.             handler(this, new PropertyChangedEventArgs(propertyName));
  11.     }
  12. }

Pero aún podemos hacer otra cosa más, si estamos usando el Framework 4.5, podemos usar el CallerMemberName, lo que hace es si no indicamos un argumento al llamar al método RaisePropertyChanged por defecto lo obtendrá del miembro de la clase que lo esté invocando. (Código alternativo)

VMBase.cs (Alternativa)
  1. public class VMBase : INotifyPropertyChanged
  2. {
  3.     public event PropertyChangedEventHandler PropertyChanged;
  4.  
  5.     public void RaisePropertyChanged([CallerMemberName]string propertyName = "")
  6.     {
  7.         var handler = PropertyChanged;
  8.  
  9.         if (handler != null)
  10.             handler(this, new PropertyChangedEventArgs(propertyName));
  11.     }
  12. }

Tened presente que si no deseamos usar este atributo, podemos también en vez de escribir el RaisePropertyChanged como un método, escribirlo como una propiedad.

Bien ahora lo único que debemos hacer es añadir la llamada a RaisePropertyChanged  después de modificar nuestra variable.

VMMainPage.cs
  1. public class VMMainPage : VMBase
  2. {
  3.     private string message;
  4.  
  5.     public string Message
  6.     {
  7.         get { return this.message; }
  8.         set
  9.         {
  10.             this.message = value;
  11.             RaisePropertyChanged();
  12.         }
  13.     }
  14.  
  15.     public VMMainPage()
  16.     {
  17.         Message = "MVPWay says hello!";
  18.     }
  19. }

De esta forma automáticamente si el valor de esta propiedad pública cambia se va a reflejar ese cambio en la View.

En la mayoría de las ocasiones las ViewModel deben ejecutar código bajo petición de un usuario, como es nuestro caso por medio del botón que teníamos implementado. Para llevar a cabo este cometido tenemos a nuestra disposición los comandos.

Un comando es un tipo de propiedad especial, representado por ICommand, que incorpora lo necesario para ejecutar código. Lo que vamos hacer es crear una clase dentro de la carpeta Base que se llame DelegateCommand.

Vamos a tener dos métodos, un método llamado CanExecute que nos va a indicar la posibilidad de ejecutar el comando y un método Execute que será el encargado de ejecutar el código del comando. También podemos observar un método CanExecuteChanged que será el encargado de mostrar el estado del CanExecute.

DelegateCommand.cs
  1. public class DelegateCommand : ICommand
  2. {
  3.     Action execute;
  4.     Func<bool> canExecute;
  5.  
  6.     public DelegateCommand(Action execute, Func<bool> canExecute)
  7.     {
  8.         this.execute = execute;
  9.         this.canExecute = canExecute;
  10.     }
  11.  
  12.     public bool CanExecute(object parameter)
  13.     {
  14.         if (this.canExecute != null)
  15.             return this.canExecute();
  16.  
  17.         return true;
  18.     }
  19.  
  20.     public event EventHandler CanExecuteChanged;
  21.  
  22.     public void Execute(object parameter)
  23.     {
  24.         this.execute();
  25.     }
  26. }

Esta clase podremos añadirle todo lo que queramos, pero aquí mostramos las bases para poder tener más o menos unas nociones básicas de este patrón.

Ahora en nuestra ViewModel añadiremos el siguiente código.

VMMainPage.cs
  1. public class VMMainPage : VMBase
  2. {
  3.     private string message;
  4.  
  5.     private DelegateCommand showMessageCommand;
  6.  
  7.     public string Message
  8.     {
  9.         get { return this.message; }
  10.         set
  11.         {
  12.             this.message = value;
  13.             RaisePropertyChanged();
  14.         }
  15.     }
  16.  
  17.     public ICommand ShowMessageCommand
  18.     {
  19.         get { return this.showMessageCommand; }
  20.     }
  21.  
  22.     public VMMainPage()
  23.     {
  24.         Message = "MVPWay says hello!";
  25.  
  26.         this.showMessageCommand = new DelegateCommand(ExecuteMessage, null);
  27.     }
  28.  
  29.     private void ExecuteMessage()
  30.     {
  31.         MessageBox.Show(this.Message);
  32.     }
  33. }

Ahora debemos enlazar el comando con nuestro botón ¿cómo hacemos esto? Todos los botones tienen una propiedad Command, solo tenemos que hacerle un Binding a esta propiedad.

MainPage.xaml
<Button Content="Hola" Command="{Binding ShowMessageCommand}"/>

Muchos de vosotros estaréis pensando, más o menos entiendo todo, pero ¿cuándo podre ver funcionar lo que llevamos? Antes de continuar vamos a ver como ver lo que llevamos que ya sería totalmente operativo Risa. En el constructor de nuestra View añadimos la siguiente línea.

MainPage.xaml.cs
this.DataContext = new VMMainPage();

¡Bien! ¡Funciona! ¡Ya hemos terminado! Aún no, pero falta muy poco, ¿no veis cuál es el problema? Pues con esta línea que hemos añadido aunque funciona, no es lo correcto. El patrón MVVM intenta deshacerse de la necesidad del code-behind y otro punto a tener en cuenta es que la asignación de nuestras ViewModels se encuentra en cada View, con lo que si necesitamos modificarla tendremos que ir a cada View.

Para evitar esto existe un concepto denominado locator. Se trata de una clase que añadiremos en ViewModels/Base que se encargará de exponer todas nuestras ViewModels mediante propiedades para que podamos usar enlace a datos y asignarlas a sus correspondientes Views. Para poder llevar a cabo esto necesitaremos añadir un contenedor de dependencias, en este ejemplo vamos a usar Autofac, pero se pueden usar otros; unity, metroIoC, simpleIoC, etc. (Para añadirlo lo podéis hacer por medio del gestor de NuGet).

VMLocator.cs
  1. public class VMLocator
  2. {
  3.     IContainer container;
  4.  
  5.     public VMMainPage MainViewModel
  6.     {
  7.         get { return container.Resolve<VMMainPage>(); }
  8.     }
  9.  
  10.     public VMLocator()
  11.     {
  12.         ContainerBuilder builder = new ContainerBuilder();
  13.  
  14.         builder.RegisterType<VMMainPage>();
  15.             
  16.         container = builder.Build();
  17.     }
  18. }

Ahora vamos añadir un nuevo namespace y crearemos un nuevo recuerso en nuestro App.xaml.

Una vez hecho esto sólo nos queda referenciar que el DataContent de nuestra página MainPage este enlazada con su ViewModel.

MainPage.xaml
DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">

Con esto y quitando la línea que añadimos antes

MainPage.xaml.cs
this.DataContext = new VMMainPage();

Ya tendríamos terminado nuestro proyecto, aplicado el patrón MVVM.

Tened en cuenta que esto que he explicado no es estrictamente obligatorio realizarlo así, es sólo una nomenclatura estándar que cualquier persona que abra un proyecto MVVM se va a encontrar, podéis tomarlo como un consejo de buenas prácticas.

Descargar_Ejemplo

Más información | Implementación del patrón MVVM