Buenas prácticas aplicando MVVM: crear un framework propio

jueves, 5 de junio de 2014


Ya hemos visto una introducción a buenas prácticas aplicando MVVM, también veíamos cuando podía ser un buen momento para crearnos un framework propio sencillo. Yo comentaba que cuando nos estamos introduciendo en el patrón o cuando estamos afrontando un escenario sencillo sin páginas entre las que haya que navegar pueden ser buenos momentos porque el framework que necesitamos es más sencillo y manejable, de lo contrario yo particularmente utilizaría un framework existente.

Crearnos un framework propio en algún momento considero que es una buena práctica para sentar unas buenas bases de conocimiento sobre el patrón MVVM y si encima desarrollamos habitualmente aplicaciones sencillas, vamos a reutilizar mucho código repetitivo que hay que escribir si no utilizamos un framework de los que hay.

Vamos a ver lo básico que debe tener un framework MVVM.


Comunicación entre componentes

En este esquema que aparece en un artículo de David Hill, refleja claramente como se comunican los componentes de MVVM.


La vista se comunica con el view model mediante binding y notificaciones para la representación de datos, la vista mediante el concepto de binding enlaza el contenido de los controles que contiene a propiedades que expone su view model, estas propiedades tienen la capacidad notificar cuando han cambiado. De esta forma la vista es capaz de actualizar su contenido automáticamente.

Las acciones que pueden realizar el view model están encapsuladas dentro de commands que pueden ser enlazados por la vista mediante un binding también. Hay controles como Button que tienen una propiedad command, cuando el botón es pulsado y esta enlazado con un comando del view model, este se ejecuta.

Para aquellos controles que no disponen de esta propiedad command, hay librerías como la nueva Behaviors SDK de Windows 8.1, que permiten usar el concepto EventToCommand. Este concepto es la capacidad de ejecutar un comando del view model cuando se produce un evento en un control.

Creando el framework

Para crear nuestro framework nos vamos a crear una librería, ¿de que tipo?, depende de las necesidades, si solemos hacer aplicaciones de todo tipo como WPF, Windows Phone etc.. puede que la mejor solución sea una librería portable, pero como digo depende de cada caso.

Vamos a ver los componentes mínimos que necesitamos añadir a nuestra librería y que también son los que vamos a encontrar en cualquier framework existente

ObservableObject

Para poder hacer que nuestros objetos sean enlazables desde la vista de forma que cuando el valor de alguna propiedad cambie, la vista se actualice automáticamente, vamos a necesitar hacer nuestros objetos observables.

Para  conseguirlo, en nuestra librería vamos a crear  una clase que llamaremos ObservableObject, los objetos que queramos hacer observables en nuestras aplicaciones heredarán de esta clase. ObservableObject implementa el interface INotifyPropertyChanged, esta interfaz contiene un evento PropertyChanged.

public class ObservableObject:INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string caller)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(caller));
        }
    }

    protected bool SetField<T>(ref T field, T value, [CallerMemberName]string caller = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;

        field = value;
        OnPropertyChanged(caller);
        return true;
    }
}

En nuestra aplicación cuando queramos un objeto observable, tendremos que heredar de ObservableObject y en las propiedades de las que queramos notificar cambios a la vista, utilizaremos la función SetField de la clase base, donde indicamos por referencia el campo a cambiar y el valor. No es necesario indicar la propiedad que estamos cambiando porque con el atributo CallerMemberName vamos a ser capaces de averiguarlo.
public class MainPageVM:ObservableObject
{
    private int name;
    public int Name
    {
        get { return name; }
        set 
        { 
            SetField(ref name, value);
        }
    }
}

RelayCommand

Para poder invocar métodos de nuestros view models, estos deben deben estar encapsulados en Commands y deben estar expuestos mediante propiedades que se enlazaran desde la vista. En lugar de crearnos un objeto nuevo específico para cada propiedad command que tengamos en un view model, lo habitual que existe en los frameworks MVVM y también por ejemplo en las plantillas de proyectos Windows 8.1 y  Windows phone 8.1 es tener un objeto RelayCommand o también conocido como DelegateCommand , de esta forma reutilizamos bastante código y queda todo mucho más limpio.

Es un objeto que implementa el interface ICommand, tiene dos funciones, Execute que invoca el método que encapsula el Action recibido por parámetro, CanExecute que indica si el comando puede ser invocado, ejecuta el Func<> que puede ser recibido por parámetro, CanExecuteChanged es un evento que tendremos que disparar manualmente invocando RaiseCanExecuteChanged cuando queramos que se vuelva a evaluar si un comando se puede ejecutar.

public class RelayCommand : ICommand
{
    private readonly Action _execute;
    private readonly Func<bool> _canExecute;

    public event EventHandler CanExecuteChanged;

    public RelayCommand(Action execute)
        : this(execute, null)
    {
    }

    public RelayCommand(Action execute, Func<bool> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute();
    }

    public void Execute(object parameter)
    {
        _execute();
    }

    public void RaiseCanExecuteChanged()
    {
        var handler = CanExecuteChanged;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

En nuestro view model nos definiremos una propiedad para el comando de esta forma
public class MainPageVM : ObservableObject
{

    public MainPageVM()
    {
        CalculateCommand = new RelayCommand(() => CalculateResult(), () => op1 > 0 && op2 > 0);
    }

    public ICommand CalculateCommand { get;private set; }


    private int op1;
    public int Op1
    {
        get { return op1; }
        set
        {
            SetField(ref op1, value);
            (CalculateCommand as RelayCommand).RaiseCanExecuteChanged();
        }
    }

    private int op2;
    public int Op2
    {
        get { return op2; }
        set
        {
            SetField(ref op2, value);
            (CalculateCommand as RelayCommand).RaiseCanExecuteChanged();
        }
    }

    private void CalculateResult()
    {
        Result = op1 + op2;
    }

    private int result;
    public int Result
    {
        get { return result; }
        set { SetField(ref result, value); }
    }
}

Como veis tengo de forma manual que lanzar el evento notificando a la interfaz de usuario que ha cambiado la propiedad CanExecute del comando, para que se vuelva a evaluar. Esto en WPF se puede evitar utilizando la clase CommandManager, como explica Josh Smith en este artículo.

Si nuestro framework esta desarrollado sobre una librería portable porque nuestra intención es utilizarla en diferentes entornos como WPF, Windows Phone 8.1..., podemos utilizar las variables de compilación por ejemplo para utilizar CommandManager para el evento CanExecuteChanged donde se puede y dejar la declaración normal donde no podemos utilizar esta carácteristica.
#if SILVERLIGHT
        public event EventHandler CanExecuteChanged;
#elif NETFX_CORE
        public event EventHandler CanExecuteChanged;
#else
        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (_canExecute != null)
                {
                    CommandManager.RequerySuggested += value;
                }
            }

            remove
            {
                if (_canExecute != null)
                {
                    CommandManager.RequerySuggested -= value;
                }
            }
        }
#endif

NETFX_CORE es para Windows 8.1 y para Windows Phone 8.1.

Código Fuente

El código fuente es un ejemplo de una librería y una app de Windows Phone 8.1. En la librería también he incluido la versión genérica de RelayCommand que aquí no lo he mencionado para que no quedara muy extenso el artículo.

Resumen

Hemos visto que crearse alguna vez un framework propio para MVVM es una buena práctica para sentar unas buenas bases con el patrón.

Hemos visto en que se basa la comunicación entre los componentes y las clases que necesitaríamos como mínimo para poder empezar a funcionar.

Libros Relacionados

MVVM Unleashed
Advanced MVVM

3 comentarios:

  1. Gracias por ese articulo, y para enlazar eventos a los controles en runtime? tienes algun articulo. wilmer barrios

    yo tengo la ejecucion por codigo de c# y lo compilo con roslyn , pero es muy lenta la primera compilacion.

    ResponderEliminar
  2. mi correo es wilmer1104@yahoo.com

    ResponderEliminar
  3. Muchas gracias por el comentario. Lo siento pero no tengo ningún artículo más sobre lo que comentas. Gracias

    ResponderEliminar