[Windows Phone 8.1] El Action Center

Introducción

Los Live Tiles disponibles en la plataforma Windows son una excelente
y elegante forma de notificar al usuario con información relevante para
el mismo. Sin embargo, no todas las aplicaciones están ancladas al
inicio y a veces el usuario puede perder información. Para solventar
esto llega el Action Center, un lugar donde ver todas
las notificaciones de todas las aplicaciones incluso de las no ancladas
al incio además de poder acceder a configuración básica del sistema como
el modo vuelo, WiFi o Bluetooth.

Con el SDK de Windows Phone 8.1 tenemos a nuestra disposición varias
APIs que permiten administrar las notificaciones del centro de
actividades (Action Center). En este artículo vamos a conocer las APIs
disponibles.

¿Te apuntas?

Manos a la Obra

Comenzamos creando un nuevo proyecto:

Partimos de la plantilla Blank App para centrar nuestra atención en la gestión de notificaciones. Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.

En el artículo actual vamos a centrarnos en varios puntos:

  • Enviar una notificación Toast local que mostrará un mensaje (como hsata ahora) y además quedara registrada en el Action Center.
  • Enviar un nuevo tipo de notificación Toast llamada “fantasma” que no
    muestra mensaje sino que aparece directamente en el Action Center.
  • Vamos a aprender como gestiona las colecciones de notificaciones el Action Center.
  • Vamos a gestionar notificaciones, además de añadirlas, vamos a ver como eliminarlas.

Para cubrir estos objetivos, vamos a añadir múltiples vistas, cada
una de ellas, centrada en uno de los objetivos anteriores. Añadimos:

  • ToastView: Desde donde enviar una notificación Toast.
  • GhostView : Desde donde enviar una notificación “fantasma”.
  • QueueView: Desde aqui enviaremos un grupo de notificaciones.
  • EditView: Desde aqui gestionaremos múltiples notificaciones.

Nos centramos en la vista principal de la aplicación:

<ScrollViewer>
     <StackPanel Margin="12">
            <TextBlock Text="ACTION CENTER" FontSize="32"
                       Margin="0, 0, 0, 25"/>
            <TextBlock Text="Enviaremos
una notificación toast local. EL popup de la notificación aparecerá y
automáticamente tambien se enviará al action center."
                       TextWrapping="Wrap" />
            <Button Content="Notificación Toast" Command="{Binding ToastCommand}"/>
            <TextBlock Text="Enviaremos
una notificación toast fantasma local. Usamos la propiedad SupressPopup
del Toast para que solo aparezca en el action center."
                       TextWrapping="Wrap" />
            <Button Content="Notificación Toast fantasma" Command="{Binding GhostCommand}"/>
            <TextBlock Text="Toda
aplicación tiene una cantidad finita de espacio en el action center. En
este ejemplo enviamos múltiples notificaciones para demostrar su
funcionamiento."
                       TextWrapping="Wrap" />
            <Button Content="Sistema de colas" Command="{Binding QueueCommand}"/>
            <TextBlock Text="Gracias a NotificationManager.History podemos eliminar notificaciones del action center."
                       TextWrapping="Wrap" />
            <Button Content="Gestión de notificaciones" Command="{Binding EditCommand}"/>
     </StackPanel>
</ScrollViewer>

Como podemos ver, contamos con un listado de botones que ejecutan
comandos de la viewmodel, que realizarán la navegación a sus respectivas
vistas. Vemos el código de la viewmodel:

private ICommand _ghostCommand;
private ICommand _queueCommand;
private ICommand _editCommand;
private ICommand _toastCommand;
 
public ICommand GhostCommand
{
     get { return _ghostCommand = _ghostCommand ?? new DelegateCommand(GhostCommandDelegate); }
}
 
public ICommand QueueCommand
{
     get { return _queueCommand = _queueCommand ?? new DelegateCommand(QueueCommandDelegate); }
}
 
public ICommand EditCommand
{
     get { return _editCommand = _editCommand ?? new DelegateCommand(EditCommandDelegate); }
}
 
public ICommand ToastCommand
{
     get { return _toastCommand = _toastCommand ?? new DelegateCommand(ToastCommandDelegate); }
}
 
public void GhostCommandDelegate()
{
     AppFrame.Navigate(typeof (GhostView));
}
 
public void QueueCommandDelegate()
{
     AppFrame.Navigate(typeof (QueueView));
}
 
public void EditCommandDelegate()
{
     AppFrame.Navigate(typeof (EditView));
}
 
public void ToastCommandDelegate()
{
     AppFrame.Navigate(typeof(ToastView));
}

Aún nada específico de notificaciones o el Action Center, sencillamente
la estructura base de nuestro ejemplo. El resultado de lo realizado
hsata aqui es:

Enviando notificaciones Toast

Para poder probar el centro de actividades tenemos que enviar
notificaciones. La forma más sencilla de enviar notificaciones es
utilizando notificaciones locales del sistema. Para la gestión correcta de todo vamos a crear un servicio llamado LocalNotificationService:

public class LocalNotificationService : ILocalNotificationService
{
 
}

En la definición del servicio comenzamos con un método imprescindible, verificar si podemos enviar notificaciones o no:

/// <summary>
/// Verifica si podemos enviar notificaciones o no.
/// Verifica si estan habilitadas las notificaciones toast en la App.
/// </summary>
/// <returns></returns>
bool CanSendToasts();

Implementamos la interfaz:

public bool CanSendToasts()
{
     bool canSend = true;
     var notifier = ToastNotificationManager.CreateToastNotifier();
 
     if (notifier.Setting != NotificationSetting.Enabled)
          canSend = false;
 
     return canSend;
}

Antes de continuar, vamos a analizar que hacemos en el método anterior.

Utilizamos la clase ToastNotificationManager.
Esta clase tiene como objetivo principal crear objetos de tipo
ToastNotifier usados para generar notificaciones del sistema. Además,
nos permite acceder al contenido XML de las plantillas de notificaciones
para poder acceder y modificar su contenido. En este caso, utilizamos
el método  CreateToastNotifier que crea e inicializa una instancia de ToastNotification. Verificamos basicamente si las notificaciones estan habilitadas o no.

Para que la aplicación sea capaz de gestionar notificaciones Toast
debemos activar la opción en el archivo de manifiesto del paquete.

Nuestro objetivo principal en el servicio es crear notificaciones. Ese será el objetivo del siguiente método:

/// <summary>
/// Utiliza la plantilla ToastText02. Plantilla sencilla, se puede ver el catálogo en: http://msdn.microsoft.com/en-us/library/windows/apps/hh761494.aspx
/// </summary>
/// <param name="toastHeading"></param>
/// <param name="toastBody"></param>
/// <param name="tag"></param>
/// <param name="group"></param>
/// <returns></returns>
public ToastNotification CreateToast(string toastHeading, string toastBody, string tag, string group)
{
     // Usamos la plantilla ToastText02.
     ToastTemplateType toastTemplate = ToastTemplateType.ToastText02;
 
      // Obtenemos el xml que configura el toast para poder acceder a sus propiedades.
      XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);
 
      //Buscamos el text dentro del contenido.
      XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
 
      // Establecemos el texto.
      toastTextElements[0].AppendChild(toastXml.CreateTextNode(toastHeading));
      toastTextElements[1].AppendChild(toastXml.CreateTextNode(toastBody));
 
      IXmlNode toastNode = toastXml.SelectSingleNode("/toast");
      ((XmlElement)toastNode).SetAttribute("duration", "long");
 
      // Creamos la notificación.
      ToastNotification toast = new ToastNotification(toastXml);
 
      // Si nos llega tag se lo asignamos.
      if (!string.IsNullOrEmpty(tag))
           toast.Tag = tag;
 
      // Si nos llega grupo se lo asignamos.
      if (!string.IsNullOrEmpty(group))
          toast.Group = group;
 
      return toast;
}

En el código anterior, se crea una notificación Toast local utilizando la plantilla ToastText02. Esta plantilla cuenta con un máximo de dos elementos de texto. Es una notificación simple pero bastante usada.

NOTA: Podéis acceder a el catálogo de plantillas de notificación del sistema.

Tras crear la notificación volvemos a utilizar a la clase
ToastNotificationManager para acceder al contenido XML de la
notificación y añadir el texto recibido como parámetros en la
notificación.

Además, podemos recibir etiqueta o grupo para la notificación. Veremos cuando y para que se utilizarán.

Bien, el método anterior crea notificaciones locales pero… ¿cómo las enviamos?

Vamos a crear otro método en nuestro servicio que se encargue de crear la notificación y lanzarla:

/// <summary>
/// Además de crear la notificación, la lanza y muestra.
/// </summary>
/// <param name="toastHeading"></param>
/// <param name="toastBody"></param>
/// <param name="suppressPopup"></param>        
/// <param name="tag"></param>
/// <param name="group"></param>
public void ShowToast(string toastHeading, string toastBody, string tag, string group, bool suppressPopup)
{
     ToastNotification toast = CreateToast(toastHeading, toastBody, tag, group);
 
     // SuppressPopup = true no apareceel popup, se envia directamente el action center.
     toast.SuppressPopup = suppressPopup;
 
     // Envia la notificación toast.
     ToastNotificationManager.CreateToastNotifier().Show(toast);
}

Para enviar la notificación localmente utilizamos el método ToastNotifier.Show(ToastNotification).

Hasta aquí nuestro servicio es lo suficientemente potente como para enviar notificaciones locales. Asi pues… !¿a que esperamos?!

Nos centramos en el primero de nuestros puntos, enviar una notificación local normal. Recordad que creamos una vista llamada ToastView que vinculamos (usamos Ioc, con un ViewModelLocator) a su viewmodel:

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

Nos centramos en la vista de ToastView. Basicamente añadimos un botón que nos permita enviar la notificación:

<Grid>
     <StackPanel Margin="12">
          <TextBlock Text="DEMO:" FontSize="32" />
          <TextBlock TextWrapping="Wrap">
                Enviaremos
una notificación toast local. EL popup de la notificación aparecerá y
automáticamente tambien se enviará al action center.
          </TextBlock>
          <Button Content="Enviar Notificación" Command="{Binding SendNotificationCommand}"/>
     </StackPanel>
</Grid>

El resultado es:

Nos centramos en la viewmodel de esta vista:

private ILocalNotificationService _localNotificationService;
private ICommand _sendNotificationCommand;
 
public ToastViewModel(ILocalNotificationService localNotificationService)
{
     _localNotificationService = localNotificationService;
}
 
public ICommand SendNotificationCommand
{
     get { return _sendNotificationCommand = _sendNotificationCommand ?? new DelegateCommand(SendNotificationCommandDelegate); }
}
 
public void SendNotificationCommandDelegate()
{
     if (_localNotificationService.CanSendToasts())
          _localNotificationService.ShowToast("Toast Simple", "EL popup de la notificación aparecerá", string.Empty, string.Empty, false);
}

El botón lanza un comando que utiliza el servicio de notificaciones
locales llamando al método ShowToast que analizamos previamente. Creará
una notificación Toast local y la mostrará:

Aparecerá un mensaje como el de la captura anterior. Además:

Automáticamente se mostrará también en el centro de actividades.

Notificaciones fantasma

Aunque mostrar un mensaje emergente con las notificaciones es una
funcionalidad fantástica además de una manera ideal de atraer la
atención del usuario sobre algo importante que esta ocurriendo en
nuestra aplicación. De hecho, es una forma tan efectiva de llamar la
atención del usuario que si mostramos demasiados mensajes lograremos el
objetivo contrario al buscado, resultaremos pesados.

¿Y si en lugar de mostrar un mensaje emergente con cada notificación, las registramos directamente en el centro de actividades?

Pues ahora es algo posible. Veamos como sería. Comenzamos creando la interfaz de la vista GhostView:

<Grid>
     <StackPanel Margin="12">
          <TextBlock Text="DEMO:" FontSize="32" />
          <TextBlock TextWrapping="Wrap">
                Enviaremos una notificación toast "fantasma" local. Usamos la propiedad SupressPopup del Toast para que solo aparezca en el action center.
          </TextBlock>
          <Button Content="Enviar Notificación Fantasma" Command="{Binding SendGhostNotificationCommand}"/>
      </StackPanel>
</Grid>

El resultado:

Nos centramos en la viewmodel de la vista:

private ILocalNotificationService _localNotificationService;
private ICommand _sendGhostNotificationCommand;
 
public GhostViewModel(ILocalNotificationService localNotificationService)
{
     _localNotificationService = localNotificationService;
}
 
public ICommand SendGhostNotificationCommand
{
     get
{ return _sendGhostNotificationCommand = _sendGhostNotificationCommand
?? new DelegateCommand(SendGhostNotificationCommandDelegate); }
}
 
public void SendGhostNotificationCommandDelegate()
{
     if (_localNotificationService.CanSendToasts())
          _localNotificationService.ShowToast("Toast Simple", "EL popup de la notificación NO aparecerá", string.Empty, string.Empty, true);
}

De nuevo contamos con un comando que lanza el método ShowToast
de nuestro servicio de notificaciones locales. Sin embargo, en esta
ocasión el método cuenta con un parámetro distinto al caso anterior. El
último parámetro es un booleano que áfecta a ka propiedad SuppressPopup.
Esta propiedad indica si se suprime el mensaje emergente de la
notificación pero manteniendo al usuario informado mediante el centro de
actividades:

La cola de notificaciones del Action Center

El centro de actividades tiene límites. Una misma aplicación puede mantener hasta 20 notificaciones como límite. Cada aplicación gestiona su cola de notificaciones en el centro de actividades siguiente el principio FIFO.

¿Qué quiere decir esto?

Cuando se envia una nueva notificación de la aplicación al centro de
actividades y ya se ha llegado al límite, se elimina automáticamente la
notificación más antigua.

NOTA: En caso de tener más de 20 notificaciones
se muestra un mensaje “Más notificaciones” para avisar al usuario. Al
pulsarlo se abre la aplicación.

Creamos la interfaz que nos permita enviar múltiples notificaciones:

<Grid>
     <StackPanel Margin="12">
          <TextBlock Text="DEMO:" FontSize="32" />
          <TextBlock TextWrapping="Wrap">
                Toda
aplicación tiene una cantidad finita de espacio en el action center. En
este ejemplo enviamos múltiples notificaciones para demostrar su
funcionamiento.
          </TextBlock>
          <Button Content="Enviar múltiples notificaciones" Command="{Binding SendNotificationsCommand}"/>
     </StackPanel>
</Grid>

El resultado es:

Nos centramos en la viewmodel de la vista:

El pulsar el botón ejecutamos un comando de la viewmodel en la que en bucle lanzamos el evento ShowToast de nuestro servicio 25 veces (declarado en constante).

private const int toastsToSend = 25;
 
private ILocalNotificationService _localNotificationService;
private ICommand _sendNotificationsCommand;
 
public QueueViewModel(ILocalNotificationService localNotificationService)
{
     _localNotificationService = localNotificationService;
}
 
public ICommand SendNotificationsCommand
{
     get { return _sendNotificationsCommand = _sendNotificationsCommand ?? new DelegateCommand(SendNotificationsCommandDelegate); }
}
 
public void SendNotificationsCommandDelegate()
{
     if (_localNotificationService.CanSendToasts())
     {
          // Enviamos múltiples notificaciones
          for (int i = 0; i < toastsToSend; i++)
          {
               _localNotificationService.ShowToast(string.Format("Toast {0}", i + 1), "Contenido", string.Empty, string.Empty, true);
          }
     }
}

El resultado es el siguiente:

Como ya mencionamos previamente, cada aplicación cuenta con una cola
de hasta 20 notificaciones mostrando la opción “más notificaciones” en
caso de existir más.

Eliminar notificaciones del Action Center

Hasta ahora hemos visto como se envían notificaciones Toast y notificaciones Toast fantasmas
y su registro en el centro de actividades asi como la gestión de colas
que lleva a cabo el mismo. A continuación, continuaremos viendo como
realizar gestión de notificaciones en el centro de actividades.

Creamos la interfaz de esta última parte del ejemplo:

<Grid>
     <StackPanel Margin="12">
          <TextBlock Text="DEMO:" FontSize="32" />
          <TextBlock TextWrapping="Wrap">
                Gracias a NotificationManager.History podemos eliminar notificaciones del action center.
          </TextBlock>
          <Button Content="Enviar notificaciones" Command="{Binding SendNotificationsCommand}" />
          <Button Content="Eliminar por Tag" Command="{Binding RemoveTagCommand}" />
          <Button Content="Eliminar por grupo" Command="{Binding RemoveGroupCommand}" />
          <Button Content="Eliminar todas las notificaciones" Command="{Binding RemoveAllCommand}" />
      </StackPanel>
</Grid>

Sencilla, cuatro botones que nos permiten enviar notificaciones (con
etiquetas y grupos que veremos a continuación) y eliminarlas:

Nos centramos en el primer botón. Al pulsarlo ejecutará un comando de la viewmodel:

private ICommand _sendNotificationsCommand;
 
public ICommand SendNotificationsCommand
{
     get { return _sendNotificationsCommand = _sendNotificationsCommand ?? new DelegateCommand(SendNotificationsCommandDelegate); }
}
 
public void SendNotificationsCommandDelegate()
{
     _localNotificationService.ShowToast("Toast Tag", "Contenido", "Tag", string.Empty, false);
 
     _localNotificationService.ShowToast("Toast Group", "Contenido", string.Empty, "Group", false);
 
     _localNotificationService.ShowToast("Toast Tag&Group", "Contenido", "Tag", "Group", false);
}

El comando lanzará tres notificaciones:

  • La primera de ellas con etiqueta llamada “Tag”.
  • La segunda lanzará una notificación con grupo llamado “Group”.
  • La última será una notificación con etiqueta y grupo, “Tag” y “Group” respectivamente.

ToastNotification cuenta con las propiedades Tag y Group que permiten administrar facilmente las notificaciones:

Las propiedades Tag y Group de la notificación nos
permiten quitar notificaciones de forma específica o grupos de
notificaciones. Para ello, vamos a añadir nuevos métodos en nuestro
servicio de notificaciones locales:

/// <summary>
/// Elimina notificaciones toast por tag o por grupo.
/// </summary>
/// <param name="tag"></param>
/// <param name="group"></param>
public void RemoveToast(string tag, string group)
{
     if (!string.IsNullOrEmpty(group) && string.IsNullOrEmpty(tag))
          ToastNotificationManager.History.RemoveGroup(group);
     else if (string.IsNullOrEmpty(group) && !string.IsNullOrEmpty(tag))
          ToastNotificationManager.History.Remove(tag);
     else
          ToastNotificationManager.History.Remove(tag, group);
}
 
/// <summary>
/// Elimina todas las notificaciones toast.
/// </summary>
public void RemoveAllToasts()
{
     ToastNotificationManager.History.Clear();
}

Usamos los métodos Remove de ToastNotificationHistory para quitar notificaciones específicas o grupos de notificaciones.

Creamos un método llamado RemoveToast que nos permite eliminar notificaciones por etiqueta, por grupo o por ambas propiedades. Utilizamos el método ToastNotificationHistory.Remove(string)  para eliminar una notificación por etiqueta. Para eliminar una notificación por grupo utilizamos el método ToastNotificationHistory.RemoveGroup(string). Por último, para eliminar notificaciones por etiqueta y grupo utilizamos ToastNotificationHistory.Remove(string, string).

NOTA: No podemos crear un objeto de tipo
ToastNotificationHistory directamente pero podemos obtener una
referencia a la instancia por medio de la propiedad History de la clase ToastNotificationManager.

El segundo método creado, RemoveAllToasts, utiliza el método ToastNotificationHistory.Clear() para quitar todas las notificaciones del centro de activiades.

El segundo botón ejecutarán un comando en la viewmodel:

private ICommand _removeTagCommand;
 
public ICommand RemoveTagCommand
{
     get { return _removeTagCommand = _removeTagCommand ?? new DelegateCommand(RemoveTagCommandDelegate); }
}
 
public void RemoveTagCommandDelegate()
{
     _localNotificationService.RemoveToast("Tag", string.Empty);
}

Que utilizará el método RemoveToast pasandole como parámetro la etiqueta correspondiente. El segundo botón volverá a ejecutar un comando utilizando el método RemoveToast de nuestro servicio de notificaciones locales:

private ICommand _removeGroupCommand;
 
public ICommand RemoveGroupCommand
{
     get { return _removeGroupCommand = _removeGroupCommand ?? new DelegateCommand(RemoveGroupCommandDelegate); }
}
    
public void RemoveGroupCommandDelegate()
{
     _localNotificationService.RemoveToast(string.Empty, "Group");
}

En este caso pasándole como parámetro el grupo correspondiente. Por
último, el último botón eliminará todas las notificaciones. En este caso
utilizaremos el método RemoveAllToasts de nuestro servicio:

private ICommand _removeAllCommand;
 
public ICommand RemoveAllCommand
{
     get { return _removeAllCommand = _removeAllCommand ?? new DelegateCommand(RemoveAllCommandDelegate); }
}
 
public void RemoveAllCommandDelegate()
{
     _localNotificationService.RemoveAllToasts();
}

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

Más información

[Evento CartujaDotNet] Sevilla Mobility Day

El evento

No hay duda, los smartphones han llegado, y ya forman parte de la
vida de todos nosotros. En muchos aspectos de nuestra vida accedemos a
información, realizamos algna tarea o sencillamente nos entretenemos con
uno de ellos.

Esto también nos afecta como desarrolladores. El desarrollo móvil se
ha convertido en una prioridad en una gran mayoria de ámbitos. Por ese
motivo, el próximo evento de CartujaDotNet se centra efusivamente en
este ámbito. Tras las novedades disponibles con el último SDK de Windows Phone 8.1 tenemos una gran cantidad de puntos que tratar. Además, tambien tenemos novedades en el desarrollo multiplataforma donde Xamarin anuncio recientemente la versión 3 del mismo con suculentos cambios o en el desarrollo de videojuegos, donde engines como Wave Engine, nos ponen más fácil la tarea de crear grandes juegos multiplataforma.

Y ante tal cantidad de novedades, ¿que podemos hacer?. En
CartujaDotNet, tienen la solución, meterlo todo en una coctelera,
agitarlo y asi nace el Sevilla Mobility Day, un día con
múltiples sesiones técnicas hablando de aplicaciones universales en
Windows Phone, detalles técnicos en el desarrollo de Windows Phone como
el uso de Behaviors, animaciones y Visual States, desarrollo de
aplicaciones multiplataforma con Xamarin o como desarrollar videojuegos
multiplataforma con Wave Engine.

¿Te apuntas?

Fecha

El evento tendrá lugar el próximo Sábado, 05 de Julio de 9:30h a 14:00h. Cada charla tendrá una duración de 1 hora con descanso intermedio de 5 minutos.

Lugar

Tendrá lugar en el Cloud Pointing de Sevilla situado en el Parque Empresarial Nuevo Torneo. Tenéis la información exacta del lugar a continuación:

c Biología, 12, Edificio Vilamar 2, 3ª Planta
Parque Empresarial Nuevo Torneo
41015 Sevilla

Ponentes

El evento cuenta con una gran variedad de ponentes de lujo:

  • El evento comienza con un servidor hablando de aplicaciones universales en Windows Phone. En esta sesión aprenderemos como desarrollar aplicaciones unviersales para Windows Phone 8.1 y Windows reutilizando la mayor parte del código.
  • Continuamos con otra sesión de la mano de Josué Yeray.
    Analizará a un trio clásico de XAML que nos ayudará a crear mejores
    apps tanto para Windows Phone 8.1 como para Windows Store y Universal
    Apps. Sin duda hablamos del equipo formado por los behaviors, las animaciones y los visual states.
  • Continuamos con Juan María Lao con otra sesión muy divertida donde veremos como crear un juego multiplataforma con Wave Engine para Windows Phone, Android, iOS, Metro, Mac, Linux, Windows y Ouya.
  • Cerramos el evento con una charla muy interesante sobre desarrollo multiplataforma utilizando Xamarin de la mano de Juan Cano y Marcos Cobeñas.

Más información

[Evento WPSUG] Gestión de dispositivos Windows Phone con Windows Intune

Introducción

Volvemos a la carga en WPSUG, grupo de usuarios hispanos de Windows Phone,
con un nuevo Hangout sobre Windows Phone. En esta ocasión, trataremos
un punto vital a nivel empresarial, la gestión de dispositivos móviles
con sistemas MDM como Windows Intune. En este Hangout
vamos a analizar todas las dudas principales que pueden surgir al
gestionar dispositivos Windows Phone desde Windows Intune:

  • Que es el MDM
  • Que posibilidades ofrece Windows Intune
  • Como crear una cuenta
  • Políticas de configuración
  • Como desplegar aplicaciones

El evento

El próximo Jueves 26 de Junio, a las 19:00 (GMT+1)  tendra lugar Hangout en el que contaremos con un invitado especial, Jose ángel Fernández, Evangelista Técnico de Infraestructura en Microsoft, que nos contará todos los detalles relacionados con MDM y Windows Phone.

¿Te apuntas?

Más información

[#WPhoneIO] Material del hangout “Behaviors, Animaciones y VisualStates”

El evento

El pasado Jueves 19 de Junio tuvo lugar un nuevo Hangout #wphoneio de desarrolloweb.com en el que nos reunimos Josué Yeray, Rafa Serna  y un servidor para abordar un conjunto de conceptos sumamente útiles pero no muy extendidos en el uso habitual, Behaviors, Animaciones y Visual States.

El material

La grabación la podéis volver a ver a continuación:

En mi caso, tuve el placer de pode analizar las animaciones en  aplicaciones Universales. Vimos varios puntos:

  • Introducción al sistema de animaciones. Conociendo StoryBoards
  • Tipos de animaciones
  • Definiendo frames (funciones easing)
  • Como gestionar animaciones con behaviors

Tenéis disponible la presentación utilizada:

 

El elemento Storyboard define el comportamiento de la animación mediante sus propiedades:

  • Duration. Tiempo total que va a durar la animación.
  • BeginTime. Tiempo a esperar antes de ejecutar el primer frame. Necesario si queremos lanzar y sincronizar distintas animaciones.
  • AutoReverse: AL terminar se ejecuta la animación al revés automáticamente.
  • RepeatBehavior: Define el comportamiento de repetición. Puede
    contener un número que indica el número de veces que se repetirá la
    animación, o la palabra Forever, que indica que se repetirá continuamente hasta que explícitamente paremos.
  • SpeedRatio. Velocidad de reproducción.

Contamos con gran variedad de tipos diferentes de animación.
De cada opción disponibles además contamos con dos versiones, una simple
que nos permite sencillamente indicar un valor final y una duración y
una variante más compleja que nos permite definir una colección de
frames para la animación:

  • DoubleAnimation/ DoubleAnimationUsingKeyFrames, propiedades numéricas.
  • ColorAnimation / ColorAnimationUsingKeyFrames, propiedades basadas en colores.
  • PointAnimation / PointAnimationUsingKeyFrames, propiedades basadas en puntos.
  • ObjectAnimationUsingKeyFrames, cualquier tipo de propiedad diferentes a las anteriores.

En la primera DEMO que vimos, analizamos cada uno de los tipos de animaciones descritos previamente:

Podéis descargar el ejemplo a continuación:

Continuamos viendo los tipos disponibles en cada frame, instante en el tiempo de la animación:

  • Linear, usa una interpolación lineal entre frames para animar la propiedad.
  • Discrete, usa una interpolación discreta entre frames para animar la propiedad.
  • Easing, usa una función de easing para modificar la interpolación entre frames.
  • Spline, usa un Spline para definir la interpolación entre frames.

Podéis descargar el ejemplo a continuación:

Por último, aplicamos todo lo visto en una “aplicación real”. Nos
enfrentamos a un poblema real. Contamos con una aplicación que conecta
con un servicio REST que obtiene la clasificación de pilotos de la
Fórmula 1. Desde que realizamos la petición al servicio hasta que
mostramos el resultado pasan en ocasiones unos milisegundos. Utilizando
las herramientas de depuración y testing que tenemos a nuestro alcance,
en ocasiones nos damos cuentas de errores por nuestra parte que nos
permiten mejorar la celeridad con la que se obtienen los datos (dobles
llamadas, cálculos que podemos agilizar, excepciones, etc.). Sin
embargo, hay otras en las que no podemos mejorar más. En estos casos, el
usuario sigue esperando una aplicación robusta, rápida y fluida. Gracias al uso de behaviors y animaciones podemos trasmitir una falsa fluidez.

Podéis ver el resultado sin usar animaciones:

Si nada mas entrar en la vista, animamos el título atrayendo la atención
del usuario y a continuación con un leve efecto de fading vamos
mostrando cada resultado segun vamos obteniendolo, el resultado es:

El segundo con la pantalla en blanco que trasmitimos sin usar animaciones queda resuelto y todo con muy pocas líneas de código!

Los behaviors nos permiten desde nuestro XAML realizar acciones.

Contamos con dos trigger principales que nos permiten lanzar un beehavior segun algun tipo de acción o cambio:

  • DataTriggerBehavior. Este trigger nos permite ejecutar behaviors basándonos en datos para definir cuando comenzar la ejecución.
  • EventTriggerBehavior. Nos permite definir un evento, que al lanzarse, ejecutará nuestros behaviors.

El behavior a utilizar para gestionar animaciones es ControlStoryboardAction.

Podéis descargar el ejemplo a continuación:

Más información

[Universal Apps] Utilizando Text to Speech

Introducción

Una de las novedades más interesantes que tuvimos con el SDK de Windows Phone 8 fueron las Speech
APIs (Text to Speech, Speech to Text, Comandos de voz, etc.). Nos
permiten ofrecer en nuestras aplicaciones una mayor accesible a un mayor
número de usuarios con problemas de visión por ejemplo.

NOTA: En Windows Phone 7 teníamos la posibilidad
de de utilizar Text to Speech mediante el uso de Bing Speech API. Era
una opción mucho más limitada además que requería conexión a internet
(para conectar con los servicios de Bing).

En Windows Phone 8…

Recordemos como se utilizaba Text To Speech en Windows Phone 8.
Nuestro objetivo será añadir un TextBox donde se pueda escribir un texto
a leer junto a un botón para provocar la lectura del mismo. Tambien
añadiremos la posibilidad de elegir voz (masculina o femenina). Nos
centramos en el archivo MainPage.xaml para preparar la interfaz de
nuestra aplicación. Dentro del Grid principal de la aplicación
(ContentPanel) añadimos:

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
     <StackPanel>
          <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
               <RadioButton x:Name="rbMale" Content="Masculino" IsChecked="true"/>
               <RadioButton x:Name="rbFemale" Content="Femenino"/>
          </StackPanel>
          <TextBox HorizontalAlignment="Left" Height="450" TextWrapping="Wrap" Width="450" Text="Prueba de lectura desde Windows Phone 8!" Name="inputTextBox"/>
          <Button Content="Leer" HorizontalAlignment="Left" Width="456"/>
     </StackPanel>
</Grid>

Al pulsar el botón:

SpeechSynthesizer synth = new SpeechSynthesizer();
  
var voices = InstalledVoices.All.Where(v => v.Language == "es-ES").OrderByDescending(v => v.Gender);
  
VoiceGender gender = VoiceGender.Male;
  
if (rbMale.IsChecked == true)
     gender = VoiceGender.Male;
else
     gender = VoiceGender.Female;
  
synth.SetVoice(voices.Where(v => v.Gender == gender).FirstOrDefault());
  
await synth.SpeakTextAsync(inputTextBox.Text);

Instanciamos un objeto de tipo SpeechSynthesizer. Con LINQ seleccionamos el conjunto de sintetizadores españoles de la clase InstalledVoices.
Utilizamos los CheckBox para determinar el género elegido para la voz.
Establecemos el sintetizador elegido utilizando el método SetVoice. Por último, bastará con llamar al método SpeakTextAsync que recibirá como parámetro el texto a sintetizar en voz.

NOTA: Debemos añadir la capacidad de Reconocimiento de voz (ID_CAP_SPEECH_RECOGNITION).

Ahora

En Windows Phone 8.1 asi como en Windows 8.1 contamos con una nueva
API compartida. Para aprender a utilizarla vamos a crear un nuevo
proyecto:

Utilizaremos el patrón MVVM en nuestra aplicación. En la carpeta ViewModels añadimos una clase llamada MainViewModel donde crearemos una propiedad pública de tipo string que contendrá el texto a sintetizar en voz:

public string Message
{
     get { return "Hola!.
Este ejemplo muestra como crear un servicio para poder realizar Text to
Speech en aplicaciones universales utilizando el patrón MVVM."
; }
}

En nuestra vista, añadiremos un TextBlock que bindearemos al texto para mostrarlo en pantalla:

<Grid>
     <TextBlock Text="{Binding Message}"
                TextWrapping="WrapWholeWords"
                FontSize="32"
                Margin="15"/>
</Grid>

Por supuesto, necesitaremos un botón para que al pulsarlo realicemos el
speech del mensaje. Añadiremos una CommandBar con un AppBarButton para
ello:

<Page.BottomAppBar>
     <CommandBar>
          <AppBarButton Label="speech">
               <AppBarButton.Icon>
                    <BitmapIcon UriSource="ms-appx:///Assets/Speech.png" />
               </AppBarButton.Icon>
          </AppBarButton>
     </CommandBar>
</Page.BottomAppBar>

Al pulsar el botón debemos realizar una acción, es decir, en nuestro lenguaje ejecutaremos un comando de nuestra viewmodel.

public class MainViewModel : ViewModelBase
{
 
}

Bindeamos un comando al AppBarButton además de pasar el mensaje como parámetro del comando:

<Page.BottomAppBar>
     <CommandBar>
          <AppBarButton Label="speech" Command="{Binding SpeechCommand}" CommandParameter="{Binding Message}">
               <AppBarButton.Icon>
                    <BitmapIcon UriSource="ms-appx:///Assets/Speech.png" />
               </AppBarButton.Icon>
          </AppBarButton>
     </CommandBar>
</Page.BottomAppBar>

Definimos el comando en nuestra viewmodel:

private ICommand _speechCommand;
 
public ICommand SpeechCommand
{
     get { return _speechCommand = _speechCommand ?? new DelegateCommandAsync<string>(SpeechCommandDelegate); }
}
 
public async Task SpeechCommandDelegate(string message)
{
 
}

En el comando recibimos el mensaje a leer como parámetro. Vamos a añadir
la parte importante, la sintetización de texto en voz. Para ello, vamos
a crear un servicio que inyectaremos por inyección de dependencias en
nuestra viewmodel y la usaremos en el comando. Definimos el servicio:

/// <summary>
/// SpeechService Contract.
/// </summary>
public interface ISpeechService
{
     /// <summary>
     /// Synthetize a text into a speech and pronounces it.
     /// </summary>
     /// <param name="text"></param>
     /// <returns></returns>
     Task TextToSpeech(string text);
 
     /// <summary>
     /// Stop the current speech running.
     /// </summary>
     void StopSpeech();
}

Como podéis ver, muy sencillo, un servicio con dos métodos, uno para comenzar a sintetizar texto en voz y otro para detenerlo.

/// <summary>
/// SpeechService Implementation.
/// Allow share data with another applications.
/// </summary>
public class SpeechService : ISpeechService
{
 
}

Pasamos a la definición del servicio en si. Para realizar text to speech utilizaremos el namespace Windows.Media.SpeechSynthesis:

//Consts
const VoiceGender gender = VoiceGender.Female;
 
//Variables
static private SpeechSynthesizer _speech = null;
MediaElement _mediaElement;
 
/// <summary>
/// Provides access to the functionality of an installed a speech synthesis engine.
/// </summary>
static private SpeechSynthesizer Speech
{
     get { return _speech ?? (_speech = InitializeSpeech()); }
}
 
/// <summary>
/// Initialize the SpeechSynthesizer.
/// </summary>
/// <returns></returns>
static private SpeechSynthesizer InitializeSpeech()
{
     // Initialize a new instance of the SpeechSynthesizer.
     var speech = new SpeechSynthesizer();
     var language = CultureInfo.CurrentCulture.ToString();
     var voices = SpeechSynthesizer.AllVoices.Where(v => v.Language == language).OrderByDescending(v => v.Gender);
     speech.Voice = voices.FirstOrDefault(v => v.Gender == gender);
 
     return speech;
}

Creamos una propiedad detipo SpeechSynthesizer.
Esta clase nos da acceso a la funcionalidad de síntesis de voz. Creamos
un método InitializeSpeech que se encarga de crear una nueva instancia
de la clase SpeechSynthesizer y de configurar los parámetros básicos.
Accedemos a la colección de voces instaladas en el dispositivo gracias a
la propiedad SpeechSynthesizer.AllVoices.
Obtenemos la cultura actual del dispositivo para seleccionar la
priemera voz de dicha cultura y del género (masculino o femenino)
indicado mediante una enumeración del tipo VoiceGender.
El método de inicialización lo llamaremos desde una propiedad pública
que devuelve un objeto de tipo SpeechSynthesizer incializado.

Pasamos al método TextToSpeech, protagonista de nuestro servicio:

/// <summary>
/// Synthetize a text into a speech and pronounces it.
/// </summary>
/// <param name="message">The message to be pronounced.</param>
public async Task TextToSpeech(string message)
{
     try
     {
          if (!string.IsNullOrEmpty(message))
          {
               // Speak a string.
               var result = await Speech.SynthesizeTextToStreamAsync(message);
               _mediaElement = new MediaElement();
               _mediaElement.SetSource(result, result.ContentType);
               _mediaElement.Play();
          }
     }
     catch (Exception ex)
     {
          Debug.WriteLine("SpeechService", ex);
     }
}

Accede a la propiedad Speech que devuelve el objeto de tipo SpeechSynthesizer que utilizamos para llamar al método SynthesizeTextToStreamAsync. Método asíncrono que devuelve un stream.

¿Que hacemos con el resultado?

Pues realmente con el stream podemos hacer una gran variedad de
procesos. Lo más simple sería asignarlo como fuente a un control de tipo
MediaElement y lanzamos la reproducción con el método Play.

Continuamos con el segundo método StopSpeech:

/// <summary>
/// Stop the current speech running.
/// </summary>
public void StopSpeech()
{
     if (_speech != null && _mediaElement != null)
          _mediaElement.Stop();
}

Como podemos ver en lsa líneas anteriores es sumamente sencillo, utilizamos el método Stop del control MediaElement que reproduce el audio para detenerlo.

Para utilizar el servicio lo registramos en nuestro contenedor Ioc y lo inyectamos en la viewmodel por el punto de entrada, el contructor:

private ISpeechService _speechService;
 
public MainViewModel(ISpeechService speechService)
{
     _speechService = speechService;
}

En el comando creado previamente utilizamos el servicio:

await _speechService.TextToSpeech(message);

Y… oops!. ¿Que ocurre?

Obtenemos una expceción de acceso denegado. Tranquilos, solo nos falta recordar que debemos añadir el micrófono como una de las capabilities marcadas:

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

Recordar, cualquier duda o sugerencia será bienvenida en los comentarios de la entrada.

Más información

[Universal Apps] Navegación entre páginas, paso de parámetros y gestión de la tecla volver (2/2)

Introducción

Anteriormente vimos como como se realiza la navegación entre páginas
en Windows Phone 8.1 además de aprender como gestionar la tecla física
atrás. En el artículo actual pasaremos a aplicar y utilizar los
conceptos básicos previos tal y como lo utilizaríamos en una aplicación
real. Vamos a aprender a navegar utilizando un servicio de navegación e
incluso

MVVM en escena!

Realmente podemos mantener una implementación MVVM
muy similar a como hacíamos hasta ahora en Windows Phone 8. 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 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.

Utilizando PageBase

En la carpeta Views añadiremos nuestras páginas. En nuestro ejemplo, tendremos dos páginas:

  • Pagina1
  • Pagina2

Cada página será un objeto de tipo PageBase, tanto en XAML:

<base:PageBase
    x:Class="Ejemplo_NavegacionMVVM02.Views.Pagina1"
    xmlns:base="using:Ejemplo_NavegacionMVVM02.Views.Base"
    mc:Ignorable="d"
    Background="LightCoral">
    <Grid>
 
    </Grid>
</base:PageBase>

Como en el code-behind:

/// <summary>
/// Pagina1.
/// </summary>
public sealed partial class Pagina1 : PageBase
{
     public Pagina1()
     {
         this.InitializeComponent();
     }
}

Añadimos en la primera página (Pagina1) un botón que permita navegar a la segunda página:

<Button Content="Navegar a página 2"
        HorizontalAlignment="Center"
        />

Para permitir navegar a la segunda página, necesitamos definir un comando en la viewmodel. Creamos una clase derivada de ViewModelBase:

public class Pagina1ViewModel : ViewModelBase
{
 
}

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

public class Pagina1ViewModel : ViewModelBase
{
     public override System.Threading.Tasks.Task OnNavigatedFrom(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         return null;
     }
 
     public override System.Threading.Tasks.Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         return null;
     }
}

Y añadimos un comando que permita navegar de la página principal a la segunda página:

public class Pagina1ViewModel : ViewModelBase
{
     //Commands
     private ICommand _navigateCommand;
 
     public override System.Threading.Tasks.Task OnNavigatedFrom(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         return null;
     }
 
     public override System.Threading.Tasks.Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         return null;
     }
 
     public ICommand NavigateCommand
     {
         get { return _navigateCommand = _navigateCommand ?? new DelegateCommand(NavigateCommandDelegate); }
     }
 
     public void NavigateCommandDelegate()
     {
         this.AppFrame.Navigate(typeof(Pagina2), "Esto es un parámetro");
     }
}

Recordamos que el control Frame hospeda controles Page
y tiene un historial de navegación que se puede utilizar para ir hacia
atrás y hacia adelante por las páginas que ya visitaste. Tras obtener el
Frame correspondiente, utilizamos el método Navigate para realizar la navegación a otra página. Tenemos dos sobrescrituras del método Navigate:

  • Navigate(TypeName). Provoca que el Frame cargue el contenido especificado por el tipo pasado como parámetro.
  • Navigate(TypeName, Object).
    En este caso, además de indicar el tipo del contenido a cargar (primer
    parámetro), podemos pasar un parámetro a la página que se navega(segundo
    parámetro).

En nuestro ejemplo hemos utilizado la segunda sobrescritura del método pasando un parámetro.

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.

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<Pagina1ViewModel>();
         _container.RegisterType<Pagina2ViewModel>();
     }
 
     public Pagina1ViewModel Pagina1ViewModel
     {
         get { return _container.Resolve<Pagina1ViewModel>(); }
     }
 
     public Pagina2ViewModel Pagina2ViewModel
     {
         get { return _container.Resolve<Pagina2ViewModel>(); }
     }
}

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 que también tenemos en el proyecto Shared:

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

Asignamos la viewmodel como DataContext de nuestra viewmodel:

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

Y todo preparado!. Nuestra segunda página sera similar a la primera (tipo PageBase):

<base:PageBase
    x:Class="Ejemplo_NavegacionMVVM02.Views.Pagina2"
    xmlns:base="using:Ejemplo_NavegacionMVVM02.Views.Base"
    mc:Ignorable="d"
    Background="LightSeaGreen"
    DataContext="{Binding Pagina2ViewModel, Source={StaticResource Locator}}">
    <Grid>
        <Button Content="Volver"
                HorizontalAlignment="Center"
                Command="{Binding NavigateBackCommand}"/>
    </Grid>
</base:PageBase>

Contará con su propia viewmodel:

public class Pagina2ViewModel : ViewModelBase
{
     //Commands
     private ICommand _navigateBackCommand;
 
     public override System.Threading.Tasks.Task OnNavigatedFrom(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         return null;
     }
 
     public override System.Threading.Tasks.Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
     {
         if (args.Parameter != null)
             Debug.WriteLine(args.Parameter);
 
         return null;
     }
 
     public ICommand NavigateBackCommand
     {
         get { return _navigateBackCommand = _navigateBackCommand ?? new DelegateCommand(NavigateBackCommandDelegate); }
     }
 
     public void NavigateBackCommandDelegate()
     {
            this.AppFrame.GoBack();
     }
}

Utilizamos el método GoBack del Frame que navega al elemento más inmediato del historial de navegación, la página anterior.

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

Recordar qur cualquier duda o comentario lo podéis dejar en los comentarios.

Más información

[Cross Platform] Introducción a Xamarin.Forms (1/2)

Introducción

No hay duda, los smartphones han llegado, y ya forman parte de la
vida de todos nosotros. En muchos aspectos de nuestra vida accedemos a
información, realizamos algna tarea o sencillamente nos entretenemos con
uno de ellos.

Esto también nos afecta como desarrolladores. El desarrollo móvil se
ha convertido en una prioridad en una gran mayoria de ámbitos.

Actualmente contamos con varias plataformas dominantes:

  • iOS
  • Android
  • Windows Phone

Esto a nivel de desarrollo nos supone barreras. Contamos con
plataformas de desarrollo diferentes, lenguajes diferentes, etc.
suponiendo un nivel de aprendizaje y esfuerzo cada vez mayor de cara a
desarrolladores. Además, la experiencia nos ha demostrado que los
usuarios no aceptan aplicaciones no nativas. Los usuarios buscan aplicaciones rápidas, fluidas y con
consumos moderados, perfectamente adaptadas a la plataforma ofreciendo
una experiencia completa.

¿Qué podemos hacer para mejorar este panorama?

Xamarin.Forms

Con la reciente actualización a la versión 3 de Xamarin nos llega Xamarin.Forms. Es un toolkit para crear una abstracción sobre la interfaz de usuario de Android, iOS y Windows Phone permitiendo desarrollarla una única vez con código C# o Extensible Application Markup Language (XAML).

Xamarin.Forms nos permite crear aplicaciones para:

  • Android 4.0 o superior
  • iOS 6.1 o superior
  • Windows Phone 8.0

NOTA: Fijáos que hablamos de Windows Phone 8.0
con Silverlight. Incluso necesitamos usar el Toolkit de Windows Phone
para poder tener acceso a ciertos controles como por ejemplo el
DatePicker o a animaciones.

Nuestra primera aplicación Xamarin.Forms

Una vez instalada la versión 3 de Xamarin tendremos acceso a las nuevas soluciones Xamarin.Forms:

Contamos con las siguientes opciones:

  • Aplicación con Librería Portable: Crea una solución con un proyecto para cada plataforma junto a una librería portable donde añadir el código compartido.
  • Aplicación con proyecto Shared: Crea una solución
    con un proyecto para cada paltaforma junto a un cuarto proyecto Shared.
    Muy similar al concepto de aplicación universal en Windows Phone 8.1.
  • Librería portable: Crea una librería portable.

Creamos una aplicación con proyecto Shared.Contamos con la siguiente estructura de solución:

Por defecto se nos crea una aplicación de ejemplo muy básica con una única vista y un saludo en medio.

Analicemos el código fuente de la plantilla antes de continuar. La aplicación generada cuenta con una única página de tipo Xamarin.Forms.Page
que representa un Activity en Android, un View Controller en iOS y una
Page en Windows Phone. El tipo de página utilizada (posteriormente
veremos que contamos en Xamarin.Forms con múltiples tipos de páginas) es
Xamarin.Forms.ContentPage que cuenta con un Label como contenido.

En el proyecto compartido contamos con una clase llamada App. Esta
clase es la responsable de indicar cual sera la primera página a mostrar
en todas las plataformas.

public class App
{
    public static Page GetMainPage()
    {
        return new ContentPage
        {
            Content = new Label
            {
                Text = "Hello, Forms !",
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand,
            },
        };
    }
}

En el caso por defecto, se crea con C# una instancia nueva de una
página de contenido y se le añade un Label centrado en pantalla.
Sencillo, ¿verdad?.

Una vez creada la página, en cada plataforma se debe:

  • Inicializar Xamarin.Forms
  • Proveer la página de contenido

Windows Phone

Debemos incializar Xamarin.Forms que lo haremos utilizando Forms.Init() además de establecer como contenido de la página nuestra Xamarin.Forms.ContentPage creada en App:

public partial class MainPage : PhoneApplicationPage
{
    public MainPage()
    {
        InitializeComponent();
 
        Forms.Init();
        Content = HelloXamarinFormsWorld.App.GetMainPage().ConvertPageToUIElement(this);
    }
}

Android

Con el MainLauncher creamos un Activity como en una aplicación normal Android, exceptuando que nuestro Activity hereda de Xamarin.Forms.Platform.Android.AndroidActivity. En el método OnCreate inicializamos Xamarin.Forms y establecemos nuestra página.

namespace HelloXamarinFormsWorld.Android
{
    [Activity(Label = "HelloXamarinFormsWorld", MainLauncher = true)]
    public class MainActivity : AndroidActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
 
            Xamarin.Forms.Forms.Init(this, bundle);
 
            SetPage(App.GetMainPage());
        }
    }
}

iOS

Por último,  en iOS, la clase AppDelegate inicializará Xamarin.Forms y establecerá el RootViewController a nuestra ContentPage de la clase App.

[Register("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
    UIWindow window;
 
    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
        Forms.Init();
 
        window = new UIWindow(UIScreen.MainScreen.Bounds);
 
        window.RootViewController =  App.GetMainPage().CreateViewController();
 
        window.MakeKeyAndVisible();
 
        return true;
    }
}

Creando una App con C#

Hasta ahora hemos visto que Xamarin.Forms nos permite crear la
interfaz dde usuario de Windows Phone, Android e iOS de manera
compartida usando C# o XAML y hemos analizado el proyecto base creado
con las plantillas.

Continuamos profundizando cada vez más en Xamarin.Foms, vamos a crear
una primera aplicación sencilla utilizando el patrón MVVM y diseñando
la interfaz de usuario con C#.

Vamos a crear una aplicación que cuente con un botón, que al ser
pulsado ejecute  un comando en la viewmodel y actualice un texto con el
número de veces que el botón ha sido pulsado. Simple, pero suficiente
para ver muchos conceptos básicos.

Creamos un nuevo proyecto con la plantilla Blank App (Xamarin.Forms Shared). En el proyecto Shared creamos las carpetas:

  • Views
  • ViewModels

Dentro de la carpeta ViewModels, añadimos una carpeta Base donde vamos a añadir algunas clases base importantes para cubrir todas nuestras necesidades. Comenzamos añadiendo un ViewModelBase que se encargará de:

  • Notificar cambios
  • Notificar el estado
public abstract class ViewModelBase : INotifyPropertyChanged
{
     private Frame _appFrame;
     private bool _isBusy;
 
     public Frame AppFrame
     {
            get { return _appFrame; }
     }
 
     public bool IsBusy
     {
         get { return _isBusy; }
         set
         {
             _isBusy = value;
             RaisePropertyChanged();
         }
     }
 
     public event PropertyChangedEventHandler PropertyChanged;
 
     public void RaisePropertyChanged([CallerMemberName]string propertyName = "")
     {
         var Handler = PropertyChanged;
         if (Handler != null)
             Handler(this, new PropertyChangedEventArgs(propertyName));
     }
 
     internal void SetAppFrame(Frame viewFrame)
     {
         _appFrame = viewFrame;
     }
}

De esta clase heredarán todos nuestros viewmodels. Además, añadiremos otra clase importante, DelegateCommand.

Añadimos una nueva clase, llamada MainViewModel en nuestra carpeta ViewModels. Por supuesto, añadimos también otra clase llamada MainView que será la vista principal de nuestra aplicación, en la carpeta Views.

Comenzamos creando la vista de nuestra aplicación en C# (MainView).

public class MainView : ContentPage
{
 
}

Como podemos ver, nuestra clase hereda de ContentPage. En Xamarin.Forms contamos con distintos tipos de páginas:

  • ContentPage: Página de contenido que cuenta con una única vista donde añadir contenido.
  • MasterDetailPage: Página que gestiona dos paneles de información (maestro-detalle).
  • NavigationPage: Una página que gestiona una pila de otras páginas, la navegación y la experiencia de usuario entre ellas.
  • TabbedPage: Una página que permite el accesoa las subpáginas mediante tabs.
  • CarouselPage: Una página que permite acceder a las subpáginas haciendo un gesto de swipe (scroll lateral).

En nuestro ejemplo, elegimos la página más simple, ContentPage. A
continuación empezamos a añadir el contenido de nuestra página:

//Contenedor principal
var stack = new StackLayout
{
     Orientation = StackOrientation.Vertical,
     Padding = new Thickness(0, 10)
};

Comenzamos por un contenedor. El StackLayout es
un control de tipo contenedor, su cometido es organizar y posicionar
otros controles. Apila los controles por defecto verticalmente aunque
podemos controlar si apilamos de forma vertical o horizontal.

Contamos con múltiples controles de tipo contenedor aparte del StackLayout:

  • ScrollView: Contenedor que permite realizar scroll si el contenido lo requiere.
  • AbsoluteLayout: Posiciona los elementos mediante posiciones absolutas.
  • Grid: Contenedor potente que organiza los elementos mediante filsa y columnas.
  • RelativeLayout: Posiciona los elementos mediante constrainsts.

Continuamos con nuestro ejemplo, añadimos un texto informativo:

//Texto informativo
var info = new Label
{
     Font = Font.SystemFontOfSize(NamedSize.Medium),
     Text = "Hola Xamarin Forms!",
     LineBreakMode = LineBreakMode.WordWrap
};

Hemos añadido un control Label. Tras añadirlo, lo incluimos dentro de nuestro panel:

//Añadimos un botón. Al pulsarlo, actualizará el número de veces que lo pulsamos
var button = new Button
{
     Text = "Púlsame!"  
};
 
//Añadimos el botón a nuestro contenedor
stack.Children.Add(button);
 
var result = new Label
{
     Font = Font.SystemFontOfSize(NamedSize.Large)
};
 
//Añadimos el texto a nuestro contenedor
stack.Children.Add(result);

Hasta aquí toda nuestra inferfaz casi lista!. Nos faltan detalles
sumamente importantes como gestionar la lógica del botón al hacer clic.
En nuestra viewmodel:

public class MainViewModel : ViewModelBase
{
     private int _clicCounter;
 
     private DelegateCommand _helloCommand;
 
     public MainViewModel()
     {
         _clicCounter = 0;
     }
 
     public string Message
     {
         get { return string.Format("Botón pulsado {0} veces", _clicCounter); }
     }
 
     public ICommand HelloCommand
     {
         get { return _helloCommand = _helloCommand ?? new DelegateCommand(HelloCommandDelegate); }
     }
 
     private void HelloCommandDelegate()
     {
         _clicCounter++;
         RaisePropertyChanged("Message");
     }
}

Añadimos un comando que aumentará un contador que nos indicará las veces que se ha pulsado el botón y actualizará un mensaje.

Data Binding

Al igual que en el desarrollo de Windows Phone, el Data binding lo
usamos en Xamarin.Forms para mostrar e interactuar con la información.
Básicamente, establecemos un vínculo entre la interfaz de usuario y la
lógica de la aplicación.

En nuestro ejemplo, queremos vincular nuestra vista, MainView, con
nuestra viewmodel, MainViewModel. Para ello, utilizaremos la propiedad BindingContext:

// DataContext de la página, nuestro ViewModel
BindingContext = new MainViewModel();

Creamos también una propiedad de la viewmodel para que nuestra vista tenga acceso a propiedades y comandos de la viewmodel:

private MainViewModel ViewModel
{
     get { return BindingContext as MainViewModel; }
}

Nos faltaba establecer el comando HelloCommand de nuestra viewmodel al botón de nuestra interfaz:

var button = new Button
{
     Text = "Púlsame!",
     Command = ViewModel.HelloCommand
};

Por último, nos faltaba también añadir al segundo Label de la interfaz, el binding a la propiedad Message:

result.SetBinding(Label.TextProperty, "Message");

Utilizamos el método SetBinding. Este método espera dos parámetros:

El primer parámetro espera información sobre la propiedad en la que estableceremos el binding.

La segunda propiedad espera información sobre el binding. En la
mayoría de situaciones será una cadena con el nombre de la propiedad a
bindear del BindingContext.

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

Ahora con XAML!

Pues si, como ya hemos mencionado, podemos crear la interfaz de
usuario utilizando C# o XAML. El ejemplo exactamente igual que el
anterior pero trasladado a XAML. Para ello, en la carpeta Views, vamos a
añadir una Forms XAML Page. Clic derecho, añadir nuevo elemento:

<?xml version="1.0" encoding="utf-8" ?>
                       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                       x:Class="HolaXamarinFormsXAML.Views.MainView">
</ContentPage>

Añadimos nuestro contenedor junto a nuestros Labels y el botón:

<?xml version="1.0" encoding="utf-8" ?>
                       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                       x:Class="HolaXamarinFormsXAML.Views.MainView">
    <StackLayout Orientation="Vertical" Padding="0, 10">
        <Label Text="Hola Xamarin Forms!" />
        <Button Text="Púlsame!"  />
        <Label />
    </StackLayout>
</ContentPage>

Por último, establecemos los bindings:

<?xml version="1.0" encoding="utf-8" ?>
                       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                       x:Class="HolaXamarinFormsXAML.Views.MainView">
    <StackLayout Orientation="Vertical" Padding="0, 10">
        <Label Text="Hola Xamarin Forms!" />
        <Button Text="Púlsame!" Command="{Binding HelloCommand}" />
        <Label Text="{Binding Message}" />
    </StackLayout>
</ContentPage>

Al botón le bindeamos el comando HelloCommand y al segundo Label el
mensaje resultante. Como podemos ver, la forma de establecer bindings es
igual al utilizado en XAML de Windows Phone por ejemplo.

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

Llegando más lejos

Hasta ahora hemos visto bastantes detalles de Xamarin.Forms pero nos
queda aun camino por recorrer. Una aplicación con acceso a
características del sistema, múltiples páginas, ver como gestionar esto,
inyectar servicios entre otros puntos. Todo lo anterior lo veremos en
la continuación de este artículo.

Más información