[Windows 10] MVVM en Apps Windows

MVVMIntroducción

Con la llegada de las herramientas de desarrollo de Windows 10 Technical Preview tenemos la culminación en el viaje hacia la convergencia en el desarrollo entre plataformas Windows.

Ahora hablamos de Apps Universales escritas una única vez con un código comun tanto para la lógica de negocio como para la interfaz de usuario. Además, generamos un único paquete que mantendrá una interfaz consistente y familiar para el usuario pero adaptada a cada plataforma.

Windows 10

Windows 10

Además de lanzarnos de pleno a analizar nuevos controles, APIs y herramientas, debemos abordar como implementar el patrón MVVM en Apps UAP para conseguir una buena arquitectura y compartir código.

El patrón MVVM

Model-View-ViewModel (MVVM) es un patrón de diseño de aplicaciones que permite desacoplar el código de interfaz de usuario del código que no sea de interfaz de usuario.

El patrón MVVM consta de 3 partes:

  • La vista (View) contiene la interfaz de usuario y su lógica.
  • El vista-modelo (ViewModel) contiene la lógica de presentación.
  • El modelo (Model) contiene la lógica de negocio junto a los datos.

La vista interactúa con el vista-modelo mediante enlace a datos (Data Binding) y mediante comandos:

tc02

Las ventajas conseguidas utilizando el patrón son significativas:

  • Nos permite dividir el trabajo de manera muy sencilla (diseñadores – desarrolladores)
  • El mantenimiento es más sencillo.
  • Permite realizar Test a nuestro código.
  • Permite una más fácil reutilización de código.

MVVM en escena!

Realmente podemos mantener una implementación MVVM muy similar a como hacíamos hasta ahora en Apps Universales Windows y Windows Phone 8,1.

Crearemos un nuevo proyecto UAP:

Nueva App UAP

Nueva App UAP

Comenzamos por la base. Contamos con dos ficheros clave en nuestro proyecto Universal para implementar el patrón MVVM:

  • PageBase
  • ViewModelBase

PageBase

Como revelamos con su nombre, será una clase de la que heredaran todas las páginas de nuestra aplicación. Y si, comentamos que serán todas las páginas de la aplicación (ya sean del proyecto Windows o del proyecto Windows Phone). Las páginas en ambas plataformas es exactamente igual, un objeto de tipo Page. Esta clase cuenta con varios objetivos:

  • Establecer el Frame activo en todo momento para que el acceso al mismo sea sencillo.
  • Permitir el acceso de los eventos de navegación desde nuestras viewmodels.

Además podría interesarnos además:

  • Gestionar las transiciones entre páginas.
  • Gestionar estados (Loading, etc.)
  • Gestionar el estado segun la conexión de red.

Con la funcionalidad básica (facilitarnos el acceso a Frame y eventos de navegación) quedaría:

public class PageBase : Page
{
        private ViewModelBase _vm;

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            _vm = (ViewModelBase)this.DataContext;
            _vm.SetAppFrame(this.Frame);
            _vm.OnNavigatedTo(e);
        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            base.OnNavigatedFrom(e);
            _vm.OnNavigatedFrom(e);
        }
}

Gestionamos los eventos básicos de navegación (al entrar y al salir de la página) de modo que, en el método OnNavigateTo obtenemos la viewmodel de la página y asignamos el Frame.

ViewModelBase

Continuamos con la segunda de nuestras clases base. En esta ocasión trataremos la clase base de la que heredarán todos los viewmodels. Los objetivos básicos de la clase son:

  • Notificar cambios (implementar INotifyPropertyChanged).
  • Acceso al objeto Frame que nos permitirá realizar la navegación.
  • Permitir el acceso a los eventos de navegación.
public abstract class ViewModelBase : INotifyPropertyChanged
{
        private Frame appFrame;
        private bool isBusy;

        public ViewModelBase()
        {
        }

        public Frame AppFrame
        {
            get { return appFrame; }
        }

        public bool IsBusy
        {
            get { return isBusy; }
            set
            {
                isBusy = value;
                RaisePropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public abstract Task OnNavigatedFrom(NavigationEventArgs args);

        public abstract Task OnNavigatedTo(NavigationEventArgs args);

        public void RaisePropertyChanged([CallerMemberName]string propertyName = "")
        {
            var Handler = PropertyChanged;
            if (Handler != null)
                Handler(this, new PropertyChangedEventArgs(propertyName));
        }

        internal void SetAppFrame(Frame viewFrame)
        {
            appFrame = viewFrame;
        }
}

El acceso a los eventos de navegación lo lograremos implementando en la viewmodel los métodos abstractos OnNavigatedFrom y OnNavigatedTo. Esto no permite guardar y recuperar parámetros o estados.

Definiendo Vistas. Utilizando PageBase

En la carpeta Views añadiremos nuestras vistas. Cada página será un objeto de tipo PageBase, tanto en XAML:

<base:PageBase
    x:Class="Navigation.Views.MainView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Navigation"
    xmlns:base="using:Navigation.Views.Base"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{Binding FirstViewModel, Source={StaticResource Locator}}">
    <Grid>
    </Grid>
</base:PageBase>

Como en el code-behind:

public sealed partial class MainView : PageBase
{
     public MainView()
     {
         this.InitializeComponent();
     }
}

La VistaModelo

Creamos una clase derivada de ViewModelBase:

public class FirstViewModel : ViewModelBase
{

}

Implementamos los métodos de navegación definidos en ViewModelBase:

public class FirstViewModel : ViewModelBase
{
    public override Task OnNavigatedFrom(NavigationEventArgs args)
    {
        return null;
    }

    public override Task OnNavigatedTo(NavigationEventArgs args)
    {
        return null;
    }
}

Asociando la Vista con la VistaModelo

Ahora, necesitamos conectar nuestra vista con nuestro viewmodel. Podemos hacerlo de múltiples formas, desde el constructor de la vista, instanciandola en App, usando Ioc. En nuestro caso, utilizaremos Ioc. Usaremos Unity.

Unity

Unity

Una vez añadida la referencia correspondiente en cada proyecto (Windows y Windows Phone) creamos una nueva clase en la carpeta Base dentro de la carpeta ViewModels llamada ViewModelLocator:

public class ViewModelLocator
{
     readonly IUnityContainer _container;

     public ViewModelLocator()
     {
            _container = new UnityContainer();

            _container.RegisterType<MainViewModel>();
     }

     public MainViewModel MainViewModel
     {
            get { return _container.Resolve<MainViewModel>(); }
     }
}

Sencillamente registramos nuestras viewmodels y creamos un par de propiedades públicas por cada viewmodel para poder resolverlas y acceder a ellas desde las vistas. A continuación, registramos nuestro locator en App.xaml:

<locator:ViewModelLocator x:Key="Locator" />

Asignamos la viewmodel como DataContext de nuestra viewmodel:

DataContext="{Binding MainViewModel, Source={StaticResource Locator}}"

Y todo preparado!.

Hel10 World!

Vamos a añadir un botón a nuestra interfaz que al ser pulsado muestre un mensaje en pantalla.

Añadimos el botón en nuestra vista (MainView.xaml):

<Button            
     Content="Hel10 World"            
     Command="{Binding HelloCommand}"
     HorizontalAlignment="Center" />

Nuestro botón cuenta con un comando definido en la ViewModel:

private ICommand _helloCommand;

public ICommand HelloCommand
{
     get { return _helloCommand = _helloCommand ?? new DelegateCommand(HelloCommandExecute); }
}

private void HelloCommandExecute()
{
     // TODO:    
}

Al ejecutar el comando debemos mostrar el mensaje. De momento, no hemos añadido lógica. Utilizaremos para mostrar el mensaje un servicio.

Servicios

Introducimos el concepto de servicio. Básicamente serán clases que cubrirán un escenario muy específico. Por ejemplo:

  • Gestionar Tiles
  • Enviar un email
  • Llamar por teléfono
  • Mostrar un mensaje

En lugar de escribir el código específico para esas acciones directamente en la ViewModel, lo haremos en clases separadas, nuestros servicios. Las ViewModels interactuarán con estos servicios para poder realizar y gestionar las acciones necesarias.

Primero definiremos nuestro servicio de diálogo mediante una sencilla interfaz:

public interface IDialogService
{
     void Show(string message);
}

Realizamos la implementación de nuestro servicio:

public class DialogService : IDialogService
{
     public void Show(string message)
     {
    var messageDialog = new MessageDialog(message);
    var result = messageDialog.ShowAsync();
     }
}

Muy simple. Utilizamos el MessageDialog para mostrar el mensaje recibido como parámetro en nuestro servicio.

En el ViewModelLocator registramos el servicio:

_container.RegisterType<IDialogService, DialogService>(new ContainerControlledLifetimeManager());

Visto como crear el servicio y como registrarlo queda la duda de como se resuelve y utiliza en la ViewModel. Bastará con declarar la interfaz de la clase como parámetro del constructor público de la ViewModel. Registramos el servicio y la ViewModel en nuestro contenedor de Unity, Unity lo resolverá automáticamente. Cada servicio necesario en la ViewModel es «inyectado» con la implementación declarada en nuestro contenedor.

De esta forma conseguimos:

  • No tener que crear nuevas instancias de nuestro servicio cada vez que sea necesario.
  • Tener el código para una acción concreta en una única clase, sin repeticiones similares en múltiples ViewModels.
  • Tenemos la capacidad de modificar la implementación a «inyectar» en la ViewModel de un servicio con suma facilidad.
public MainViewModel(IDialogService dialogService)
{
     _dialogService = dialogService;
}

Modificamos la ejecución del comando utilizando nuestro servicio de diálogo:

private void HelloCommandExecute()
{
     _dialogService.Show("Hello World!");
}

Si ejecutamos la App veremos algo como lo siguiente:

Hel10 World

Hel10 World

Al pulsar el botón, se ejecutará el comando definido en la ViewModel asignada como DataContext de la vista, que utilizará el servicio de dialogo inyectado en el constructor para mostrar el dialogo:

Mostrando mensaje

Mostrando mensaje

Podéis descargar el ejemplo realizado a continuación:

También podéis acceder al código fuente directamente en GitHub:

Ver GitHub

Recordar que cualquier tipo de duda o sugerencia la podéis dejar en los comentarios de la entrada.

Más información

25 pensamientos en “[Windows 10] MVVM en Apps Windows

  1. Pingback: [Windows 10] HTTP Live Streaming | Javier Suárez | Blog

  2. Pingback: [Windows 10] HTTP Live Streaming - Javier Suárez

  3. Pingback: [Windows 10] x:Bind, bindings compilados | Javier Suárez | Blog

  4. Pingback: [Windows 10] x:Bind, bindings compilados - Javier Suárez

  5. Pingback: [Windows 10] La nueva Title Bar | Javier Suárez | Blog

  6. Pingback: [Windows 10] La nueva Title Bar - Javier Suárez

  7. Pingback: [Tips and Tricks] Windows 10. Controlar si la App se ejecuta en pantalla completa | Javier Suárez | Blog

  8. Pingback: [Tips and Tricks] Windows 10. Controlar si la App se ejecuta en pantalla completa - Javier Suárez

  9. Pingback: [Windows 10] Novedades XAML: x:DeferLoadStrategy | Javier Suárez | Blog

  10. Pingback: [Windows 10] Password Vault | Javier Suárez | Blog

  11. Pingback: [Windows 10] Password Vault - Javier Suárez

  12. Pingback: [Windows 10] Trabajando con múltiples ventanas | Javier Suárez | Blog

  13. Pingback: [Windows 10] Trabajando con múltiples ventanas - Javier Suárez

  14. Pingback: [Tips and Tricks] Windows 10: Mostrar botón atrás en la TitleBar | Javier Suárez | Blog

  15. Pingback: [Windows 10] Utilizando x:Bind en un diccionario de recursos | Javier Suárez | Blog

  16. Pingback: [Windows 10] Utilizando x:Bind en un diccionario de recursos - Javier Suárez

  17. Pingback: [Tips and Tricks] Windows 10: Gestión de la batería | Javier Suárez | Blog

  18. Pingback: [Tips and Tricks] Windows 10: Gestión de la batería - Javier Suárez

  19. Pingback: [Windows 10] Experiencias multipantalla utilizando ProjectionManager | Javier Suárez | Blog

  20. Pingback: [Windows 10] Experiencias multipantalla utilizando ProjectionManager - Javier Suárez

  21. Hola, muchas gracias por tan buena explicación. No obstante yo tengo un problema que no se cómo resolver. Estoy tratando de empezar a entender MVVM, por lo que quizás mi duda sea muy básica.

    He seguido todos los pasos y examinado el proyecto demo (al compilar salen un montón de errores, supongo que usante alguna TP del SDK). Y al finalizar me sale un solo error, en la vista principal: Error CS1061 ‘MainPage’ no contiene una definición para ‘InitializeComponent’ ni se encuentra ningún método de extensión ‘InitializeComponent’ que acepte un primer argumento del tipo ‘MainPage’ (¿falta alguna directiva using o una referencia de ensamblado?)

    ¿Alguna idea de qué he hecho mal?; he repasado el código varias veces no veo nada erróneo.

    Muchas gracias; saludos

  22. Pingback: [Windows 10] Imprimiendo desde nuestras Apps UWP | Javier Suárez | Blog

Deja un comentario