[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

Anuncios

[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

[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

[Windows Phone 8.1] Capturando la pantalla utilizando la API MediaCapture

MovieIntroducción

Entre la enorme cantidad de nuevas APIs, controles y herramientas disponibles con la llegada de Windows Phone 8.1, desde un principio llamo mucho la atención las nuevas APIs disponibles para grabar en video o tomar capturas de la actividad de la pantalla. Esta nueva característica la tenemos disponible dentro del namespace Windows.Media.Capture y sera nuestro centro de atención en este artículo.

¿Te apuntas?

Primeros pasos

Comenzamos creando un nuevo proyecto:

Nueva App Windows Phone 8.1 desde plantilla vacía

Nueva App Windows Phone 8.1 desde plantilla vacía

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 contará con un botón para comenzar a grabar lo que ocurre en pantalla y otro para detener la grabación. Tras terminar la grabación podemos ver el video resultante.

Antes de comenzar, vamos a añadir la Webcam como capacidad en el archivo Package.manifiest:

Añadimos las capacidades necesarias

Añadimos las capacidades necesarias

Manos a la obra!

Tomar capturas de pantalla de la aplicación e incluso videos es una funcionalidad fantástica para segun que tipo de aplicaciones y sobretodo en juegos. En nuestro ejemplo, debíamos contar con “algo” interesante que grabar. Para ello, vamos a añadir un elipse con una animacion Easing de modo que otorgemos el efecto de una bola cayendo y rebotando.

Para gestionar la animación vamos a utilizar el SDK de Behaviors incluido dentro de las extensiones:

Behaviors SDK (XAML)

Behaviors SDK (XAML)

Nos centramos en la vista principal, MainView.xaml. Añadimos la elipse:

<Ellipse x:Name="Ball" Height="75" Width="75" Fill="Red" RenderTransformOrigin="0.5,0.5">
     <Ellipse.RenderTransform>
          <TranslateTransform/>
     </Ellipse.RenderTransform>
     <interactivity:Interaction.Behaviors>
          <core:EventTriggerBehavior EventName="Loaded">
               <media:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource MyStoryboard}"/>
          </core:EventTriggerBehavior>
     </interactivity:Interaction.Behaviors>
</Ellipse>

Al cargar lanzaremos una animacion llamada MyStoryBoard que tenemos definida en los recursos de la página:

<Storyboard x:Name="MyStoryboard">
    <DoubleAnimation From="0" To="250" Duration="00:00:10"
                     Storyboard.TargetName="Ball"
                     Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)">
         <DoubleAnimation.EasingFunction>
              <BounceEase Bounces="40" EasingMode="EaseOut"
                          Bounciness="1.2" />
         </DoubleAnimation.EasingFunction>
     </DoubleAnimation>
</Storyboard>

De este modo, al lanzar la aplicación se lanzará la animación con el siguiente resultado:

Animación

Animación

Ya tenemos listo el elemento visual a grabar. Necesitamos dos botones en nuetra aplicación. Uno para grabar la pantalla y otro que permita ver el resultado:

<Page.BottomAppBar>
     <CommandBar>
          <AppBarButton />
          <AppBarButton />
     </CommandBar>
</Page.BottomAppBar>

Creamos una CommandBar y añadimos los botones. El primer botón nos permitirá comenzar y detener la grabación del video. El segundo nos permitirá ver el video resultante. Para poder conseguir este resultado, en la viewmodel necesitaremos una propiedad bool que nos indique si se esta grabando el video o no, para permitir comenzar o detener la grabación y otra propiedad bool que nos indique si ya hay un video grabado:

private bool _recording;
private bool _result;

public bool Recording
{
     get { return _recording; }
     set
     {
          _recording = value;
          RaisePropertyChanged("Recording");
     }
}

public bool Result
{
     get { return _result; }
     set
     {
          _result = value;
          RaisePropertyChanged("Result");
     }
}

El segundo botón lo mostraremos cuando la propiedad Result sea cierta, necesitaremos un Converter para convertir la propiedad bool a Visibility:

public class BoolToVisibilityConverter : IValueConverter
{
     public object Convert(object value, Type targetType, object parameter, string language)
     {
          if (value is bool)
               return ((bool) value) ? Visibility.Visible : Visibility.Collapsed;

          return Visibility.Collapsed;
     }

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

El primer botón nos permite controlar dos estados diferentes:

  • Comenzar a grabar.
  • Detener una grabación.

Si estamos grabando o no nos lo indica la propiedad Recording, necesitaremos dos converters para obtener el Label y el Icon del AppBarButton segun el valor de la propiedad:

public class RecordStateToIconElementConverter : IValueConverter
{
     public object Convert(object value, Type targetType, object parameter, string language)
     {
            var recording = value as bool?;

            if (recording != null)
                return recording == true ? new SymbolIcon(Symbol.Stop) : new SymbolIcon(Symbol.Play);
            return new SymbolIcon(Symbol.Stop);
     }

     public object ConvertBack(object value, Type targetType, object parameter, string language)
     {
            return null;
     }
}
public class RecordStateToStringConverter : IValueConverter
{
     public object Convert(object value, Type targetType, object parameter, string language)
     {
          var recording = value as bool?;

          if (recording != null)
              return recording == true ? "Stop" : "Play";
          return "Play";
     }

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

Cada botón realizará una acción, en la viewmodel, la ejecución de un comando:

private ICommand _recordCommand;
private ICommand _resultCommand;

public ICommand RecordCommand
{
     get { return _recordCommand = _recordCommand ?? new DelegateCommandAsync(RecordCommandDelegate); }
}

public ICommand ResultCommand
{
     get { return _resultCommand = _resultCommand ?? new DelegateCommand(ResultCommandDelegate); }
}

public async Task RecordCommandDelegate()
{   

}

public void ResultCommandDelegate()
{

}

De modo que nuestros dos botones en la CommandBar quedaran:

<AppBarButton
     Label="{Binding Recording, Converter={StaticResource RecordStateToStringConverter}}"
     Icon="{Binding Recording, Converter={StaticResource RecordStateToIconElementConverter}}"
     Command="{Binding RecordCommand}" />
<AppBarButton
     Visibility="{Binding Result, Converter={StaticResource BoolToVisibilityConverter}}"
     Label="Result"
     Icon="Forward"
     Command="{Binding ResultCommand}" />

API Windows.Media.Capture

Llegamos a la parte fundamental del ejemplo y del artículo, el uso de las APIs disponibles en Windows.Media.Capture. Crearemos un servicio sencillo que nos permita grabar con facilidad lo que ocurre en la pantalla de nuestro dispositivo. El servicio lo llamaremos ScreenRecorederService y su definición sera la siguiente:

public interface IScreenRecorderService
{
     ScreenRecorderService.RecordingStatus Status { get; }
     Task Start(string recordName);
     void Stop();
}

Como podemos ver contamos con:

  • Una propieddad Status. Nos permitirá consultar en todo momento si el servicio esta grabando la pantalla, esta detenido, ha terminado la grabación con éxito o por el contrario ha ocurrido algun error.
  • Un evento Start que recibirá como parámetro el nombre del video resultante de la grabación. Este evento se encarga de comenzar la grabación del video.
  • Un evento Stop que detiene una grabación inciada de video.

Nos centramos en la implementación del servicio. Contaremos con una sencilla enumeración que nos permita determinar con facilidad el estado de la grabación:

public enum RecordingStatus
{
     Stopped,
     Recording,
     Failed,
     Sucessfull
};

Sencillo, ¿cierto?.

Comenzar la grabación del video

Nos centramos en el método Start de nuestro servicio. Comenzamos creando un objeto de clase ScreenCapture utilizando el método: GetForCurrentView:

//Inicializamos ScreenCapture.
ScreenCapture screenCapture = ScreenCapture.GetForCurrentView();

A continuación, creamos una instancia de la clase MediaCaptureInitializationSettings y establecemos la fuente de audio y vídeo con las propiedades AudioSource y VideoSource del objeto ScreenCapture previamente definido:

// Establecemos MediaCaptureInitializationSettings para que ScreenCapture capture audio y video.
var mediaCaptureInitializationSettings = new MediaCaptureInitializationSettings
{
     VideoSource = screenCapture.VideoSource,
     AudioSource = screenCapture.AudioSource,
     StreamingCaptureMode = StreamingCaptureMode.AudioAndVideo
};

El objeto MediaCaptureInitializationSettings nos permite establecer la configuración básica necesaria para crear un objeto de tipo MediaCapture. El objeto MediaCapture es el encargado de ofrecernos la posibilidad de capturar fotos, audio y vídeos.

// Inicializamos MediaCapture con los settings anteriores.
_mediaCapture = new MediaCapture();
await _mediaCapture.InitializeAsync(mediaCaptureInitializationSettings);

Inicializamos utilizando el método InitializeAsync pasándole como parámetro la configuración anterior.

NOTA: InitializeAsync iniciará una solicitud de consentimiento para obtener permisos de usuario.

Continuamos suscribiéndonos a los eventos Failed, RecordLimitationExceeded, y SourceSuspensionChanged:

_mediaCapture.Failed += (s, e) => { _recordingStatus = RecordingStatus.Failed; };
_mediaCapture.RecordLimitationExceeded += s => { _recordingStatus = RecordingStatus.Failed; };

screenCapture.SourceSuspensionChanged += (s, e) => Debug.WriteLine("IsAudioSuspended:" +
                                                                   e.IsAudioSuspended +
                                                                   " IsVideoSuspended:" +
                                                                   e.IsVideoSuspended);

Tenemos:

  • El evento Failed se lanzará cuando cualquier tipo de error ocurra durante la grabacion del video.
  • El evento RecordLimitationExceeded se lanzará cuando se supere el tiempo máximo de grabación. En Windows 8.1 el tiempo máximo de grabación es de 3 horas.
  • El evento SourceSuspensionChanged se lanzará cada vez que se cambie el estado de activo y suspensión de la aplicación durante la grabación del video. Profundizaremos en la gestión de la grabación del video y la suspensión de la aplicación algo más adelante en este mismo artículo.

A continuación, definimos el perfil de codificación para el archivo de video y audio. Para ello, utilizamos un objeto de la clase MediaEncondingProfile. Tenemos disponible los  formatos más habituales tanto de video como de audio:

  • MP3
  • MP4
  • Wav
  • Wma
  • Wmv
// Creamos un encondig a utilizar. Por defecto, MP4 1080P.                  
MediaEncodingProfile mediaEncodingProfile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.HD1080p);

Tras crear el perfil de codificación, creamos el archivo donde se guardarán los medios capturados:

// Creamos el fichero resultante de la grabación.            
StorageFile video =
                    await
                        ApplicationData.Current.LocalFolder.CreateFileAsync(string.Format("{0}.mp4", recordName),
                            CreationCollisionOption.ReplaceExisting);

Por último, utilizaremos el método StartRecordToStorageFileAsync para comenzar la grabación:

// Con el formato, calidad y archivo destino definidos, comenzamos a grabar.
IAsyncAction startAction = _mediaCapture.StartRecordToStorageFileAsync(mediaEncodingProfile, video);
startAction.Completed += (info, status) =>
{
     if (status == AsyncStatus.Completed)
          _recordingStatus = RecordingStatus.Recording;
     if (status == AsyncStatus.Error)
     {
          _recordingStatus = RecordingStatus.Failed;
          Debug.WriteLine(info.ErrorCode.Message);
     }
};

Detener la grabación del video

Para detener la grabación del video utilizamos el método StopRecordAsync.

public void Stop()
{
     //Detenemos la grabación.           
     IAsyncAction stopAction = _mediaCapture.StopRecordAsync();
     stopAction.Completed += (info, status) =>
     {
          if (status == AsyncStatus.Completed)
               if (_recordingStatus == RecordingStatus.Recording)
                    _recordingStatus = RecordingStatus.Sucessfull;
     };
}

Gestion de la suspensión

Ya hemos mencionado el evento SourceSuspensionChanged. Ante ciertas circunstancias la grabación se suspende para ser reanudada tras finalizar la acción que provocó la suspensión. El video y el audio se pueden bloquear de manera independiente por lo que en ciertas circunstancias se detendrá la grabación de video, en otras las de audio y en ocasiones ambas. Entre alguna de las situaciones que provocan a detención de la grabación tenemos:

  • Una llamada entrante. Detiene la grabación de video y audio.
  • Notificaciones con información personal del usuario.
  • Cuando la aplicación no esta en primer plano.
  • Cuando se reproduce sonido en background se detiene la grabación de audio.

Con el evento SourceSuspensionChanged podemos detectar si se ha detenido la grabacion de video y audio gracias a las propiedades IsVideoSuspended y IsAudioSuspended respectivamente. Podemos utilizar las propiedades para notificar al usuario que se ha detenido la grabacion. Sin embargo, para reanudar la misma no tenemos que hacer nada.

Buenas prácticas

Para no interferir con otras aplicaciones que el usuario pueda utilizar cuando suspende nuestra aplicación, debemos limpiar los recursos de captura utilizados en la suspensión.

Para realizar esta tarea creamos una propiedad pública con un objeto de tipo MediaCapture en el archivo App.xaml.cs:

public MediaCapture MediaCapture { get; set; }

Establecemos la propiedad en nuestro servicio:

// Establecemos el MediaCapture de App.xaml.cs para gestionar la suspensión.
var app = Application.Current as App;
if (app != null) app.MediaCapture = _mediaCapture;

En el evento OnSuspending realizaremos la detención de la grabación y la liberación de recursos:

if (MediaCapture != null)
{
     await MediaCapture.StopRecordAsync(); 

     MediaCapture.Dispose();
}

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

Más información

[Evento] Material del Madrid Mobility Day

dateEl evento

El pasado Martes, 30 de Septiembre tuve el enorme placer de participar en el Madrid Mobility Day junto a Josué Yeray, Alejandro Campos y Luis Guerrero. El evento constaba de múltiples sesiones de desarrollo para Windows y Windows Phone, publicación y marketing, desarrollo de videojuegos con Unity y desarrollo de aplicaciones móviles multiplataforma con Xamarin.

Microsoft Ibérica

Microsoft Ibérica

Las charlas

Participé con dos charlas de desarrollo:

  • Desarrollo de aplicaciones Windows Phone con Silverlight 8.1.
  • Introducción al desarrollo de apps móviles multiplataforma con Xamarin.Forms. En esta ocasión con la inestimable ayuda de Josué Yeray.

Desarrollo de aplicaciones Windows Phone con Silverlight 8.1

Con la llegada de Windows Phone 8.1 tenemos nuevas formas posibles de desarrollar nuestras aplicaciones. Por un lado se habla de Silverlight 8.1, por otro de Windows XAML, WinJS, etc. La idea de la charla era:

  • ¿Que diferencia hay entre Silverlight 8.1 y Windows XAML?
  • ¿Si ya tengo una App Windows Phone 8.0, que uso?
  • ¿Y si es Windows Store?

En Windows Phone 8.1 podemos desarrollar con:

  • Silverlight Windows Phone 8.0: Todas las aplicaciones Windows Phone 8.0 funcionan en Windows Phone 8.1 aunque evidentemente sin tener acceso a las nuevas APIs.
  • Silverlight 8.1: Nueva versión destinada a migrar aplicaciones con rapidez y sencillez.
  • Windows XAML: Gran novedad, llega a Windows Phone una nueva versión de XAML, el ya usado en aplicaciones WinRT. La convergencia entre las paltaformas se potencia gracias a esta nueva opción dando lugar a los proyectos universales.
  • WinJS: Permite desarrollar aplicaciones nativas con HTML5, CSS y JS. Ahora además de aplicaciones Windows Store permite crear aplicaciones Windows Phone, incluso aplicaciones universales.

Las conclusiones básicas de la charla fueron:

  • Si tienes una aplicación Windows Phone 8.0 y no necesitas ninguna de las nuevas APIs disponibles en Windows Phone 8.1 no tienes que hacer nada. Las aplicaciones Windows Phone 8.0 funcionan en los dispositivos Windows Phone 8.1.
  • Si tienes una aplicación Windows Phone 8.0 y quieres utilizar las nuevas APIs de Windows Phone 8.1, Silverlight 8.1 es una buena opción. Silverlight 8.1 nos permite migrar aplicaciones con suma facilidad. Si tu aplicación utiliza:
    • CameraCaptureTask
    • Camera Lenses
    • Lockscreen background image provider
    • Alarmas
    • Recordatorios

Silverlight 8.1 es tu opción ya que estas opciones son exclusivas de Silverlight.

Desde Windows Phone 8.0

  • Sin embargo, hay opciones disponibles en Windows Phone 8.0 que no estan disponibles en Silverlight 8.1 como por ejemplo Background Audio. En este caso Windows XAML es la opción adecuada.
  • Si partimos de una aplicación Windows Store, Windows XAML es más directo.
  • Si partimos de una aplicación Windows Store desarrollada con WinJS, utilizar WinJS para Windows Phone es lo más idóneo.
  • Si partimos de cero, podemos elegir entre Windows XAML, WinJS o Silverlight 8.1. Todo depende de los conocimientos que tengas en los lenguajes y que quieras realizar. Si quieres cubrir teléfonos y tabletas (aplicaciones Windows Phone y Windows Store), realizar una aplicación universal es lo más idóneo.

Nueva Aplicación

Introducción al desarrollo de apps móviles multiplataforma con 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).

En esta sesión junto a Josué Yeray teníamos como objetivos:

  • Trasmitir en que consiste y como funciona Xamarin.Forms.
  • Crear y explicar una App desde cero.
  • Aplicar MVVM.
  • Comparar con alternativas como MVVMCross.

El material

Desarrollo de aplicaciones Windows Phone con Silverlight 8.1

Os dejo a continuación la presentación utilizada en esta sesión:

Además de los ejemplos.

Introducción al desarrollo de apps móviles multiplataforma con Xamarin.Forms

Presentación utilizada en esta sesión:

Y ejemplos.

Extra

No quisiera terminar sin agradecer a los chicos de DX por su colaboración,  facilitarnos la sala y en definitiva hacer que todo sea tan sencillo. También me gustaría agradecer a Cristina Guerrero, nuestra MVP Lead por realizarnos una visita. Era la primera vez que la conocía en persona y solo puedo decir que es aun más encantadora en persona si cabe. Y por supuesto, como no, gracias a todos los asistentes.

Madrid Mobility Day

Madrid Mobility Day

Más información

[Windows Phone 8.1] Update Task

Command-RefreshIntroducción

Con la llegada de nuevo SDK siempre esperamos novedades significativas que nos permitan crear cada vez mejores aplicaciones y otorgar experiencias de usuario más completas. Con la llegada del SDK de Windows Phone 8.1 contamos con nuevos controles, nuevas APIs, herramientas, etc.

Entre la enorme cantidad de novedades contamos con una nueva background task realmente útil llamada Update Task.

Como podemos adivinar por su nombre, este background task se ejecuta cuando la aplicación se actualiza. Esto nos permite mostrar información al usuario, actualizar datos, actualizar settings, etc.

En este artículo vamos a aprender como utilizar el Update Task.

¿Te apuntas?

Creando el Update Task

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.

Añadir un componente Windows Runtime

Añadir un componente Windows Runtime

Una vez creado el componente WinRT renombraremos la clase a UpdateTask. La clase UpdateTask implementa la interfaz IBackgroundTask. Esta interfaz cuenta con un único método llamado Run. Este método sera al que el sistema llamará para iniciar la background task y es necesario en todas las tareas en segundo plano.

namespace UpdateTask
{
    public sealed class UpdateTask : IBackgroundTask
    {
        public void Run(IBackgroundTaskInstance taskInstance)
        {

        }
    }
}

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

La Task tendrá como objetivo notificar al usuario de una nueva versión. Por lo tanto, en el método Run vamos a lanzar una notificación Toast y vamos a actualizar el Tile de la aplicación.

Creamos un método para lanzar una notificación Toast:

/// <summary>
/// Muestra una notificación Toast para notificar al usuario de la nueva versión.
/// </summary>
private void ShowNotification()
{
     var toastnotifier = ToastNotificationManager.CreateToastNotifier();
     var toastDescriptor = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
     var txtNodes = toastDescriptor.GetElementsByTagName("text");

     txtNodes[0].AppendChild(toastDescriptor.CreateTextNode("Nueva versión!"));
     txtNodes[1].AppendChild(toastDescriptor.CreateTextNode(string.Format("Actualizada : {0}", DateTime.Now)));

     var toast = new ToastNotification(toastDescriptor);

     toastnotifier.Show(toast);
}

Utilizamos la clase PushNotificationChannelManager encargada de crear objetos que se utilizan para recuperar canales de notificaciones de inserción de Servicios de notificaciones de inserción de Windows (WNS). Utilizamos el método CreateToastNotifier que crea una nueva instancia de ToastNotification, que permite generar una notificación Toast para la aplicación.

Otro método actualizará el Tile de la aplicación:

/// <summary>
/// Actualiza el Tile de la aplicación notificando de la nueva versión.
///
/// Catálogo de plantillas de Tiles: http://msdn.microsoft.com/es-es/library/windows/apps/hh761491.aspx
/// </summary>
private void UpdateTileStatus()
{
     var tileContent = TileUpdateManager.GetTemplateContent(
                       TileTemplateType.TileSquare150x150Text01);

     var tileText = tileContent.SelectNodes("tile/visual/binding/text");

     tileText[0].InnerText = "Nueva versión!";
     tileText[1].InnerText = "Actualizada";
     tileText[2].InnerText = DateTime.Now.ToString();

     var notification = new TileNotification(tileContent);
     var updater = TileUpdateManager.CreateTileUpdaterForApplication();

     updater.Update(notification);
}

La clase TileUpdateManager crea objetos de tipo TileUpdater utilizados para actualizar el Tile de Aplicación. Esta clase también nos facilita el acceso al contenido XML de las plantillas de Tile para poder personalizar dicho contenido.

En el método Run de la Task ejecutaremos ambos métodos:

public void Run(IBackgroundTaskInstance taskInstance)
{
     ShowNotification();
     UpdateTileStatus();
}

Una vez terminada nuestra Task debemos añadir la referencia a la misma de nuestro proyecto Windows Phone. Clic derecho, opción “Add references”:

Añadimos la referencia a la background task

Añadimos la referencia a la background task

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 Update Task:

Añadimos la capacidad Update Task en el Package.appxmanifiest

Añadimos la capacidad Update Task en el Package.appxmanifiest

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

Entry Point

Entry Point

Y todo listo!

NOTA: Para que nuestro ejemplo funcione en su totalidad antes de abandonar el archivo de manifiesto debemos activar la capacidad de notificaciones Toast en nuestra aplicación.

Probando el Update Task

Es hora de verificar que todo funciona correctamente. Tras lanzar nuestra aplicación al menos una vez, podemos simular una actualización de la aplicación modificando la versión de la misma en el archivo Package.appxmanifiest.

Actualizamos la versión de la aplicación

Actualizamos la versión de la aplicación

Tras ejecutar la aplicación:

El resultado

El resultado

Recibiremos una notificación Toast indicandonos la nueva versión además de haber sido actualizado el Tile de la aplicación.

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

Más información

[Windows Phone 8.1] Application Data APIs

Folder-Edit-01Introducción

Entre la gran cantidad de novedades recibidad en el SDK de Windows Phone 8.1 brillan con fuerza las APIs de Application Data. Nuevas APIs que brindan mas opciones y que en su mayoría se alinean mucho con las APIs ya existentes en Windows Runtime (WinRT). Algo que permite a los desarrolladores Windows Store dar el salto con facilidad a Windows Phone además de permitir compartir código con facilidad.

En esta entrada vamos a analizar las novedades en las APIs de Application Data.

¿Te apuntas?

Application Data APIs

Tanto en aplicaciones Silverlight 8.1 como en aplicaciones Windows XAML tenemos disponible las siguientes carpetas:

  • LocalFolder: Un viejo conocido. Ya lo teníamos disponible en Windows Phone 8 e incluso era la carpeta usada como Isolated Storage desde Windows Phone 7. Guardaremos información que persiste entre actualizaciones de la aplicación y entra dentro de los datos guardados al realizar un backup del sistema.
var localFolder = ApplicationData.Current.LocalFolder;
var file = await roamingFolder.CreateFileAsync("file.txt", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(file, "Ejemplo archivo en LocalFolder");
  • RoamingFolder: Almacenamiento muy útil. Al guardar datos en RoamingData, la información estará disponible en todos los dispositivos donde la aplicación este instalada (con el mismo id). Ideal para guardar la configuración de la aplicación y mantenerla sincronizada entre la aplicación Windows Phone y la Windows Store por ejemplo.
var roamingFolder = ApplicationData.Current.RoamingFolder;
var file = await roamingFolder.CreateFileAsync("file.txt", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(file, "Ejemplo archivo en RoamingFolder");

Podemos controlar cuando los datos de roaming han sido cambiados en alguna de las aplicaciones gracias al evento DataChanged.

Windows.Storage.ApplicationData.Current.DataChanged +=
      new TypedEventHandler<ApplicationData, object>(DataChangeHandler);
 
private async void DataChangedHandler(ApplicationData appData, object o)
{
 
}
  • TemporaryFolder: Aqui guardaremos información sin tener la necesidad de borrarla más tarde. La información se guardará entre las distintas sesiones pero cuando el sistema requiera espacio (Ejemplo: poca memoria disponible), eliminará la información.  Es un lugar idóneo donde guardar datos obtenidos de peticiones web, servicios o imágenes por ejemplo.
var tempFolder = ApplicationData.Current.TemporaryFolder;
var tempFile = await roamingFolder.CreateFileAsync("file.txt", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(tempFile, "Ejemplo archivo en TemporaryFolder");

Podemos acceder a ficheros en la carpeta temporal desde XAML utilizando el protocolo “ms-appdata:///temp/”.

<img src="ms-appdata:///temp/file.png" alt="" />

NOTA: La información de TemporaryFolder no se guarda en los Backups realizados al sistema.

Otra de las novedades, esta vez solo disponible en las APIs de Windows Phone 8.1 es LocalCacheFolder. Almacenamiento muy similar a LocalFoler pero con ligeras diferencias. Los datos no se copian nunca al realizar backups.

NOTA: Podemos usar la API solo en Windows Phone, en un proyecto Shared necesitamos definir directivas de compilación para evitar errores.

#if WINDOWS_PHONE_APP
    var localCache = ApplicationData.Current.LocalCacheFolder;
#endif

Más información

[Windows Phone 8.1] Probando notificaciones en el emulador

windows_phone_mac_iconIntroducción

Uno de los primeros cambios visibles en el nuevo SDK de Windows Phone 8.1 es el emulador. Contamos con una gran variedad de emuladores con diferentes resoluciones, tamaños de pantalla y memoria lo que nos permite probar una gran cantidad de la funcionalidad de nuestras aplicaciones. Entre la gran cantidad de situaciones que podemos probar, tenemos la posibilidad de probar notificaciones sin la necesidad de crear un servicio en la nube que si es necesario al publicar la aplicación.

Notificaciones

Notificaciones

Manos a la obra!

Vamos a crear un proyecto básico pero suficiente para poder probar la herramienta de notificaciones del emulador. Creamos un nuevo proyecto:

Nuevo proyecto

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.

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

Habilitar las notificaciones Toast en el archivo de manifiesto

Habilitar las notificaciones Toast en el archivo de manifiesto

Pasamos a definir la interfaz de nuestra aplicación. El objetivo sera probar la herramienta de notificaciones del emulador por lo que nuestra interfaz sera lo más simple posible para conseguir el objetivo. En este caso, un simple botón que al ser pulsado recuperare un canal de notificación de inserción para la aplicación:

<Grid>
     <Button Content="Enviar notificación"
             Command="{Binding NotificationCommand}"
             HorizontalAlignment="Center"/>
</Grid>

El resultado es:

Nuestra interfaz

Nuestra interfaz

En la viewmodel correspondiente a la vista se ejecutará un comando:

private ICommand _notificationCommand;

public ICommand NotificationCommand
{
     get { return _notificationCommand = _notificationCommand ?? new DelegateCommandAsync(NotificationCommandDelegate); }
}

public async Task NotificationCommandDelegate()
{
     var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();

     channel.PushNotificationReceived += (s, e) => {
          if (e.ToastNotification != null)
               Debug.WriteLine("{0}:{1}", e.NotificationType, e.ToastNotification.Content.InnerText);
     };
}

Utilizamos la clase PushNotificationChannelManager encargada de crear objetos que se utilizan para recuperar canales de notificaciones de inserción de Servicios de notificaciones de inserción de Windows (WNS). Esta clase cuenta con un método llamado CreatePushNotificationChannelForApplicationAsync() que sera el utilizado para recuperar un canal de notificación de inserción para la aplicación. A continuación, para asegurarnos que todo esta funcionando como debe, nos suscribimos al evento PushNotificationReceived que se desencadena cuando llega una notificación de inserción a este canal.

Simulando notificaciones

Y ya. Todo listo para poder probar el simulador de notificaciones!. Pulsamos el botón y abrimos las herramientas extras seleccionando la pestaña notificaciones:

Herramienta extra notificaciones

Herramienta extra notificaciones

Habilitamos la simulación de notificaciones y pulsamos el botón refrescar:

Pulsamos el botón Refrescar

Pulsamos el botón Refrescar

Se refresca el contenido de la herramienta mostrando el AppId de la aplicación asi como la Uri, tipo de notificación y plantilla utilizada. En este punto podemos ver el contenido y probar con distintos tipos de notificaciones hasta poder elegir con certeza cual encaja con nuestros objetivos. Pulsamos el botón enviar:

Enviamos la notificación

Enviamos la notificación

Y automáticamente recibimos el mensaje de la notificación quedando la misma tambien registrada en el centro de actividades:

Notificación recibida!

Notificación recibida!

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

Más información

Windows Phone 8.1 Update 1 SDK disponible: Nuevos emuladores

Binary-CodeIntroducción

Windows Phone 8.1 Update 1 esta disponible para todos los participantes del programa Preview for Developers. Esta nueva actualización viene cargada con algunas novedades interesantes como los Live Folders (carpetas) o soporte a nuevas resoluciones. Al incluir novedades como por ejemplo, las nuevas resoluciones, era necesario permitir a los desarrolladores probar sus aplicaciones…

Live Folders

Live Folders

Descargar e instalar el nuevo SDK

Por ese motivo nos llega este SDK con el Update 1 de Windows Phone 8.1 además de nuevas imágenes con nuevos emuladores.

NOTA: Es necesario contar con Windows 8.1 y al menos Visual Studio 2013 Update 2 en la máquina de desarrollo para poder instalar el SDK. Podemos instalar Visual Studio Express 2013 Update 2 desde el siguiente enlace.

Para descargar el SDK accederemos al siguiente enlace. Una vez descargados los ficheros, contaremos con dos ficheros, un ejecutable y una ISO:

Archivos descargados

Archivos descargados

Información de los ficheros

Información de los ficheros

Lanzaremos el ejecutable con privilegios de administrador y nos aparecerá el asistente de instalación:

Windows Phone 8.1 Update 1 SDK

Windows Phone 8.1 Update 1 SDK

Tras unos minutos, la instalación finalizará:

Instalación completada

Instalación completada

Nuevos emuladores

Tendremos disponible nuevos emuladores preparados con el Update 1 para poder probar nuestras aplicaciones:

  • Windows Phone Emulator 8.1 Update 1 WVGA 4 inch
  • Windows Phone Emulator 8.1 Update 1 WVGA 4 inch 512MB
  • Windows Phone Emulator 8.1 Update 1 WXGA 4.5 inch
  • Windows Phone Emulator 8.1 Update 1 720p 4.7 inch
  • Windows Phone Emulator 8.1 Update 1 1080p 5.5 inch
  • Windows Phone Emulator 8.1 Update 1 1080p 6 inch
Nuevos emuladores!

Nuevos emuladores!

Más información

[Windows Phone 8.1] Integrando nuestra aplicación con Cortana

CortanaIntroducción

En el pasado //BUILD/ de Microsoft, Joe Belfiore vicepresidente del programa Windows Phone presento Cortana. Cortana es el asistente virtual de Windows Phone y entre la enorme cantidad de funcionalidades incluidas además de las que van incorporando poco a poco (como por ejemplo, recomendaciones basadas en búsquedas de Foursquare) cabe destacar que es una plataforma sobre la que se pueden desarrollar aplicaciones de terceros. En este artículo vamos a centrarnos en analizar paso a paso como integrar una aplicación Windows Phone con Cortana.

¿Te apuntas?

Nuestra Aplicación

Para integrar una aplicación Windows Phone con Cortana lo primero que necesitamos es… una aplicación!. Vamos a crear una aplicación sencilla pero que nos sea válida para lograr nuestros objetivos. Nuestra aplicación permitirá consultar cualquier clasificación de pilotos de Formula 1 celebrada hasta la actual:

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. Como hemos comentado, nuestra aplicación permitirá consultar cualquier clasificación de pilotos de Formula de cualquier año, necesitamos un selector de año. Añadimos un botón en nuestra interfaz:

<Button />

Al botón le añadiremos un ListPickerFlyout, nos permitirá mostrar un listado de opciones al pulsar sobre el botón:

<Button>
     <Button.Flyout>
          <ListPickerFlyout
               Title="SELECCIONA AÑO">
               <ListPickerFlyout.ItemTemplate>
                    <DataTemplate>
                         <StackPanel>
                              <TextBlock Text="{Binding}" FontSize="{StaticResource TextStyleExtraLargeFontSize}"/>
                         </StackPanel>
                    </DataTemplate>
               </ListPickerFlyout.ItemTemplate>
          </ListPickerFlyout>
     </Button.Flyout>
</Button>

La interfaz de la página principal de nuestra aplicación es la siguiente:

Nuestra página principal

Nuestra página principal

El control ListPickerFlyout muestra una colección indicada mediante la propiedad ItemsSource, y el elemento seleccionado lo podemos obtener mediante la propiedad SelectedItem:

<Button
     HorizontalAlignment="Stretch" HorizontalContentAlignment="Left"
     Content="{Binding SelectedYear}">
     <Button.Flyout>
          <ListPickerFlyout
               Title="SELECCIONA AÑO" ItemsSource="{Binding Years}"
               SelectedItem="{Binding SelectedYear, Mode=TwoWay}">
               <ListPickerFlyout.ItemTemplate>
                    <DataTemplate>
                         <StackPanel>
                              <TextBlock Text="{Binding}" FontSize="{StaticResource TextStyleExtraLargeFontSize}"/>
                         </StackPanel>
                    </DataTemplate>
               </ListPickerFlyout.ItemTemplate>
          </ListPickerFlyout>
     </Button.Flyout>
</Button>

Al pulsar el botón:

ListPickerFlyout para la selección de año

ListPickerFlyout para la selección de año

¿Pero la propiedad Years bindeada a la propiedad ItemsSource de donde obtiene la información?. En nuestra viewmodel:

private ObservableCollection<string> _years;
private string _selectedYear;

public ObservableCollection<string> Years
{
     get { return _years; }
     set { _years = value; }
}

private void LoadYears()
{
     for (var i = 1950; i <= DateTime.Now.Year; i++)
     {
          Years.Add(i.ToString());
     }
}

public string SelectedYear
{
     get { return _selectedYear; }
     set
     {
          _selectedYear = value;
          RaisePropertyChanged();
     }
}

Creamos una colección de años que usaremos para bindear al control ListPickerFlyout. La información, los años seleccionables, los rellenamos con el sencillo método LoadYears que podéis ver en las líneas superiores. Una vez seleccionado un año del que obtener la clasificación de pilotos, necesitamos lanzar la búsqueda:

<Page.BottomAppBar>
     <CommandBar>
          <AppBarButton Label="buscar" Icon="Find" Command="{Binding SearchCommand}" />
     </CommandBar>
</Page.BottomAppBar>

Añadimos una CommandBar con un botón que nos permita realizar la búsqueda. En la viewmodel:

private ICommand _searchCommand;

public ICommand SearchCommand
{
     get { return _searchCommand = _searchCommand ?? new DelegateCommand(SearchCommandDelegate); }
}

public void SearchCommandDelegate()
{
     AppFrame.Navigate(typeof(SearchView), _selectedYear);
}

Añadimos un comando de modo que al ejecutarse, navega a una nueva vista pasando como parámetro el año seleccionado. En la vista a la que navegamos tendremos un listado:

<GridView    
     ItemsSource="{Binding DriverStanding}"
     ItemTemplate="{StaticResource DriverTemplate}"
     ItemsPanel="{StaticResource ItemPanelTemplate}"
     SelectionMode="None"
     IsItemClickEnabled="True">

Con la plantilla que definirá el aspecto de cada elemento de la lista:

<DataTemplate x:Key="DriverTemplate">
     <Grid Margin="5" Width="300">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid>
                <Ellipse Height="50" Width="50" Fill="Red"/>
                <TextBlock Text="{Binding Position}" FontSize="28" HorizontalAlignment="Center"
                           VerticalAlignment="Center" Foreground="White" />
            </Grid>
            <StackPanel Grid.Column="1" Margin="15, 0">
                <TextBlock Text="{Binding Driver.CompleteName}" FontSize="24" />
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Points}" Foreground="Red" />
                    <TextBlock Text=" Points" />
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding Wins}" Foreground="Red" />
                    <TextBlock Text=" Wins" />
                </StackPanel>
            </StackPanel>
     </Grid>
</DataTemplate>
    
<ItemsPanelTemplate x:Key="ItemPanelTemplate">
     <StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>

Y por su puesto, su correspondiente viewmodel donde sobrescribimos el evento OnNavigatedTo que se lanzará cada vez que entremos a la página para obtener el parámetro de navegación (el año del que deseamos obtener la clasificación) y lanzamos la carga de la información:

public override async System.Threading.Tasks.Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
{
     var season = args.Parameter.ToString();
     await LoadStandingsData(season);
}

La carga de la información accederá a un servicio que nos permite obtener las clasificaciones de la Formula 1 por año y volcará la información en una colección disponible como propiedad públicada bindeada a la lista:

private async Task LoadStandingsData(string season)
{
     var driverStandings = await _standingService.GetSeasonDriverStandingsCollectionAsync(season);
     var drivers = driverStandings.StandingsLists.First().DriverStandings;

     foreach (var driver in drivers)
     {
          DriverStanding.Add(driver);
     }
}

El resultado:

Resultado de la búsqueda

Resultado de la búsqueda

En la captura superior podemos ver el resultado de la búsqueda de la clasificación de pilotos del año 2006. Un ejemplo sencillo pero interesante que podría ganar mucho realizando la integración con Cortana.

Cortana

Inspirado por un famoso personaje de Halo que era un asistente digital personal para el protagonista del juego, Master Chief, nos llega un asistente de voz personal a Windows Phone.

NOTA: Tras la voz de Cortana esta Jen Taylor la misma mujer que dio voz al personaje en el videojuego.

En otras plataformas contaban con Siri (Apple) o Google Now (Google), asistentes de voz más versátiles que el sistema de reconocimiento de voz con el que contábamos hasta ahora en Windows Phone.

Cortana

Cortana

Cortana llega para ir más alla que todo lo existente hasta ahora.

¿Cómo?

Cortana no se limitará a un sistema que reaccionará ante comandos de voz, si no que tomará múltiples fuentes para contar con la mayor cantidad de información posible para interaccionar de la forma más precisa posible.

La primera vez que usemos Cortana nos realizará algunas preguntas básicas sobre nosotros. A partir de ese momento, Cortana mirara en contactos, lugares habituales, intereses… todo lo necesario para aprender lo máximo posible de nosotros mismos.

NOTA: El nivel de acceso de Cortana a nuestros datos es configurable.

Asi que Cortana no se limita a responder órdenes o realizar búsquedas básicas como lugares, el tiempo o resultados deportivos, además puede establecer recordatorios como recordarle a tu madre que tal le va el nuevo Windows Phone que le has regalado por ejemplo.

Por si fuese poco, otra gran diferencia de Cortana con otros asistentes digitales es la posibilidad de interactuar con aplicaciones de terceros.

Integración con Cortana

Vamos a integrar nuestra aplicación con Cortana.

¿Qué quiere decir esto?

Sencillo, desde Cortana permitiremos preguntar por “Formula One standings for 2006” y que en lugar de realizar una búsqueda en Bing, abra nuestra aplicación, realice la búsqueda correspondiente y muestre la información completa de la clasificación de pilotos de Formula 1 del año 2006. De esta forma, por un lado ofrecemos una experiencia cada vez más completa e integrada al usuario desde Cortana y además logramos otro punto de entrada y un mayor uso de nuestra aplicación.

Para integrar nuestra aplicación con Cortana realizaremos tres sencillos pasos:

1º Creando la definición de comandos de voz (VCD)

El usuario puede mediante voz tratar con Cortana para activar la aplicación y ejecutar una acción concreta. Por ejemplo, el usuario que usa la aplicación Formula 1 Standings podría mantener presionado el botón Buscar para iniciar Cortana y decir: “Formula 1 Standings for 2006”. Esto activará la aplicación Formula 1 Standings, que, seguidamente, navegará a la página de búsqueda realizando la acción. El primer paso para conseguir esto sera crear un archivo VCD (Definición de comando de voz). El archivo VCD es un documento XML en el que definimos todos los comandos de voz que el usuario puede utilizar para lanzar acciones en la aplicación.

Creamos el archivo VCD. En Visual Studio, hacemos clic derecho en el proyecto y seleccionamos Agregar->Nuevo elemento y luego Archivo de texto. Tras agregar el archivo de texto debemos asignarle el nombre que consideremos oportuno, en nuestro ejemplo se le ha llamado VoiceCommandDefinitions y lo más importante debemos cambiar la exntesión a .xml. Además, En la ventana de Propiedades del archivo xml establecemos la propiedad Acción de compilación a Contenido y a continuación Copiar en el directorio de salida en Copiar si es posterior. Nuestra definición de comando de voz es la siguiente:

<?xml version="1.0" encoding="utf-8"?>
<VoiceCommands xmlns="http://schemas.microsoft.com/voicecommands/1.1">
  <CommandSet xml:lang="en-us">
    <CommandPrefix>formula one standing for</CommandPrefix>
    <Example>Search Formula One Standings</Example>
    <Command Name="for">
      <Example>2005</Example>
      <ListenFor>[for] {dictatedSearchTerms}</ListenFor>
      <Feedback>checking that out on Ergast API for you, give me a second</Feedback>
      <Navigate Target="SearchView.xaml"/>
    </Command>
    <PhraseTopic Label="dictatedSearchTerms" Scenario="Natural Language">
   </PhraseTopic>
</CommandSet>
</VoiceCommands>

Todos los comandos de voz tienen:

  1. Una frase de ejemplo que muestra como lanzar el comando.
  2. Las palabras que se reconocerán para lanzar el comando.
  3. El texto que Cortana mostrará al reconocer el comando.
  4. La pantalla a la que navegará la aplicación al reconocer el comando.

Analicemos nuestra definición de comandos de voz paso a paso:

  • <CommandPrefix></CommandPrefix> Puede ser el nombre de la aplicación o la frase que el usuario utilizará para lanzar nuestra aplicación.
  • <Example></Example> Ejemplo de más alto nivel indicando al usuario un ejemplo delo que puede hacer.
  • <Command></Command> Unidad lógica de lo que el usuario quiere hacer. Contiene lo que el usuario dice, lo que Cortana responde y lo que Cortana hace.
  • <ListenFor></ListenFor> Una o más frases.
  • <Feedback></Feedback> Feedback visual trasmitido por Cortana hacia al usuario tras reconocer un comando.
  • <Navigate></Navigate> Acción a realizar tras reconocer el comando. Es opcional en apps no-Silverlight.

2º Registrar el XML del VCD en el inicio de la App

Durante la ejecución de la aplicación, registraremos el conjunto de comandos contenidos en el archivo VCD:

async Task InstallVoice()
{
     var storageFile =
          await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///VoiceCommandDefinitions.xml"));

     await VoiceCommandManager.InstallCommandSetsFromStorageFileAsync(storageFile);
}

Utilizamos el método GetFileFromApplicationUriAsync que nos permitirá acceder a recursos de la aplicación utilizando URIs “ms-appx://” o “ms-appdata://”. En este caso lo usamos para acceder al archivo xml del VCD. A continuación, utilizamos la clase estática VoiceCommandManager que nos permite acceder o instalar conjunto de definiciones de comandos de voz mediante archivo VCD. En este caso, procedemos a la instalación utilizando el método InstallCommandSetsFromStorageFileAsync.

NOTA: Para que la instalación de voces se pueda realizar debemos añadir el micrófono como una capacidad de la aplicación.

Añadir el Micrófono en las capacidades de la App

Añadir el Micrófono en las capacidades de la App

3º Gestionar la activación de comandos de voz

Cuando el usuario utilizando Cortana ejecuta una de nuestros comandos de voz en nuestra aplicación se lanza el evento OnActivated. Realmente este evento forma una parte importante del ciclo de vida de las aplicaciones Windows Phone. Sin embargo, en este caso, al lanzarse el evento por motivo de un comando de voz recibimos un parámetro de tipo VoiceCommand.

Gestión de la App desde comando de voz

Gestión de la App desde comando de voz

Tras una pequeña validación, navegaremos a la página principal pasando como parámetro la información recibida desde Cortana con el objeto de tipo SpeechRecognitionResult:

protected override void OnActivated(IActivatedEventArgs args)
{
     if (args.Kind == ActivationKind.VoiceCommand)
     {
          var voiceArgs = (IVoiceCommandActivatedEventArgs)args;
          var result = voiceArgs.Result;

          var frame = Window.Current.Content as Frame;
          frame.Navigate(typeof(SearchView), result);
          Window.Current.Content = frame;

          Window.Current.Activate();
      }

      base.OnActivated(args);
}

Con la información recibida desde Cortana, gestionaremos el contenido en una clase (la viewmodel de la página principal) y redigiremos al usuario a la vista correspondiente con la información esperada:

Gestión del comando de voz

Gestión del comando de voz

En la viewmodel de la vista a la que navegamos, en el método sobrescrito OnNavigatedTo, capturaremos el parámetro con la información enviada por Cortana, obtendremos el valor de la propiedad Text que indica el valor indicado por el usuario, en nuestro ejemplo el año del que desea ver la clasificación de pilotos y se lo pasamos a un método encargado de gestionar el comando de voz:

public override async System.Threading.Tasks.Task OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs args)
{
     var season = string.Empty;

     if (args.NavigationMode == NavigationMode.New)
     {
          var voiceResult = args.Parameter as SpeechRecognitionResult;

          if (voiceResult != null)
               season = voiceResult.Text;
          else
               season = args.Parameter.ToString();
     }

     if (!string.IsNullOrEmpty(season))
          await LoadStandingsData(season);
}

Hasta aqui todo preparado. Puedes descargar el ejemplo completo a continuación:

¿Probamos?

Comenzamos abriendo Cortana:

Entramos en Cortana

Entramos en Cortana

Podemos consultar que aplicaciones estan integradas y que podemos decir preguntando “What can i say?”:

Apps integradas con Cortana

Apps integradas con Cortana

Vemos que nuestra aplicación de ejemplo que nos permite realizar consultas sobre las clasificaciones de la formula 1 esta disponible. Si pulsamos sobre ella:

¿Que podemos decir?

¿Que podemos decir?

Vemos un ejemplo de lo que podemos consultar. Realizamos una consulta:

Ejecutando un comando de voz

Ejecutando un comando de voz

Cortana interpreta el comando, detecta nuestra aplicación, muestra un mensaje de feedback al usuario de lo que se esta realizando y lanza nuestra aplicación:

Ejecutando la acción!

Ejecutando la acción!

El resultado final:

Resultado final!

Resultado final!

Como hemos comentado logramos otro punto de entrada hacia nuestra aplicación asi como ofrecer una experiencia mucho más completa e integrada desde Cortana. Comparemos el resultado a la búsqueda sin existir nuestra aplicación:

Resultado sin nuestra App

Resultado sin nuestra App

En video:

Consideraciones en el diseño

En el proceso de integración de nuestra aplicación con Cortana debemos tener en cuenta una serie de consideraciones:

  • Usa comandos sencillos en los elementos <ListenFor> del archivo VCD.
  • Analiza con calma los comandos de voz. Se cuidadoso con el coste y beneficio de cada palabra.
  • Adapta la interfaz y la respuesta otorgada segun la petición del usuario.
  • Manten al usuario siempre informado de todo lo que esta ocurriendo.

Más información