Interacción con el usuario MVVM – Play Silverlight

En esta entrada quiero desarrollar un tema que dejé pendiente en la anterior entrada y que considero importante, como es la interacción con el usuario para  aspectos como notificar ciertos eventos o solicitar confirmación antes de ejecutar una operación. La forma habitual de hacerlo es través de la clase MessageBox, sin embargo, su uso rompe con el patrón MVVM, ya que haríamos referencia a componentes visuales desde el modelo de la vista.

Existen dos enfoques para tratar los aspectos relacionados con la interacción con el usuario. Por un lado, tenemos el uso de un servicio de interacción del que podemos hacer uso desde el modelo de la vista y en el que la implementación de dicho servicio es independiente de la de la vista. Por otra parte, tenemos un enfoque en el que se utilizan objetos de interacción en el modelo de la vista, una serie de componentes de la vista y, triggers y comportamientos para unir ambos extremos. Este último es el enfoque utilizado en Prism y del que incluiré un pequeño proyecto de prueba en el que se utilizarán todos las posibilidades que ofrece. El resultado de lo visto en esta entrada dará como resultado lo siguiente:

Servicio de interacción

Este enfoque utiliza un servicio que interactúa con el usuario a través del uso de la clase MessageBox. Antes decía que el uso de MessageBox rompía con el patrón MVVM, pero el hecho de encapsular los componentes visuales en un servicio aparte, permite mantener una separación de responsabilidades.

Sin embargo, con esto no conseguiríamos desacoplar totalmente el modelo de la vista del servicio, sino que necesitamos que el primero haga uso del segundo sin que se requiera conocer su implementación. Para ello, tenemos dos opciones: utilizar un contenedor de inyección de dependencias (como Unity, Spring.Net o Castle Windsor) o implementar el patrón ServiceLocator. Ambas opciones nos permiten obtener una referencia a un objeto que implementa cierta interfaz (en este caso podría ser una interfaz con una única función  Show con n parámetros de entrada y que devuelva el resultado de la interacción) y hacer uso de sus funciones.

Así conseguimos que sea el servicio el que implemente los aspectos visuales de la interacción y permitiendo reutilizar código, ya que al utilizar referencias a interfaces podemos hacer diferentes implementaciones del servicio según las necesidades.

Podemos encontrar un ejemplo de implementación de un servicio de interacción realizado por Josh Smith aquí.

Prism y la interacción con el usuario

En el caso de Prism, se utiliza un enfoque diferente, ya que se utiliza un objeto de interacción que está conectado a la vista a través de un comportamiento. Estos son los 3 tipos de elementos proporcionados por Prism:

  • A nivel de vista, Prism incluye las clases NotificationChildWindow, que muestra un mensaje con un único botón para aceptar, y ConfirmationChildWindow, con el que podemos pedir confirmación al usuario para ejecutar una operación. También se permite el uso de vistas personalizadas, como veremos más adelante.
  • A nivel de modelo de la vista, encontramos la clase InteractionRequest<T>, que encapsula los aspectos no visuales de la interacción, y mediante la cual iniciaremos la interacción con el usuario y recogeremos los resultados a través de una función de callback.
  • Para conectar vista y modelo de la vista, disponemos de dos clases InteractionRequestTrigger y PopupChildWindow. La primera se asociará al objeto InteractionRequest del modelo de la vista y lanzará el comportamiento de tipo PopupChildWindow que se encarga de mostrar la ventana.

De esta manera, conseguimos una completa separación entre la vista, que agrupa todos los componentes visuales necesarios para llevar a cabo la interacción, y el modelo de la vista,  desde la que se lanzará la interacción y donde se gestionará la respuesta.

A continuación, explicaré los diferentes tipos de interacción que podemos llevar a cabo con la infraestructura proporcionada por Prism.

Notificación

El tipo más básico, es la notificación, con la que simplemente se muestra un mensaje al usuario con un único botón “Aceptar”.

En este tipo de interacción, en el modelo de la vista encontramos una instancia de InteractionRequest de tipo Notification., que viene incluida en Prism, y que tiene como propiedades Title (título de la ventana) y Content (texto del mensaje.) Después encontramos el método Notify en el que se ve cómo se inicia la interacción a través del método Raise, que tendrá como argumentos un objeto de tipo Notification y opcionalmente una función de callback que se ejecutará al terminar la interacción.

// Objeto de tipo InteractionRequest con contexto de tipo Notification
public InteractionRequest<Notification> Notification { get; set; }

/// <summary>
/// Inicio de la interacción
/// </summary>
private void Notify()
{
   Notification.Raise(new Notification { Title = "Notification", Content = "This is a notification" }, Notified);
}

/// <summary>
/// Función de callback ejecutada al pulsar OK
/// </summary>
/// <param name="notification">Contexto</param>
private void Notified(Notification notification)
{
   Actions.Add("Notifified");
}

Por su parte, en el xaml, encontramos los elementos que he comentado anteriormente, comenzando con el trigger (InteractionRequestTrigger) cuya propiedad SourceObject asociaremos al objeto InteractionRequest del modelo de la vista. Al ejecutar el método Raise de este último, se lanzará el trigger que se encargará de mostrar la ventana. La visualización de la ventana se realiza a través de un comportamiento PopupChildWindowAction en cuya propiedad ChildWindow pondremos la vista a mostrar (en este caso se utiliza la ventana de notificación que viene incluida en Prism).

<prism:InteractionRequestTrigger SourceObject="{Binding Notification}">
    <prism:PopupChildWindowAction>
        <prism:PopupChildWindowAction.ChildWindow>
             <prism:NotificationChildWindow/>
        </prism:PopupChildWindowAction.ChildWindow>
     </prism:PopupChildWindowAction>
</prism:InteractionRequestTrigger>

Confirmación

El segundo tipo es la confirmación, que mostrará un mensaje al usuario y permititrá al usuario elegir entre dos opciones, aceptar o cancelar.

En el modelo de la vista las diferencias con la notificación son, que el tipo de la clase InteractionRequest es Confirmation, y que, en este caso, en la función de callback el parámetro de entrada sí aporta información al incluir en su propiedad Confirmed la elección del usuario.


// Objeto de tipo InteractionRequest con contexto de tipo Confirmation
 public InteractionRequest<Confirmation> Confirmation { get; set; }

 /// <summary>
 /// Inicio de la interacción
 /// </summary>
 private void Confirm()
 {
    Confirmation.Raise(new Confirmation { Title = "Confirmation", Content = "¿Are you sure?" }, OnSelected);
 }

 /// <summary>
 /// Función de callback ejecutada al pulsar OK o Cancel
 /// </summary>
 /// <param name="confirmation">Contexto</param>
 private void OnSelected(Confirmation confirmation)
 {
    if (confirmation.Confirmed)
    {
       Actions.Add("Confirmed");
    }
    else
    {
       Actions.Add("Not confirmed");
    }
 }

A nivel de xaml, la única diferencia con la notificación es el uso de una instancia de la clase ConfirmationChildWindow para asociar a la propiedad ChildWindow del comportamiento.


<prism:InteractionRequestTrigger SourceObject="{Binding Confirmation}">
   <prism:PopupChildWindowAction>
      <prism:PopupChildWindowAction.ChildWindow>
         <prism:ConfirmationChildWindow/>
      </prism:PopupChildWindowAction.ChildWindow>
   </prism:PopupChildWindowAction>
</prism:InteractionRequestTrigger>

Controles personalizados

En los dos ejemplos mencionados se utilizan vistas incluidas en Prism, sin embargo, tenemos la posibilidad de crear controles personalizados con su propio contexto de datos.

En el modelo de la vista se pueden ver las diferencias más notables con respecto a lo visto anteriormente. Por un lado, la definición del objeto de interacción será del tipo InfoRequestViewModel, que corresponderá con el modelo de la vista asociado a la vista a mostrar. Por otro, en la función de callback, encontramos que como parámetro recibimos un objeto del tipo del modelo de la vista (en él  podemos  haber definido propiedades de salida que serán completadas por el usuario durante la interacción), y que recogeremos en el modelo de la vista padre para gestionarlas como queramos.


 // Objeto de tipo InteractionRequest con contexto de tipo Confirmation
 public InteractionRequest<InfoRequestViewModel> InfoRequest { get; set; }

 /// <summary>
 /// Inicio de la interacción
 /// </summary>
 private void Modal()
 {
    InfoRequest.Raise(new InfoRequestViewModel
    {
       AvailableItems = new ObservableCollection<string>{ "Perro", "Gato" }
    },
    OnInfoObtained
    );
 }

 /// <summary>
 /// Función de callback ejecutada al pulsar OK o Cancel
 /// </summary>
 /// <param name="result">Modelo de a vista de la ventana hija</param>
 private void OnInfoObtained(InfoRequestViewModel result)
 {
    if (result.Result.HasValue && result.Result.Value)
    {
       if (result.SelectedItem != null)
       {
          Actions.Add(result.SelectedItem + " selected");
       }
       else
      {
          Actions.Add("No item selected");
      }
    }
    else
    {
       Actions.Add("Dialog Cancelled");
    }
}

En el xaml, la única diferencia es que la vista que mostrará el comportamiento PopupChildWindow, será una clase personalizada que hereda de ChildWindow.

<prism:InteractionRequestTrigger SourceObject="{Binding InfoRequest}">
   <prism:PopupChildWindowAction>
      <prism:PopupChildWindowAction.ChildWindow>
         <my:SelectItemView />
      </prism:PopupChildWindowAction.ChildWindow>
   </prism:PopupChildWindowAction>
</prism:InteractionRequestTrigger>

Hasta aquí este entrada sobre interacción con el usuario. Os dejo un proyecto que pone en práctica todo lo visto en esta entrada (Eliminar extensión .doc y descomprimir) UserInteractionRequest.rar

Anuncios
Esta entrada fue publicada en Patrones, Prism, Silverlight y etiquetada , , , , . Guarda el enlace permanente.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s