[Tips and Tricks] Universal Apps. Behavior para gestionar gestos swipe con comando

SwipeIntroducción

En las aplicaciones móviles la pantalla táctil permite una interacción profunda del usuario con la misma. Gracias a la pantalla táctil el usuario puede interactuar utilizando gran variedad de gestos. Tocar, un toque prolongado, deslizar, pinzamientos y asi cubriendo una gran variedad de gestos con uno o varios dedos.

En aplicaciones Windows Phone 8.1 Silverlight asi como en aplicaciones Universales WinRT tenemos a nuestra disposición un conjunto de eventos de manipulación que nos permiten gestionar la actividad dada en la pantalla táctil con uno o más dedos. Podemos determinar toques, toques prolongados, deslizamientos, etc. Sin embargo, la gestión es mediante eventos, ¿que ocurre si deseamos una gestión igual de sencilla pero manteniendo nuestra arquitectura sólida basada en MVVM?

En este artículo vamos a ver como crear un Behavior que nos permita capturar un tipo de gestos e invocar un comando de nuestra ViewModel.

¿Te apuntas?

Crear el Behavior

Comenzamos creando un nuevo proyecto:

Nueva Aplicación Universal

Nueva Aplicación Universal

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.

Nuestro objetivo sera muy sencillo. Nuestra aplicación de ejemplo tendra un Behavior asociado al Grid principal que capturará los gestos de tipo Swipe lanzando un comando de la ViewModel pasando como parámetro la dirección del gesto.

NOTA: El Behavior se podrá asociar a cualquier elemento derivado de UIElement.

Los Behaviors nos permiten añadir lógica a nuestro XAML para realizar acciones sencillas (pero increíblemente útiles). Entre el conjunto variado de acciones que nos permiten ejecutar, podemos lanzar animaciones, cambiar el estado de un VisualState, realizar la navegación de páginas o invocar un comando. En líneas generales suelen permitir realizar tareas habituales sin necesidad de escribir código en el code-behind de la vista lo que hace posible que mantengamos una arquitectura sólida sin código auxiliar repartido en distintas partes.

Añadir extensión Behaviors

Añadir extensión Behaviors

Con la llegada del SDK de Windows 8.1 recibimos también la incorporación del Behavior SDK. Disponible en una librería aparte del Core de .NET, podemos utilizarlo facilmente añadiendo la referencia a la misma.

NOTA: Con la llegada de Windows Phone 8.1 recibimos acceso a la misma extensión.

Si para nuestros objetivos, ninguno de los Behaviors que tenemos disponible nos cubre, podemos crear Behaviors nuevos totalmente personalizados.

A diferencia de la implementación que teníamos en WPF por ejemplo, no contamos con clases del tipo Behavior<T> de las que derivar, sino que contamos con interfaces, IBehavior. Disponible en el namespace Microsoft.Xaml.Interactivity.

public abstract class Behavior<T> : DependencyObject, IBehavior
       where T : DependencyObject
{

     public T AssociatedObject { get; set; }

     protected virtual void OnAttached()
     {
     }

     protected virtual void OnDetaching()
     {
     }

     public void Attach(DependencyObject associatedObject)
     {
            if (associatedObject == this.AssociatedObject ||
                DesignMode.DesignModeEnabled)
            {
                return;
            }

            this.AssociatedObject = (T)associatedObject;
            OnAttached();
     }

     public void Detach()
     {
            if (!DesignMode.DesignModeEnabled)
            {
                OnDetaching();
            }
     }

     DependencyObject IBehavior.AssociatedObject
     {
            get { return this.AssociatedObject; }
     }
}

La clase superior implementa la interfaz IBehavior de modo que facilmente podemos sobreescribir la propiedad AssociatedObject. Creamos un nuevo Behavior para los elementos que deriven de UIElement:

public class SwipeBehavior : Behavior<UIElement>
{

}

Implementamos la interfaz:

protected override void OnAttached()
{
     base.OnAttached();

     this.AssociatedObject.ManipulationMode =
                this.AssociatedObject.ManipulationMode |
                ManipulationModes.TranslateX |
                ManipulationModes.TranslateY;

     this.AssociatedObject.ManipulationCompleted += OnManipulationCompleted;
}

protected override void OnDetaching()
{
     base.OnDetaching();

     this.AssociatedObject.ManipulationCompleted -= OnManipulationCompleted;
}

private void OnManipulationCompleted(object sender,
            ManipulationCompletedRoutedEventArgs e)
{

}

SwipeBehavior hereda de la clase Behavior<T>.  En el evento sobreescrito OnAttached nos suscribimos al evento ManipulationCompleted gestionando los modos de trasladación en el eje X e Y (Swipes). En el método OnDetaching realizamos la dessuscripción del evento ManipulationCompleted.

public ICommand SwipeCommand
{
     get { return (ICommand)GetValue(SwipeCommandProperty); }
     set { SetValue(SwipeCommandProperty, value); }
}

public static readonly DependencyProperty SwipeCommandProperty =
     DependencyProperty.Register("SwipeCommand",
     typeof(ICommand),    
     typeof(SwipeBehavior),  
     new PropertyMetadata(null));

public static ICommand GetSwipeCommand(DependencyObject d)
{
     return (ICommand)d.GetValue(SwipeCommandProperty);
}

Creamos una DependencyProperty de tipo ICommand. De esta forma, permitimos bindear un comando a ejecutar pasándole como parámetro la dirección del gesto.

Gestión de gestos

Verificaremos valores recibidos de ManipulationCompletedRoutedEventArgs. Concretamente haremos varias verificaciones tanto en el eje X como en el Y para determinar la dirección del gesto. Creamos dos sencillas constantes para simplificar los cálculos:

private const double Min = 0.5;
private const double Max = 100;

En el evento ManipulationCompleted:

bool right = e.Velocities.Linear.X.CompareTo(Min) >= 0 && e.Velocities.Linear.X.CompareTo(Max) <= 0;
bool left = e.Velocities.Linear.X.CompareTo(-Max) >= 0 && e.Velocities.Linear.X.CompareTo(-Min) <= 0;
bool up = e.Velocities.Linear.Y.CompareTo(-Max) >= 0 && e.Velocities.Linear.Y.CompareTo(-Min) <= 0;
bool down = e.Velocities.Linear.Y.CompareTo(Min) >= 0 && e.Velocities.Linear.Y.CompareTo(Max) <= 0;

var swipeCommand = GetSwipeCommand(this);

if (swipeCommand != null)
{
     if (right && !(up || down))
                    swipeCommand.Execute(SwipeDirection.Right);
     if (left && !(up || down))
                    swipeCommand.Execute(SwipeDirection.Left);
     if (up && !(right || left))
                    swipeCommand.Execute(SwipeDirection.Up);
     if (down && !(right || left))
                    swipeCommand.Execute(SwipeDirection.Down);
}

El argumento de tipo ManipulationCompletedRoutedEventArgs, cuenta con toda la información relacionada con la manipulación realizada (gesto). Entre el conjunto de propiedades disponibles contamos con Velocities.Linear.X y Velocities.Linear.Y que nos permiten determinar la dirección del gesto. Dependiendo de la dirección del gesto, ejecutaremos el comando bindeado en la propiedad SwipeCommand pasándole como parámetro la dirección.

En nuestra interfaz, detectaremos el gesto de tipo Swipe y segun la orientación del gesto, mostrando un texto de información indicándola.

En nuestra ViewModel, definiremos la propiedad que mostrará en pantalla la orientación del gesto:

private string _info;

public string Info
{
     get { return _info; }
     set
     {
          _info = value;
          RaisePropertyChanged();
     }
}

Al lanzar un gesto de tipo Swipe lanzaremos un comando:

private ICommand _swipeCommand;

public ICommand SwipeCommand
{
     get { return _swipeCommand = _swipeCommand ?? new DelegateCommand<SwipeDirection>(SwipeCommandExecute); }
}

private void SwipeCommandExecute(SwipeDirection direction)
{
     switch (direction)
     {
          case SwipeDirection.Up:
               Info = "Arriba";
               break;
          case SwipeDirection.Down:
               Info = "Abajo";
               break;
          case SwipeDirection.Left:
               Info = "Izquierda";
               break;
          case SwipeDirection.Right:
               Info = "Derecha";
               break;
     }
}

Recibimos como parámetro el tipo del Swipe de modo que podemos ejecutar en consecuencia.

En la interfaz de usuario, ante cualquier elemento visual derivado de UIElement:

<interactivity:Interaction.Behaviors>
     <behaviors:SwipeBehavior
          SwipeCommand="{Binding SwipeCommand}" />
</interactivity:Interaction.Behaviors>

Sencillo, ¿verdad?

GesturesBehavior

GesturesBehavior

Podéis descargar el sencillo 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

[Evento WPSUG] Porqué no has terminado, cuando has terminado tu app

MoneyIntroducción

Nuestro trabajo como desarrolladores de Apps móviles no termina al publicar la App en la Store. De hecho, podríamos decir que ahi comienza un verdadero trabajo de análisis, recopilación de información y diferentes estrategias de Marketing con el fin de potenciar a la misma.

wpsug.png-550x0

El evento

El próximo Jueves 26 de Marzo, a las 19:00 (GMT+1)  tendra lugar un Hangout en el que tendremos el placer de contar con Vanessa Estorach, con quien veremos diferentes técnicas y estrategias de Marketing en Apps móviles, destacar la App, optimizar resultados, aumentar beneficios, etc.

  • 19:00 en España
  • 13:00 en Colombia
  • 12:00 en México Centro
  • 13:30 en Venezuela
  • 15:00 en Chile continental

¿Te apuntas?

Más información

[Tips and Tricks] Universal Apps. Evento ItemClick a Comando

List All Application installed on cmputerEl problema

Los controles ListView y GridView son una pieza angular muy importante en la mayoría de aplicaciones Windows Store y Windows Phone. En su uso necesitaremos determinar cuando el usuario selecciona un elemento. Trabajando con el patrón MVVM nos encontramos con un problema. Para determinar el elemento seleccionado tenemos dos opciones:

  1. Utilizar la propiedad SelectedItem. Podemos bindear una propiedad de nuestra ViewModel a la propiedad SelectedItem del control. Es una opción válida pero en ciertas situaciones puede no ser completa. Por ejemplo, si al establecer la propiedad realizamos una navegación, al volver atrás si volvemos a intentar navegar al mismo elemento no ocurrira nada. Esto es asi, porque la propiedad SelectedItem esta ya establecida y no cambia. Debemos establecer a nulo la propiedad utilizando los eventos de navegación.
  2. Utilizar el evento ItemClick que nos facilita un argumento de tipo ItemClickEventArgs con la información del elemento seleccionado. Forma idónea pero con un gran problema, no expone una propiedad Command para bindear y utilizar nuestra ViewModel.

¿Qué podemos hacer?

En este artículo crearemos una sencilla clase que exponga una propiedad de dependencia adjunta a utilizar.

ItemClick utilizando un Comando

Añadiremos un GridView con su propiedad ItemsSource bindeada a una propiedad de la ViewModel:

<GridView
     ItemsSource="{Binding Items}"
     SelectionMode="None"
     IsSwipeEnabled="false"
     IsItemClickEnabled="True" />

Deshabilitamos los modos de selección y habilitamos el evento ItemClick. En la ViewModel tendremos definida la propiedad:

private ObservableCollection<string> _items;

public ObservableCollection<string> Items
{
     get
     {
          if (_items == null)
             LoadData();
         return _items;
     }
     set { _items = value; }
}

La propiedad la instanciará un método LoadData que rellenará la colección de sencillos datos para poder probar:

private void LoadData()
{
     _items = new ObservableCollection<string>();
     for (int i = 0; i < 100; i++)
     {
          _items.Add((i + 1).ToString());
     }
}

Llega el momento clave. Vamos a crear una clase donde definiremos una propiedad de dependencia adjunta (Attached DependencyProperty) de tipo ICommand:

public static class ItemClickCommandBehavior
{
     public static readonly DependencyProperty ItemClickCommandProperty =
            DependencyProperty.RegisterAttached("ItemClickCommand", typeof(ICommand),
            typeof(ItemClickCommandBehavior), new PropertyMetadata(null, OnItemClickCommandPropertyChanged));

     public static void SetItemClickCommand(DependencyObject d, ICommand value)
     {
         d.SetValue(ItemClickCommandProperty, value);
     }

     public static ICommand GetItemClickCommand(DependencyObject d)
     {
         return (ICommand)d.GetValue(ItemClickCommandProperty);
     }

     private static void OnItemClickCommandPropertyChanged(DependencyObject d,
         DependencyPropertyChangedEventArgs e)
     {
         var control = d as ListViewBase;
         if (control != null)
             control.ItemClick += OnItemClick;
     }

     private static void OnItemClick(object sender, ItemClickEventArgs e)
     {
         var control = sender as ListViewBase;
         var itemClickCommand = GetItemClickCommand(control);

         if itemClickCommand != null && command.CanExecute(e.ClickedItem))
             itemClickCommand.Execute(e.ClickedItem);
     }
}

Cuando se ejecute el evento ItemClick, se tomará el Comando definido en la propiedad, se le pasará el argumento ClickedItem y se ejecutará.

NOTA: Importante resaltar que en la clase utilizamos ListViewBase, por lo que podremos adjuntar la propiedad tanto en controles GridView como ListView.

Ya podremos adjuntar la propiedad en nuestro GridView. Debemos definir el comando a ejecutar en la ViewModel:

private ICommand _clickCommand;

public ICommand ClickCommand
{
     get { return _clickCommand = _clickCommand ?? new DelegateCommandAsync<string>(ClickCommandExecute); }
}

private async Task ClickCommandExecute(string parameter)
{
     await _dialogService.ShowAsync(parameter);
}

El comando es sencillo. En el método Execute del mismo recibimos el objeto que contiene al elemento seleleccionado como parámetro. En nuestro ejemplo, utilizamos un sencillo servicio de dialogo para mostrar un mensaje con la información del elemento seleccionado.

Todo listo!. Solo nos queda añadir la propiedad adjunta. Añadimos en la vista el namespace donde hemos definido nuestra clase ItemClickCommandBehavior:

xmlns:behaviors="using:ItemClickCommandBehavior.Behaviors"

Y en el GridView:

behaviors:ItemClickCommandBehavior.ItemClickCommand="{Binding ClickCommand}"

De esta forma, tendremos nuestro GridView:

Nuestra colección de elementos

Nuestra colección de elementos

Al pulsar sobre cualquier elemento, nuestra clase dispará el evento ItemClick donde se tomará el comando adjunto y se ejecutará:

Comando ejecutado

Comando ejecutado

Podéis descargar el sencillo 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

[Windows 10] Novedades a nivel de desarrollo presentadas en el MWC15

Un primer vistazo a Apps Universales en Windows 10

Windows 10 ha llegado como la culminación en el viaje hacia la convergencia en el desarrollo entre plataformas Windows.

Gracias a esta convergencia, podemos ejecutar nuestra App en teléfonos, tabletas, PCs y en la Xbox One. Además, debemos unir los nuevos dispositivos que se unen a la «familia» como dispositivos IoT como la Raspberry Pi 2.

Universal Apps

Universal Apps

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.

Novedades

Para constuir Apps Universales bajo un mismo paquete, con el mismo código pero adaptando la experiencia a cada plataforma se han añadido una serie de novedades a destacar.

UX Adaptable

El modelo de Apps ha sido mejorado en líneas generales pero además se ha mejorado mucho el ViewStateMananger para permitir crear vistas que se adapten al dispositivo con facilidad. Esto nos permitirá crear vistas comunes sin separar en ficheros diferentes por plataforma en la mayoría de casos. Aunque se permitirá seguir dividiendo vistas en caso necesario.

VisualStateManager

VisualStateManager

Además los controles en tiempo de ejecución se adaptarán a las condiciones usadas para interactuar por el usuario. De esta forma, tendremos controles que cambien tamaño u opciones dependiendo de si se usa entrada táctil o ratón por ejemplo.

Cortana

La integración de Cortana será mayor permitiendo búsqueda de Apps, lanzar Apps más frecuentes, etc.

Cloud

Seguiremos contando con opciones en Azure potentes y versátiles como Azure Mobile Services o el Hub de notificaciones además del servicio de notificaciones Windows (WNS), etc. Con Windows 10 llegan más servicios o algunos serán mejorados como OneDrive o Cortana.

Entrando en materia

En el Hall 8 del MWC, Microsoft puso a disposición del público una gran cantidad de Surfaces preparadas para permitir probar y crear Apps Universales Windows 10 mediante dos Hands on Labs guiados.

Devs creando Apps Universales Windows 10

Devs creando Apps Universales Windows 10

Tras probar a fondo todo lo posible, a nivel de desarrollo me gustaría destacar una serie de novedades bastante suculentas.

RelativePanel

Estamos ante un nuevo Panel que tiene como principal objetivo permitir crear interfaces con diseños que se adapten con facilidad a cualquier tipo de tamaño. Posiciona a los elementos que contiene de manera relativa entre ellos.

Veamos un ejemplo sencillo:

<RelativePanel>
    <TextBlock x:Name="MyTextBlock" Text="TextBlock 1" RelativePanel.AlightHorizontalCenterWithPanel="True" />
    <TextBlock Text="TextBlock 2" RelativePanel.RightOf="{Binding ElementName=MyTextBlock}" />
</RelativePanel>

En el trozo de XAML de la parte superior utilizamos el nuevo Panel para posicionar dos simples textos. El primero de los textos se situará de manera central en forma horizontal al Panel. El segundo es mucho más interesante. Su posición será a la derecha del primer texto.

Tendremos disponible una gran variedad de opciones de alineación que serán las que otorgen un potencial muy alto al Panel.

Opciones de alineación del RelativePanel

Opciones de alineación del RelativePanel

Además, podremos utilizar StateTriggers para modificar la relación entre controles segun ciertas condiciones.

SplitView

Para que nos situemos de entrada, muchos conocéis alternativas visuales similares como «Hamburger menu». Crea un menu deslizante lateral.

SplitView

SplitView

Es impresionantemente sencillo de utilizar. Cuenta con una propiedad Pane que permite establecer el contenido del panel. Podemos mostrar el panel a izquierda o derecha mediante la propiedad PanePlacement y el ancho utilizando OpenPaneLength.

Veamos un sencillo ejemplo:

<SplitView
    x:Name="SplitView"
        OpenPaneLength="200"
        Background="Red"
        PanePlacement="Left">
        <SplitView.Pane>
            <Grid>
                <TextBlock Text="SplitView Pane" />
            </Grid>
        </SplitView.Pane>
        <Grid>
            <TextBlock Text="Content" />
        </Grid>
</SplitView>

Por último, tenemos la propiedad IsPaneOpen para determinar si el menu esta abierto o cerrado.

StateTriggers

Llegamos a una de las novedades más potentes. Hasta ahora utilizábamos ya Visual States para gestionar la apariencia de la interfaz de Apps WinRT.

Con una clara inspiración en los triggers de WPF nos llegan los StateTriggers. Su uso es muy simple, realizar un cambio en un elemento visual cuando se cumpla una condición.

Actualmente tenemos disponible AdaptiveTrigger. Basicamente la condición depende de un ancho y alto mínimo que establecemos con las propiedades MinWindowHeight y MinWindowWidth. Cuando la ventana supera el tamaño, el VisualState se activa.

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState x:Name="WideState">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="600" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="MainGrid.Margin" Value="15, 50" />
            </VisualState.Setters>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

¿Os recuerda un poco a las Media Queries en CSS?. Muchos Devs de los que probaron el SDK en alguno de los Hands On Labs me lo comentaron con frecuencia.

Tenemos otra serie de novedades en emuladores, herramientas, etc. que comentaremos con calma cuando Microsoft lo muestre con más detalles.

¿Qué es lo próximo?

Podríamos resumirlo con suma facilidad, //BUILD. Lo mostrado en este MWC es sin duda un gran adelanto que nos abre el apetito vorazmente dejandonos con ansias de mucho más. Se denota un gran esfuerzo por mejorar las herramientas de desarrollo, mejorar la plataforma y sobretodo mejorar las opciones para utilizar y compartir código entre plataformas.

Nos vemos en la próxima!

Nos vemos en la próxima!

Mientras llega la fecha, os recomiendo apuntaros al programa Insider si aun no lo hicisteis y en el caso de desarrollo, la mejor recomendación por ahora es crear Apps Universales 8.1 que serán una gran base de partida cuando la nueva plataforma este disponible.

En el próximo //BUILD se mostrará más, se darán más detalles, etc. Asi que, no se vosotros pero…¿a llegado ya el BUILD?…¿y ahora?….¿y ahora?

Más información

[Universal Apps/ Xamarin.Forms] XCC. Compilación condicional en XAML

XCC LogoIntroducción

Las condiciones de compilación son un proceso que nos permiten definir directivas de compilación que provocarán que partes del código sean compiladas y otras ignoradas.

Utilizamos las directivas #if, #else y #endif para establecer código distinto ante directivas de compilación diferente. Muy utilizados para añadir código que solo añadiremos en DEBUG y en el desarrollo de aplicaciones móviles, para poder añadir en la misma clase partes de código diferente entre distintas plataformas.

Técnica disponible para añadir bloques de código específico por plataforma en nuestro código C# por ejemplo, en el desarrollo de aplicaciones Universales Windows. Sin embargo, en el caso de vistas, debemos utilizar otro tipo de técnicas (VisualStates, separar vistas en ficheros diferentes, etc.) ya que no contamos con compilación condicional en XAML

XCC. XAML Conditional Compilation

El proyecto XAML Conditional Compilation (XCC) de Koen Zwikstra nos define una serie de namespaces XAML que nos permiten trabajar de una forma similar a como lo hacemos en C# con directivas de compilación.

XCC se convierte en una opción muy útil cuando trabajamos con vistas XAML en aplicaciones Universales Windows que deseamos compartir o en Xamarin.Forms. Podremos especificar elementos visuales solo para una plataforma e incluso el mismo elemento definirlo de forma diferente segun la paltaforma.

Aplicaciones Universales

Comenzamos creando un nuevo proyecto:

Nueva Aplicación Universal

Nueva Aplicación Universal

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.

Nuestro objetivo sera muy sencillo. Nuestra aplicación de ejemplo tendra diferentes elementos que añadiremos especificándo la plataforma o bien creando elementos comunes a ambas plataformas pero con propiedades específicas.

A continuación, añadiremos XCC. La librería la tenemos disponible en NuGet por lo que podemos instalarlo usando Nuget Package Manager. En las referencias de la solución hacemos clic derecho y seleccionamos la opción Manage NuGet Packages for Solution…

En la ventana modal que nos aparece, en la parte superior derecha donde podemos realizar una búsqueda, buscamos por “XCC”:

XCC

XCC

Seleccionamos el elemento disponible y pulsamos el botón Install. Tras un breve periodo donde se procede a descargar e incluir las librerías en las referencias de cada proyecto, tendremos lo necesario para comenzar a trabajar con directivas de compilación en XAML.

Lo primero que debemos hacer es definir en nuestra vista los espacio de nombres que utilizaremos para identificar cada plataforma:

xmlns:debug="condition:DEBUG"
xmlns:release="condition:!DEBUG"
xmlns:win81="condition:WINDOWS_APP"
xmlns:wp81="condition:WINDOWS_PHONE_APP"

Los atributos xmlns:win81 y xmlns:wp81 definen los namespaces que utilizaremos para realizar compilación condicional por plataforma con los elementos y atributos XML. También hemos definido los atributos xmlns:debug y xmlns:release que nos permiten distinguir elementos y atributos entre DEBUG y RELEASE.

Comenzamos utilizando los espacio de nombre definidos previamente:

<debug:TextBlock    
     Text="Solo visible en DEBUG" />
            
<release:TextBlock
     Text="Solo visible en RELEASE" />

Hemos definido dos controles para mostrar texto con los atributos xmlns:debug y xmlns:release respectivamente. El primero de ellos solo se compilará y aparecerá en modo DEBUG mientras que el segundo de ellos solo lo hará en RELEASE.

NOTA: El diseñador de Visual Studio ignora los prefijos de compilación condicional que utilizamos.

De la misma forma que lo utilizamos con elementos en su totalidad lo podemos hacer con atributos de un elemento:

<!-- Rojo en DEBUG, Verde en RELEASE -->     
<TextBlock Text="Siempre visible"
     debug:Foreground="Red"
     release:Foreground="Green" />

En el ejemplo superior el TextBlock se compilará para ambas plataformas. Sin embargo, el color del texto será rojo en DEBUG y verde en RELEASE.

Muy interesante sin duda lo visto hasta ahora pero la parte más interesante es el uso de los espacio de nombre que nos permiten hacer distinciones entre las plataformas.

 <!-- Win Button -->
 <win81:Button                
     Content="Windows" />
           
 <!-- Phone Button -->
 <wp81:Button
     Content="Windows Phone" />

En el trozo de XAML anterior, el primero botón solo aparecerá en la App Windows Store mientras que el segundo hara lo propio solo en la App Windows Phone.

Al igual que con las directivas de compilación para distinguir entre DEBUG y RELEASE, podemos establecer atributos (propiedades) distintas segun la plataforma:

<Button
     Content="Botón"
     win81:Background="Blue"
     wp81:Background="Green"/>

El resultado en Windows Store:

Windows Store

Windows Store

Windows Phone:

Windows Phone

Windows Phone

NOTA: XCC no modifica los archivos XAML, crea archivos XAML temporales en la carpeta obj y redirige al compilador XAML a ellos.

Extremadamente útil en vistas no complejas donde mostrar de forma diferente elementos como publicidad, algun DataTemplate, ocultar controles, etc. de una forma muy sencilla.

Al igual que en el uso de directivas de compilación en código C#, la recomendación general es la misma, no conviene realizar un uso excesivo de la técnica. Si tenemos una vista compleja llena de elementos marcados con directivas de compilación se volvería excesivamente compleja y añadiría dificultad a la hora de realizar el mantenimiento de la misma. Según las características de la vista nos encontraremos situaciones en als que conviene utilizar directivas de compilación y otras en las que lo mejor sea separar en dos ficheros diferentes.

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

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

Ver GitHub

Xamarin

XCC también esta disponible para Xamarin.Forms. Para probar las posibilidades crearemos un nuevo proyecto Xamarin.Forms:

Nueva Aplicación Xamarin.Forms

Nueva Aplicación Xamarin.Forms

En el proyecto Shared o PCL crearemos una vista principal XAML:

Nueva página XAML Xamarin.Forms

Nueva página XAML Xamarin.Forms

A continuación, añadiremos XCC utilizando NuGet de igual forma que hicimos con la Aplicación Universal Windows.

Lo primero que debemos hacer es definir en nuestra vista los espacio de nombres que utilizaremos para identificar cada plataforma:

xmlns:android="condition:__ANDROID__"
xmlns:ios="condition:__IOS__"
xmlns:wp="condition:WINDOWS_PHONE"

Los atributos xmlns:android, xmlns:ios y xmlns:wp definen los espacio de nombres que utilizaremos para definir condiciones de compilación en elementos y atributos XML.

Podemos definir con facilidad elementos visuales específicos por plataforma:

<android:Label
     Text="Android" />

<ios:Label
     Text="iOS" />

<wp:Label
     Text="Windows Phone" />

Por supuesto, también podemos utilizar directivas de compilación con atributos (propiedades):

 
<!-- Android: Verde, iOS: Azul, Windows Phone: Rojo -->
<Label Text="Label"
     android:TextColor="Green"
     ios:TextColor="Blue"
     wp:TextColor="Red" />

Podéis descargar el sencillo 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

[Tips and Tricks] RequestTheme, cambiar el tema al vuelo!

ColorIntroducción

En Windows Phone, podemos definir un tema totalmente personalizado para nuestras aplicaciones. Por defecto, ya contamos con dos temas, claro y oscuro («Light» y «Dark» respectivamente). Si no usamos nuestro propio tema totalmente personalizado debemos gestionar los elementos de nuestra interfaz para una correcta visualización tanto en el tema claro como en el oscuro. El usuario puede cambiar el tema a utilizar (por defecto oscuro).

Podemos forzar el utilizar un tema en concreto e incluso en tiempo de ejecución podemos modificar el tema a utilizar para cualquier elemento.

NOTA: Modificar la propiedad Application.RequestedTheme en tiempo de ejecución provoca una excepción.

RequestTheme

La propiedad RequestedTheme nos permite obtener o establecer el tema a utilizar. Al cambiar el valor de RequestedTheme de la Aplicación a nivel general o de un elemento en concreto provoca que se obtengan las plantillas y estilos del ResourceDictionary denominados «Light» o «Dark» de la colección ThemeDictionaries.

NOTA: La propiedad RequestedTheme es ignorada si el usuario tiene activo el modo de alto contraste.

Crearemos un ejemplo muy simple para realizar el cambio en tiempo de ejecución del tema. Creamos dos sencillos botones:

<Button Content="Light" />
<Button Content="Dark" />

El primero cambiara al tema claro y el segundo al tema oscuro:

Nuestra UI en el tema por defecto

Nuestra UI en el tema por defecto

En nuestra viewmodel, contaremos con un comando para cada botón:

private ICommand _lightCommand;
private ICommand _darkCommand;

public ICommand LightCommand
{
     get { return _lightCommand = _lightCommand ?? new DelegateCommand(LightCommandExecute); }
}

public ICommand DarkCommand
{
     get { return _darkCommand = _darkCommand ?? new DelegateCommand(DarkCommandExecute); }
}

private void LightCommandExecute()
{

}

private void DarkCommandExecute()
{

}

La definición de los botones quedara:

<Button Content="Light" Command="{Binding LightCommand}" />
<Button Content="Dark" Command="{Binding DarkCommand}" />

En Windows Phone 8.1, la App cuenta con una Window que contiene un único Frame que ocupa el 100% del área de la pantalla.

El Frame de la Aplicación

El Frame de la Aplicación

El Frame, se crea en el arranque de la aplicación y es el encargado de contener y gestionar cada una de las páginas (Page). La propiedad RequestedTheme establecida en un elemento FrameworkElement es heredado en sus elementos hijos. Podemos anular la herencia estableciendo la propiedad RequestedTheme en un elemento hijo concreto. Estableceremos la propiedad en el Frame de la Aplicación provocando que la misma sea heredada por sus elementos hijos, es decir, las páginas de la Aplicación:

AppFrame.RequestedTheme = ElementTheme.Light;

En el segundo comando establecemos el RequestedTheme del Frame de la Aplicación al tema oscuro:

AppFrame.RequestedTheme = ElementTheme.Dark;

Al cambiar al tema claro nuestra interfaz se vera de la siguiente forma:

Cambio de tema a claro

Cambio de tema a claro

El resultado en video:

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

Más información

[Windows Phone 8.1] Usando el sensor de luz

Light-BulbIntroducción

Existen grandes aplicaciones en la Windows Phone Store que se adaptan a ciertas condiciones del medio. Por ejemplo, vamos en el coche con HERE Drive+ y oscurece, el tono de la aplicación cambia de claro a oscuro para no molestar a los ojos. Lo mismo ocurre al entrar en un túnel o en condiciones atmosféricas adversas.

Cambio de tema

Cambio de tema

Sin duda, una característica sumamente interesante y de agradecer de la Aplicación.

Pero…

¿cómo lo hacen?

El sensor de luz

El sensor de luz es uno de los sensores disponibles en muchos de los dispositivos Windows Phone. Este sensor nos devolverá información relacionada con el nivel de iluminación. Esta medida viene dada en Lux, lumens por metro cuadrado. Los niveles de Lux los podemos catalogar de la siguiente forma:

Condiciones de Luz De(lux) A(lux) Valor Medio (lux) Iluminación
Oscuridad total 0 10 5 1
Muy oscuro 11 50 30 2
Interior oscuro 51 200 125 3
Interior con poca luz 201 400 300 4
Interior normal 401 1000 700 5
Interior con luz 1001 5000 3000 6
Exterior oscuro 5001 10,000 7500 7
Exterior nublado 10,001 30,000 20,000 8
Luz solar directa 30,001 100,000 65,000 9

Manos a la obra

Comenzamos creando un nuevo proyecto desde cero:

Nuevo proyecto

Nuevo proyecto

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 este artículo vamos a crear un ejemplo que adapte el tema de la aplicación entre claro y oscuro dependiendo de la luminosidad del ambiente, gracias al sensor de luz.

Comenzamos añadiendo algunas propiedades básicas e nuestra viewmodel:

public string Info
{
     get
     {
          return "En un lugar de la Mancha, de cuyo nombre no quiero acordarme...";
     }
}

Definimos un valor intermedio entre los Lux que nos indican condiciones de interior y los de exterior para determinar si el «estado» debe ser tema claro u oscuro.

public string State
{
     get { return Lux < 3000 ? "Dark" : "Light"; }
}

Por otro lado, mostraremos en pantalla en todo momento la cantidad de Lux recibidas desde el sensor de luz:

private float _lux;

public float Lux
{
     get { return _lux; }
     set
     {
          _lux = value;
          RaisePropertyChanged();
          RaisePropertyChanged("State");
     }
}

Pasamos a definir nuestra interfaz de usuario:

<ScrollViewer>
     <StackPanel>
          <StackPanel
               Orientation="Horizontal">
               <TextBlock Text="LUX:"
                          FontSize="24" />
               <TextBlock
                    Text="{Binding Lux}"
                    FontSize="24"
                    FontWeight="Black"
                    Margin="5, 0"/>
          </StackPanel>
          <TextBlock
               Text="{Binding Info}"
               FontSize="24"
               TextWrapping="Wrap" />
     </StackPanel>
</ScrollViewer>

Muy sencilla. Mostramos el valor de Lux recibidos del sensor de luz y el texto de la propiedad Info de la viewmodel.

El resultado:

Nuestra UI

Nuestra UI

Al entrar en la vista, en la sobreescritura del método OnNavigatedTo, verficamos si el dispositivo cuenta con sensor de luz. Para ello, utilizamos el método GetDefault() de la clase LightSensor disponible como no podía ser de otra forma en el namespace Windows.Devices.Sensors. Si el sensor esta disponible el método GetDefault() devolverá una instancia del sensor de luz.

Una vez establecida la referencia,estableceremos el valor de ReportInterval. Esta propiedad indica en milisegundos el valor de la tasa de refresco en la que el sensor tomará de nuevo información. Por defecto cuenta con un valor dado por la implementación del driver del sensor. Si deseamos ajustar el valor a las condiciones de nuestra aplicación bastará con asignar un valor diferente a cero.

NOTA: Al salir de la Aplicación recordad devolver el valor a cero. Esto es importante para no afectar al consumo energético.

Por último, llegamos a la forma en la que obtenemos la información. Tenemos dos opciones:

  • GetCurrentReading: Obtiene una vez el valor de Lux facilitado por el sensor.
  • ReadingChanged: Realizando una suscripción a este evento obtenemos información del sensor de manera continuada. La tasa de refresco vendrá indicada por la propiedad ReportInternval.
public override Task OnNavigatedTo(NavigationEventArgs args)
{
     _dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
     _lightSensor = LightSensor.GetDefault();

     if (_lightSensor != null)
     {
          uint minReportInterval = _lightSensor.MinimumReportInterval;
          _lightSensor.ReportInterval = minReportInterval > 1000 ? minReportInterval : 1000;
          _lightSensor.ReadingChanged += _lightSensor_ReadingChanged;
     }
     else
     {
          Debug.WriteLine("El dispositivo no cuenta con el sensor de luz");
     }

      return null;
}

Al salir de la vista, en el método OnNavigatedFrom, cancelamos la suscripción al evento ReadingChanged y establecemos el valor de ReportInterval a cero:

public override Task OnNavigatedFrom(NavigationEventArgs args)
{
     if (_lightSensor != null)
     {
          _lightSensor.ReportInterval = 0;
          _lightSensor.ReadingChanged -= _lightSensor_ReadingChanged;
     }

     return null;
}

Llega el mometo cumbre del artículo, a continuación vamos a ver como obtenemos el valor de Lux:

void _lightSensor_ReadingChanged(LightSensor sender, LightSensorReadingChangedEventArgs args)
{
     _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
     {
          Lux = args.Reading.IlluminanceInLux;
          AppFrame.RequestedTheme =
          State.Equals("Dark", StringComparison.CurrentCultureIgnoreCase) ?
               ElementTheme.Dark : ElementTheme.Light;
     }); 

     Debug.WriteLine("Lux: {0}", Lux);
}

El método anterior se lanzará de manera continuada pudiendo acceder a la información del sensor. Obtenemos el valor de Lux dados por el sensor, actualizará nuestra propiedad State y dependiendo del valor de la misma, cambiamos el tema utilizado de claro a oscuro o viceversa.

NOTA: La ejecución del método se realiza en otro hilo en background. Por lo tanto cualquier acción que conlleve un actualización de la UI debemos envolverla en una llamada de Dispatcher.

Nuestra UI en exterior

Nuestra UI en exterior

Podéis ver el resultado del ejemplo en video a continuación:

Y la App Universal realizada como ejemplo la podéis descargar del siguiente enlace:

Como hemos podido ver en este artículo, el uso del sensor de luz es una tarea bastante sencilla que nos permite adaptar la UI a condicones ambientales mejorando la experiencia de usuario.

NOTA: Exactamente la misma API la tenemos disponible para Apps Windows Store.

Recordar que podéis dejar en los comentarios cualquier tipo de sugerencia o pregunta.

Más información

[Christmas Mobile Apps Sessions] Material de «Publicación de Universal Apps»

DownloadEl evento

El pasado Miércoles 10 de Diciembre tenía lugar el cuarto webcast de las Christmas Mobile Apps Sessiones. Las Christmas Mobile Apps Sessions son un conjunto 10 webcasts sobre desarrollo de apps móviles con variada  temática.

En esta sesión, vimos formas de monetización en aplicaciones universales viendo como crear Trials, In-App Purchase o como añadir publicidad, creamos el paquete de aplicación, publicamos en la tienda con diferentes opciones y finalmente vimos como añadir analíticas avanzadas.

El material

La presentación utilizada:

Podéis descargar el conjunto de ejemplos realizados en el webcast a continuación:

Por último, podéis ver en cualquier momento el webcast en el siguiente video:

Más información

[Windows Phone 8.1] Reproducir Audio en Background

Media-PlayIntroducción

Una tarea habitual a realizar en aplicaciones es la reproducción de archivos de audio ya sean podcasts, música, sonidos, etc. En muchas de las situaciones la reproducción debe continuar cuando la aplicación pasa a background.

En este artículo vamos a crear un reproductor de audio con continuidad al pasar a background.

¿Te apuntas?

Un poco de teoría antes de comenzar…

En Windows Phone 8.0 ya podíamos realizar esta acción, reproducir audio en background. Con la llegada de las aplicaciones Universales con Windows Phone 8.1, la forma de crear la tarea en background es diferente, algo más similar a la forma ya disponible en WinRT aunque tampoco igual. Esto nos permite crear la tarea en background de audio compartiendo  código aunque no sería exactamente el mismo, hay diferencias entre la implementación del agente en background para Windows Phone y Windows Store. Además tendremos acceso a nuevas características previamente no disponibles como trabajar con la velocidad de reproducción por ejemplo.

NOTA: Al actualizar una Aplicación Windows Phone 8.0 a Silverlight 8.1 que implementase una tarea en background de audio hay que tener en cuenta que el AudioPlayerAgent no esta soportado.

Queremos reproducir audio cuando nuestra interfaz de usuario no este en primer plano. Para ello, utilizaremos una tarea en background capaz de reproducir audio.En el espacio de nombres Windows.Media.Playback contamos con un conjunto de APIs destinadas a ofrecernos la posibilidad de reproducir audio en segundo plano (incluso en primer plano en caso necesario). Usando esta API utilizaremos un MediaPlayer global encargado de llevar a cabo la reproducción.

La reprodución del audio se realizará desde background mientras que la App accederá a la información del MediaPlayer vía objeto proxy. Concretamente la comunicación se realizará por un sencillo sistema de mensajería. Se pueden enviar mensajes desde primer plano a segundo plano y viceversa.

NOTA: Un mensaje puede ser desde una simple cadena a un conjunto de valores.

Veamos el diagrama de como sería el sistema:

Diagrama de Background Audio

Arquitectura Background Audio

Cuando queremos reproducir audio en una tarea de fondo en Windows Phone estamos tratando con dos procesos. Por un lado contamos con un proceso en primer plano, nuestra App con nuestra interfaz de usuario y por otro lado una tarea en segundo plano que contará con la lógica para reproducir el audio. Esto es asi ya que si el sistema o el usuario suspende o finaliza el primer proceso, el audio seguiría reproduciendose desde el segundo.

Nuestra UI

Comenzamos creando un nuevo proyecto:

Nuevo proyecto

Nuevo proyecto

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 este ejemplo tendremos eventos del //BUILD 2014 de Channel 9. Nuestro objetivo será crear una App capaz de reproducir el audio de los eventos funcionando por supuesto en segundo plano.

Comenzamos creand el modelo:

public class Event
{
     public string Name { get; set; }

     public string Image { get; set; }

     public string Duration { get; set; }

     public string Url { get; set; }
}

Una sencilla clase que nos permita almacenar toda la información relacionada con un evento. Los valores principales será la Url donde tendremos el acceso al audio y la propiedad Name que nos indicará que se esta reproduciendo.

En nuestra viewmodel, cargaremos la información de un evento:

private Event LoadEvent()
{
     return new Event
     {
          Name = "What’s New for Windows and Windows Phone Developers",
          Image = "ms-appx:///Assets/Build.jpg",
          Duration = "27 minutes, 43 seconds",
          Url = "http://video.ch9.ms/ch9/b544/1a759a52-7309-40c6-aa63-0ef77b38b544/C9Live9001.mp3"
     };
}

Lo llamaremos cuando la vista pase a ser la activa, es decir, al entrar en la vista, sobreescritura del método OnNavigatedTo:

public override Task OnNavigatedTo(NavigationEventArgs args)
{
     // Cargamos los datos del evento
     Event = LoadEvent();

     return null;
}

Una vez cargada la información del evento contaremos en nuestra interfaz con un botón para controlar la reproducción (PlayStop). Definimos una pequeña enumeración para que la gestión del estado sea sencilla:

public enum PlayerState
{
     Play,
     Pause
};

private PlayerState _state;
public PlayerState State
{
     get { return _state; }
     set
     {
          _state = value;
          RaisePropertyChanged();
     }
}

También necesitaremos el comando a ejecutar en la viewmodel al pulsar sobre el botón:

private ICommand _playerCommand;

public ICommand PlayerCommand
{
     get { return _playerCommand = _playerCommand ?? new DelegateCommand(PlayerCommandExecute); }
}

private void PlayerCommandExecute()
{
     if(State == PlayerState.Play)
          // Play
     else
          // Stop
}

De modo que la definición del botón en nuestra interfaz sería:

<Button VerticalAlignment="Stretch"
        BorderBrush="{x:Null}" Width="50"
        Command="{Binding PlayerCommand}">
     <Image Source="{Binding State, Converter={StaticResource StateToIconConverter}}"/>
</Button>

Donde la imagen la gestiona un Converter. El Converter devuelve un icono de Play o Stop segun el estado:

public class StateToIconConverter : IValueConverter
{
     private const string Play = "ms-appx:///Assets/Play.png";
     private const string Stop = "ms-appx:///Assets/Stop.png";

     public object Convert(object value, Type targetType, object parameter, string language)
     {
          var state = value as PlayerViewModel.PlayerState?;

          if (state == null)
              return string.Empty;

          return state == PlayerViewModel.PlayerState.Play ? Play : Stop;
     }

     public object ConvertBack(object value, Type targetType, object parameter, string language)
     {
          return null;
     }
}

El resultado de nuestra UI es el siguiente:

Nuestra interfaz de usuario

Nuestra interfaz de usuario

NOTA: Para simplificar el ejemplo se han suprimido ciertas partes de código no necesarias para el objetivo principal del artículo, el audio en background. Hablamos de código como el estilo del botón de reproducción o el XAML general de la vista. En la parte inferior del artículo esta disponible todo el código fuente del ejemplo.

Creando la tarea en background

Teniendo una aplicación Windows Phone 8.1 nos centramos en añadir la background task. Para añadir la background task debemos añadir un componente WinRT.

Crear nuevo componente de Windows

Crear nuevo componente de Windows

Una vez creado el componente WinRT renombraremos la clase a UpdateTask. La clase BackgoundAudioTask implementa la interfaz IBackgroundTask. Esta interfaz cuenta con un único método llamado Run.

public sealed class BackgroundAudioTask : IBackgroundTask
{
    public void Run(IBackgroundTaskInstance taskInstance)
    {
             
    }
}

NOTA:  La clase de la tarea en segundo plano debe ser una clase public y sealed.

Comenzamos a escribir el código necesario en la tarea en background para realizar la reproducción de audio:

private BackgroundTaskDeferral _deferral;
private SystemMediaTransportControls _systemMediaTransportControl;
private MediaPlayer _mediaPlayer;

Creamos las variables globales necesarias. Antes de continuar vamos a determinar su cometido. Comenzamos hablando de la variable de tipo BackgroundTaskDeferral. La tarea en segundo plano es iniciada por el proceso en primer plano haciendo una llamada aBackgroundMediaPlayer.Current. Tras esa llamada se lanza el método IBackgroundTask.Run donde se inicia la variable _deferral con el objetivo de completar el aplazamiento, la reproducción en los eventos Canceled o Completed.

SystemMediaTransportControls representa los controles multimedia del sistema.

SystemMediaTransportControls

SystemMediaTransportControls

Los utilizaremos para gestionar el audio cuando nuestra Aplicación no se encuentre en primer plano (Ejemplo: Pantalla de bloqueo).

Por último, la variable de tipo MediaPlayer será la que nos exponga los métodos necesarios para comenzar y detener la reproducción del audio.

Continuamos. Vamos a definir el código del método Run:

public void Run(IBackgroundTaskInstance taskInstance)
{
     // La clase SystemMediaTransportControls permite a tu aplicación usar los controles de
     // transporte multimedia del sistema proporcionados por Windows y actualizar la información
     // multimedia que se muestra.
     _systemMediaTransportControl = SystemMediaTransportControls.GetForCurrentView();
     _systemMediaTransportControl.IsEnabled = true;

     BackgroundMediaPlayer.MessageReceivedFromForeground += MessageReceivedFromForeground;
     BackgroundMediaPlayer.Current.CurrentStateChanged += BackgroundMediaPlayerCurrentStateChanged;

     taskInstance.Canceled += OnCanceled;
     taskInstance.Task.Completed += Taskcompleted;

     _deferral = taskInstance.GetDeferral();
}

Aparte de instanciar las variables globales vistas previamente cabe destacar la suscripción a dos eventos fundamentales:

  • MessageReceivedFromForeground: Este evento se lanzará cada vez que un mensaje desde la UI sea enviado.
  • CurrentStateChanged: Este evento se lanzará cada vez que el estado del MediaPlayer cambie entre Playing, Paused o Stopped.

Vemos la definición de los eventos recibidos desde la aplicación:

private void MessageReceivedFromForeground(object sender, MediaPlayerDataReceivedEventArgs e)
{
     ValueSet valueSet = e.Data;
     foreach (string key in valueSet.Keys)
     {
          switch (key)
          {
               case "Play":
                    Play(valueSet[key].ToString(), valueSet["Title"].ToString());
                    break;
               case "Pause":
                    Pause();
                    break;
          }
     }
}

Sencillo. Recordad que la UI se comunica con la tarea en segundo plano vía mensajes. En este evento capturamos los mensajes y los interpretamos. Podemos recibir dos tipos de mensajes desde la UI, comenzar la reproducción detenerla. Asi que en función de la Key recibida lanzamos un método Play o un método Pause.

Veamos por lo tanto la definición de los métodos base, Play y Pause:

private void Play(string url, string title)
{
     _mediaPlayer = BackgroundMediaPlayer.Current;
     _mediaPlayer.AutoPlay = true;
     _mediaPlayer.SetUriSource(new Uri(url));

     _systemMediaTransportControl.ButtonPressed += MediaTransportControlButtonPressed;
     _systemMediaTransportControl.IsPauseEnabled = true;
     _systemMediaTransportControl.IsPlayEnabled = true;
     _systemMediaTransportControl.DisplayUpdater.Type = MediaPlaybackType.Music;
     _systemMediaTransportControl.DisplayUpdater.MusicProperties.Title = title;
     _systemMediaTransportControl.DisplayUpdater.Update();
}

El método Play define la fuente del audio en el objeto MediaPlayer y actualiza toda la información del reproductor SystemMediaTransportControl.

El método Pause:

private void Pause()
{
     if (_mediaPlayer == null)
          _mediaPlayer = BackgroundMediaPlayer.Current;

     _mediaPlayer.Pause();
}

Lanza el método Pause del MediaPlayer.

Debemos controlar que el PlaybackStatus del control SystemMediaTransportControl se ve reflejado en el estado de nuestro MediaPlayer:

private void BackgroundMediaPlayerCurrentStateChanged(MediaPlayer sender, object args)
{
     switch (sender.CurrentState)
     {
          case MediaPlayerState.Playing:
               _systemMediaTransportControl.PlaybackStatus = MediaPlaybackStatus.Playing;
               break;
          case MediaPlayerState.Paused:
               _systemMediaTransportControl.PlaybackStatus = MediaPlaybackStatus.Paused;
               break;
     }
}

También debemos gestionar la pulsación de botones en el control SystemMediaTransportControl:

private void MediaTransportControlButtonPressed(SystemMediaTransportControls sender,
            SystemMediaTransportControlsButtonPressedEventArgs args)
{
     switch (args.Button)
     {
          case SystemMediaTransportControlsButton.Play:
               BackgroundMediaPlayer.Current.Play();
               break;
          case SystemMediaTransportControlsButton.Pause:
               BackgroundMediaPlayer.Current.Pause();
               break;
     }
}

Y por supuesto, debemos cerrar correctamente la tarea de fondo. En caso de finalización o cancelación detenemos la reproducción:

private void Taskcompleted(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
{
     BackgroundMediaPlayer.Shutdown();
     _deferral.Complete();
}

private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
     BackgroundMediaPlayer.Shutdown();
     _deferral.Complete();
}

Cerramos aqui el código de nuestra tarea en background. Continuamos viendo como vincular nuestro proyecto Windows Phone 8.1, la UI, con la tarea.

En nuestro proyecto Windows Phone 8.1 hacemos clic derecho, opción “Add references”:

Añadir la referencia a la tarea en background

Añadir la referencia a la tarea en background

Tras añadir la referencia al componente WinRT debemos realizar algunos cambios en el archivo Package.appxmanifiest. Nos dirigimos a la pestaña “Capabilites”. Añadimos una nueva capacidad de tipo Background Task:

Añadimos la tarea en segundo plano

Añadimos la tarea en segundo plano

En las propiedades debemos definir el tipo a Audio la propiedad Entry Point, es decir, el nombre completo de la clase de nuestra background task incluido namespace:

Definición de la tarea en background

Definición de la tarea en background

Y todo listo!

Integrándolo todo

Con la tarea en background definida y referenciada en nuestro proyecto Windows Phone es hora de integrarlo todo. Vamos a utilizar la tarea en segundo plano en el comando que gestiona la reproducción en la viewmodel, lo recordamos:

private void PlayerCommandExecute()
{
     if(State == PlayerState.Play)
          // Play
     else
          // Stop
}

En los comentarios realizaremos llamadas a metodos Play() Y Stop() respectivamente. Definimos el método de reproducción:

private void Play()
{
     State = PlayerState.Pause;
     BackgroundMediaPlayer.SendMessageToBackground(new ValueSet
     {
          {
               "Play",
               _event.Url
          },
          {
               "Title",
               _event.Name
          }
     });
}

Enviamos un mensaje a nuestra tarea en segundo plano. En la Key le indicamos la acción a ejecutar, la reproducción, pasando en el valor la Url con el audio a reproducir. Podemos pasar tantos parámetros como necesitemos. No estamos limitados a una sencilla cadena. En este ejemplo también se pasa el nombre del audio a reproducir aunque también podría ser interesante la portada del evento/album, el autor o artista, etc.

Ahora pasamos al método de detención de la reproducción.

private void Pause()
{
     State = PlayerState.Play;
     BackgroundMediaPlayer.SendMessageToBackground(new ValueSet
     {
          {
               "Pause",
               string.Empty
          }
     });
}

De esta forma, nuestro comando quedara de la siguiente forma:

private void PlayerCommandExecute()
{
     if(State == PlayerState.Play)
          Play();
     else
          Pause();
}

Y hasta aqui ya lo tenemos todo listo!. El resultado final es el siguiente:

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

Y hasta aquí llega el artículo de hoy. Como siempre espero que os resulte interesante. Recordar que cualquier tipo de duda o sugerencia la podéis dejar reflejada en los comentarios.

En próximos artículos veremos como realizar la misma operación en una Aplicaicón Windows Store para Windows 8.1 entre otras novedades.

Más información

[Christmas Mobile Apps Sessions] Material de «Herramientas y toolkits para Universal Apps»

DownloadEl evento

El pasado Miércoles 10 de Diciembre tenía lugar el cuarto webcast de las Christmas Mobile Apps Sessiones. Las Christmas Mobile Apps Sessions son un conjunto 10 webcasts sobre desarrollo de apps móviles con variada  temática.

En esta sesión, vimos novedades de Visual Studio en XAML y C# relacionadas con aplicaciones universales, profundizamos en todas las posibilidades que nos brinda el emulador de Windows Phone, repasamos algunas características fundamentales de Blend, probamos la herramientas de análisis de rendimiento además de ver muchas otras herramientas variadas.

El material

La presentación utilizada:

Podéis descargar el conjunto de ejemplos realizados en el webcast a continuación:

Por último, podéis ver en cualquier momento el webcast en el siguiente video:

Más información