[Windows Phone 8.1] Application Data APIs

Introducció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/”.

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

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

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

Introducció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.

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:

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.

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:

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:

Habilitamos la simulación de notificaciones y 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:

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

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

Más información

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

Introducció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:

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:

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:

¿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:

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 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"?>
  <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.

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.

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:

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:

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

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:

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

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:

El 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:

El 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

WPbeta.me. Nuevo servicio para gestión de betas de apps Windows Phone

Introducción

Como desarrolladores de aplicaciones Windows Phone debemos
esforzarnos por ofrecer aplicaciones atractivas y con funcionalidades
potentes y útiles para los usuarios. Sin embargo, debajo de todos esos
aspectos independientemente del tipo de aplicación que hagamos, debemos
ofrecer una experiencia fluida en todos los dispositivos con garantías
de calidad, es decir, sin errores (bugs). Para llegar a
este objetivo debemos cumplir múltiples pasos, desde la elección de una
buena arquitectura de la aplicación que nos permita crear una gran
cantidad de tests unitarios que cubran la mayoría de situaciones de la
aplicación y podamos cazar errores con rapidez a probar el resultado en
múltiples dispositivos para verificar rendimiento y otros aspectos. Dado
que no podemos tener todos los dispositivos disponibles en el mercado
(aunque nos gustaría), un movimiento recomendado a realizar son betas de nuestra aplicación.

Las betas nos permiten recibir feedback vital de rendimiento y
comportamiento de nuestra aplicación en distintos dispositivos asi como
feedback de usabilidad y otros bugs. Normalmente la gestión de betas las
realizamos con una beta privada para amigos y/o familiares o bien
buscamos testers en redes sociales u otros medios. En todos los casos la
situación es la misma. Necesitamos un correo (la cuenta Microsoft
utilizada por el usuario en su teléfono) por usuario.

¿Y como gestionamos todo esto?

Bien, el sistema no es único ni cerrado, podemos utilizar desde un
sencillo archivo de texto o un Excel hasta sistemas algo más complejos
pero más completos como por ejemplo MailChimp, sistema de gestión de
campañas de correo con el que mantener constancia de suscriptores,
campañas, etc.

NOTA: Tenéis disponible un gran artículo sobre la gestión de betas en el siguiente enlace al blog de Josué Yeray.

Hasta ahora…

WPbeta.me

Llega WPbeta.me, es un servicio totalmente gratuito creado por el MVP Domagoj Pavlesic destinado a facilitarnos la vida en la gestión de betas:

Como desarrolladores podemos utilizar WPbeta.me para facilitar todo el
proceso de gestión de betas. En el apartado App developers podremos
enviar nuestra aplicación para que pueda aparecer y ser testeado
sencillamente con su AppID:

NOTA: La aplicación debe estar publicada en la
Windows Phone Store como beta. El App ID podéis verlo en el Dashboard
del Windows Phone Dev Center, en los detalles de la aplicación.

Una vez enviada se obtendrán todos los metadatos de la aplicación creando una página de información como la siguiente:

La URL de la landing la podemos personalizar y mantenerla fácil para compartirla con amigos y/o familiares.

Como testers lo único que deben hacer es pulsar sobre el botón “Join this beta program” y autenticarse con su cuenta Microsoft. Tras esto ya tenemos disponible el email del tester en nuestro listado.

Un servicio recien sacado del horno aun en fase beta pero que ya
soluciona alguno de los problemas más importantes a los que nos podemos
enfrentar en la gestión de betas. Cualquier tipo de feedback, bug o
nueva característica se puede enviar con su formulario de contacto.
Asi que tanto si tenéis una aplicación de la que deseáis lanzar una
beta tanto si disfrutas ayudando a otros y probar a fondo aplicaciones,
tenéis disponible este nuevo servicio.

Más información

[Windows Phone 8.1] Transiciones entre páginas

Introducción

Ya hemos visto como se realiza la navegación entre
páginas en aplicaciones universales. Como desarrolladores, debemos
preocuparnos por múltiples factores a la hora de desarrollar la
aplicación. No debemos quedarnos solo en aportar una funcionalidad útil,
si no que además la experiencia sea sólida y compacta. Por ese motivo,
en el artículo de hoy veremos como añadir distintas transiciones entre
las páginas de la aplicación.

NavigationThemeTransitions

Al navegar a una página B desde una página A, la transición
especificada en la página B será la utilizada tanto al entrar como al
salir. Toda la sincronización será realizada automáticamente por el
framework.

Tipos de transiciones

Contamos con tres tipos de transiciones:

Vamos a crear un ejemplo muy sencillo donde ver todos los tipos de transiciones disponibles.

En la página principal añadimos un botón que nos permita navegar a una segunda página:

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

Añadimos varias páginas más (en nuestro ejemplo añadimos tres páginas
más, Pagina2, Pagina3 y Pagina4) que permitan la navegación entre ellas
mostrando distintas transiciones:

Añadimos el comando:

<Button Content="Volver"
        HorizontalAlignment="Center"
        Command="{Binding NavigateBackCommand}" />

Donde realizaremos la navegación a la siguiente página:

private ICommand _navigateCommand;
 
public ICommand NavigateCommand
{
     get { return _navigateCommand = _navigateCommand ?? new DelegateCommand(NavigateCommandDelegate); }
}
 
public void NavigateCommandDelegate()
{
     _navigationService.NavigateTo<Pagina2View>("Esto es un parámetro");
}

En las siguientes páginas, añadimos dos botones para navegar hacia delante y atrás:

<StackPanel VerticalAlignment="Center">
     <Button Content="Navegar a página 3"
             HorizontalAlignment="Center"
             Command="{Binding NavigateCommand}" />
     <Button Content="Volver"
             HorizontalAlignment="Center"
             Command="{Binding NavigateBackCommand}" />
</StackPanel>

En la viewmodel:

private ICommand _navigateBackCommand;
private ICommand _navigateCommand;
 
public ICommand NavigateBackCommand
{
     get { return _navigateBackCommand = _navigateBackCommand ?? new DelegateCommand(NavigateBackCommandDelegate); }
}
 
public ICommand NavigateCommand
{
     get { return _navigateCommand = _navigateCommand ?? new DelegateCommand(NavigateCommandDelegate); }
}
 
public void NavigateBackCommandDelegate()
{
     _navigationService.NavigateBack();
}
 
public void NavigateCommandDelegate()
{
     _navigationService.NavigateTo<Pagina3View>();
}

En la segunda página añadimos una transición de tipo SlideNavigationTransitionInfo:

<Page.Transitions>
        <TransitionCollection>
            <NavigationThemeTransition>
                <NavigationThemeTransition.DefaultNavigationTransitionInfo>
                    <SlideNavigationTransitionInfo />
                </NavigationThemeTransition.DefaultNavigationTransitionInfo>
            </NavigationThemeTransition>
        </TransitionCollection>
</Page.Transitions>

NavigationThemeTransition cuenta con una propiedad llamada DefaultNavigationTransitionInfo que especifica que transición usara la página.

En la tercera, CommonNavigationTransitionInfo (recordamos que es la aplicada por defecto):

<Page.Transitions>
        <TransitionCollection>
            <NavigationThemeTransition>
                <NavigationThemeTransition.DefaultNavigationTransitionInfo>
                    <CommonNavigationTransitionInfo IsStaggeringEnabled="True" />
                </NavigationThemeTransition.DefaultNavigationTransitionInfo>
            </NavigationThemeTransition>
        </TransitionCollection>
</Page.Transitions>

En la última página añadimos ContinuumNavigationTransitionInfo:

<Page.Transitions>
        <TransitionCollection>
            <NavigationThemeTransition>
                <NavigationThemeTransition.DefaultNavigationTransitionInfo>
                    <ContinuumNavigationTransitionInfo />
                </NavigationThemeTransition.DefaultNavigationTransitionInfo>
            </NavigationThemeTransition>
        </TransitionCollection>
</Page.Transitions>

El resultado final lo podéis ver a continuación:

NOTA: Haced clic sobre la imagen anterior para visualizar un gif animado del ejemplo.

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

Como hemos podido ver a lo largo de este artículo, tenemos una gran
flexibilidad a la hora de controlar las transiciones entre páginas
directamente con el SDK del sistema sin necesidad de frameworks
externos. Además, veremos en próximos artículos que podemos realizar
animaciones en partes del contenido de la pantalla y que contamos con
transiciones aplicadas a elementos en concreto que se lanzan con cambios
de estado (Theme Transitions).

Más información

[Windows Phone 8.1] La nueva StatusBar

Introducción

Desde la llegada de Windows Phone contábamos con una pequeña barra en
la parte superior, que nos otorgaba información como el nivel de
batería, conexión, hora, cobertura, etc. Esta barra se llamaba SystemTray y
en Windows Phone 8 podíamos personalizarla, ocultarla y mostrarla
además de ser un lugar en ocasiones bastante idóneo donde añadir un
indicador de progreso en nuestras aplicaciones. Ahora, con la llegada de
Windows Phone 8.1 la SystemTray es reemplazada por un nuevo control
llamado StatusBar. En este artículo vamos a analizar todas las posibilidades del control.

¿Te apuntas?

Manos a la obra

Comenzamos creando un nuevo proyecto:

Partimos de la plantilla Blank App para centrar nuestra atención en la StatusBar.La
StatusBar es un panel situado por defecto en la parte superior de la
pantalla de aplicaciones y juegos mostrando utilizando iconos
información relevante como la conexión WiFi, Bluetooth, batería, etc. En
caso de no necesitarla en nuestra aplicación o con el objetivo de
mejorar la apariencia podemos ocultarla, modificar el estilo e incluso
para retornar feedback en tareas de peso podremos mostrar un indicador
visual.

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

  • Mostrar y ocultar la StatusBar.
  • Mostrar y ocultar un indicador de progreso en la StatusBar.
  • Personalizar la StatusBar.

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.

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

<StackPanel Grid.Row="1" Margin="12" HorizontalAlignment="Center">
     <Button Content="Ocultar Status Bar" Width="250"/>
     <Button Content="Mostrar Status Bar" Width="250"/>
     <Button Content=" Modificar Color Status Bar" Width="250"/>
     <Button Content="Mostrar Progress" Width="250"/>
     <Button Content=" Ocultar Progress" Width="250"/>
</StackPanel>

Añadimos una colección de botones que nos van a permitir trabajar con
todas las opciones posibles del control StatusBar. El resultado es
simple:

Opciones con la StatusBar

En determinadas situaciones desearemos ocultar la StatusBar para
atraer la anteción del usuario en nuestra aplicación y ganar espacio.

¿Cómo la ocultamos?

Bien, añadimos un comando al primero de los botones de nuestra interfaz que nos permitirá ocultar la StatusBar:

<Button Content="Ocultar Status Bar" Width="250" Command="{Binding HideStatusBarCommand}"/>

Añadimos el comando correspondiente en la viewmodel:

private ICommand _hideStatusBarCommand;
 
public ICommand HideStatusBarCommand
{
     get { return _hideStatusBarCommand = _hideStatusBarCommand ?? new DelegateCommandAsync(HideStatusBarCommandDelegate); }
}
 
public async Task HideStatusBarCommandDelegate()
{
 
}

Al ejecutar el comando:

var statusBar = StatusBar.GetForCurrentView();
 
await statusBar.HideAsync();

Analicemos esas dos simples líneas. En la primera de ellas utilizamos el método GetForCurrentView.
Este método nos devolverá una instancia de la StatusBar actual de la
pantalla. En la segunda, una vez tenemos acceso a la StatusBar,
utilizamos el método asíncrono HideAsync para ocultarla. El resultado:

Una StatusBar oculta la podemos vovler a mostrar programáticamente. Nos
centramos en el segundo botón de nuestro ejemplo al que añadimos un
comando:

<Button Content="Mostrar Status Bar" Width="250" Command="{Binding ShowStatusBarCommand}" />

En la viewmodel:

private ICommand _showStatusBarCommand;
 
public ICommand ShowStatusBarCommand
{
     get { return _showStatusBarCommand = _showStatusBarCommand ?? new DelegateCommandAsync(ShowStatusBarCommandDelegate); }
}
 
public async Task ShowStatusBarCommandDelegate()
{
 
}

Al ejecutar el comando volveremos a utilizar el método GetForCurrentView para acceder a la StatusBar y utilizando el método asíncrono ShowAync volveremos a mostrarla:

var statusBar = StatusBar.GetForCurrentView();
 
await statusBar.ShowAsync();

El resultado:

Como hemos podido ver hasta ahora, los métodos asíncronos para
ocultar y mostrar la StatusBar reemplazan a la propiedad IsVisible de la
SystemTray. Además de poder ocultar y mostrar la StatusBar, podemos
personalizarla. Para ello contamos con una serie de propiedades para
modificar su apariencia:

NOTA: Tanto en el color de fondo como en el del texto, no se utiliza el canal alpha del color.

Añadimos un comando al botón que nos permitira modificar la apariencia de la StatusBar:

<Button Content=" Modificar Color Status Bar" Width="250" Command="{Binding ChangeColorCommand}" />

En la viewmodel:

private ICommand _changeColorCommand;
 
public ICommand ChangeColorCommand
{
     get { return _changeColorCommand = _changeColorCommand ?? new DelegateCommand(ChangeColorCommandDelegate); }
}
 
public void ChangeColorCommandDelegate()
{
 
}

Al ejecutar el comando modificaremos las propiedades BackgroundOpacity y BackgroundColor de la StatusBar tras acceder a ella utilizando el método GetForCurrentView.
Con respecto a la opacidad tenemos diferencias notorias con respecto al
funcionamiento de la SystemTray. En la SystemTray al utilizar un valor
de opacidad menor a 1 la página se desplazaba hacia arriba ocupando el
espacio. Actualmente no es asi, al usar un valor inferior a 1 la página
no se desplaza hacia arriba. Sin embargo, seguimos teniendo esta
funcionalidad gracias al método SetDesiredBoundsMode de la clase ApplicationView.

var statusBar = StatusBar.GetForCurrentView();
statusBar.BackgroundOpacity = 1;
statusBar.BackgroundColor = GetRandomColour();

NOTA: Para el color hemos utilizado un método personal que genera un color aleatorio:

var color = String.Format("#{0:X6}", _rand.Next(0x1000000));
 
return new SolidColorBrush(
            Color.FromArgb(
            255,
            Convert.ToByte(color.Substring(1, 2), 16),
            Convert.ToByte(color.Substring(3, 2), 16),
            Convert.ToByte(color.Substring(5, 2), 16)
            )
            ).Color;

El resultado:

Continuamos con un punto muy importante. La SystemTray en Windows Phone 8
nos permitía utilizar un indicador de progreso para trasmitir al
usuario que se estaba realizando alguna tarea costosa. Con la nueva StatusBar
podemos seguir realizando la misma acción. Añadimos un comando al
cuarto botón de nuestro ejemplo que nos permitirá mostrar un indicador
de progreso en la StatusBar:

<Button Content="Mostrar Progress" Width="250" Command="{Binding ShowProgressCommand}" />

En la viewmodel:

private ICommand _showProgressCommand;
 
public ICommand ShowProgressCommand
{
     get { return _showProgressCommand = _showProgressCommand ?? new DelegateCommandAsync(ShowProgressCommandDelegate); }
}
 
public async Task ShowProgressCommandDelegate()
{
 
}

Al ejecutar el comando:

var statusBar = StatusBar.GetForCurrentView();
 
await statusBar.ShowAsync();
 
await statusBar.ProgressIndicator.ShowAsync();

Accedemos a una propiedad llamada ProgressIndicator de tipo StatusBarProgressIndicator de la StatusBar y la mostramos utilizando el método ShowAsync. El resultado es el siguiente:

Al igual que podemos mostrar el indicador de progreso, podemos ocultarlo
programáticamente. Añadimos un comando para ello al último de los
botones de nuestro ejemplo:

<Button Content=" Ocultar Progress" Width="250" Command="{Binding HideProgressCommand}" />

En la viewmodel:

private ICommand _hideProgressCommand;
 
public ICommand HideProgressCommand
{
     get { return _hideProgressCommand = _hideProgressCommand ?? new DelegateCommandAsync(HideProgressCommandDelegate); }
}
 
public async Task HideProgressCommandDelegate()
{
 
}

Al ejecutar el comando utilizamos el método asíncrono HideAsync del ProgressIndicator de la StatusBar para ocultar el indicador:

var statusBar = StatusBar.GetForCurrentView();
 
await statusBar.ProgressIndicator.HideAsync();

NOTA: Algunas aplicaciones utilizan la propiedad Text del
ProgressIndicator de la StatusBar para mostrar algun tipo de indicación
o incluso para establecer el título de la aplicación.

Hasta aqui hemos visto los puntos principales de la nueva StatusBar. El resto de puntos son bastante similares al SystemTray.  Podéis descargar el ejemplo realizado a continuación:

Más información

[Universal Apps] Uso del Zoom Semantico, creando Jumplists

Introducción

Estas envuelto en el desarrollo de una aplicación universal y quieres
utilizar un control jumplist, un listado organizado en grupos que
además nos permiten acceder rapidamente a elementos de otros grupos. Si
ya has desarrollado previamente para Windows Phone habrás intentando
utilizar un control LongListSelector pero… ¿que
ocurre?, ¿dónde se encuentra?. Las aplicaciones universales tanto
Windows como Windows Phone funcionan bajo WinRT donde no tenemos
disponible el control. Pero esperad, tranquilos, con la unificación de
las plataformas el control LongListSelector de Windows Phone 8 pasa a
ser reemplazado por el zoom semántico que ya teníamos en las
aplicaciones Windows Store. En este artículo vamos a crear un proyecto
universal desde cero y a utilizar en una aplicación el control SemanticZoom para ver como reemplaza al LongListSelector en Windows Phone y su uso en Windows Store.

¿Te apuntas?

¿Qué es el Zoom Semántico?

En zoom semántico es la forma que podemos utilizar en nuestras
aplicaciones universales a la hora de presentar conjuntos grandes de
datos o datos relacionados entre si en una única vista. El control SemanticZoom nos permite mostrar la información al usuario en dos vistas distintas del mismo contenido.

Para hacer zoom semántico, podemos hacerlo mediante un simple gesto
(acercando ambos dedos alejamos el zoom, alejando los dedos lo
acercamos). Además en Windows podemos pulsar la tecla CTRL del teclado mientras hacemos scroll con el ratón (o pulsamos las teclas + o -).

Veamos un simple ejemplo para dejarlo todo más claro. Imaginaos que
estamos desarrollando una aplicación de mensajería. Estamos viendo el
historial de mensajes que se ha mantenido con un usuario en concreto.
Veremos la vista reducida o alejada:

Si queremos tener de un simple vistazo todos los periodos de fechas en
los que hemos mantenido mensajes con el usuario bastará con hacer hacer
el gesto de acercar los dedos para mostrar la vista ampliada (tras una
pequeña animación):

Como podéis ver en el ejemplo, sin necesidad de hacer scroll podemos
mostrar de una simple vistazo la información deseada. Es más, tenemos la
posibilidad de añadir información extra. En nuestro ejemplo además de
mostrar los distintos periodos en los que se han mantenido
conversaciones, se muestra el número de mensajes que se han realizados
en cada uno de ellos.

Manos a la obra!

Vamos a crear una aplicación simple que muestre una colección de
películas agrupadas por año. El objetivo final será añadir el uso del SemanticZoom
para mostrar por defecto la colección de películas y al hacer el zoom
out mostrar los distintos años en los que están agrupadas las películas.

Como siempre solemos hacer vamos a realizar un ejemplo lo más simple
posible pero que nos sea válida para lograr nuestros objetivos:

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 probar el control necesitamos una fuente de información. En
nuestro ejemplo simplificaremos el proceso al máximo construyendo una
colección de elementos en memoria.

Comenzamos creando el modelo de nuestra aplicación:

public class Movie
{
     public string Title { get; set; }
 
     public string Description { get; set; }
 
     public string Image { get; set; }
 
     public int Year { get; set; }
}

Para simplificar el ejemplo, no vamos a conectar nuestra aplicación con
internet (rss, servicio web, azure, etc) sino que trabajaremos con datos
estáticos. Para ello, en el constructor de nuestra viewmodel creamos un listado de películas:

var movies = new List<Movie>
{    
     new Movie {Title = "John Carter", Year = 2012, Image = "ms-appx:///Assets/Carter.jpg"},
     new Movie {Title = "El caballero oscuro: La leyenda renace", Year = 2012, Image = "ms-appx:///Assets/Batman.jpg"},
     new Movie {Title = "Cisne Negro", Year = 2011, Image = "ms-appx:///Assets/Cisne.jpg"},
     new Movie {Title = "Drive", Year = 2011, Image = "ms-appx:///Assets/Drive.jpg"},
     new Movie {Title = "Toy Story 3", Year = 2010, Image = "ms-appx:///Assets/Toy.jpg"},
     new Movie {Title = "El discurso del rey", Year = 2010, Image = "ms-appx:///Assets/Rey.jpg"},
     new Movie {Title = "Origen", Year = 2010, Image = "ms-appx:///Assets/Origen.jpg"},    
     new Movie {Title = "Avatar", Year = 2009, Image = "ms-appx:///Assets/Avatar.jpg"}
};

NOTA: Las imágenes utilizadas son recursos locales compartidos en una carpeta llamada Assets del proyecto Shared.

Llegamos a este punto podríamos crear una propiedad pública para
poder hacer binding (enlazar) desde nuestra interfaz con la colección
creada. Sin embargo, dentro de nuestros objetivos está el mostrar la
colección de películas agrupadas por año. Además, con el control
SemanticZoom queremos mostrar el listado de grupos (años) distintos que
contienen películas. Debemos agrupar nuestra colección antes de
continuar. Para ello, dentro de nuestra carpeta “Models” anteriormente
creada vamos a añadir una nueva clase:

public class MoviesByYear
{
     public int Year { get; set; }
    public List<Movie> Movies { get; set; }
}

Es simple. Almacenará un año junto a la colección de películas de dicho
año correspondientes. Tenemos ya donde almacenar la información, vamos a
hacerlo.

var moviesByYear = movies.GroupBy(f => f.Year).Select(f => new MoviesByYear { Year = f.Key, Movies = f.ToList() });

Ahora si, creamos una propiedad pública que contendrá la colección de películas agrupadas por año:

public List<MoviesByYear> Movies
{
     get { return _movies; }
     set { _movies = value; }
}

Hemos dejado el view model totalmente preparado. Llega el momento de centrarnos en la vista. Vamos a añadir nuestro control SemanticZoom a nuestra vista MainView:

<SemanticZoom>
     <SemanticZoom.ZoomedInView>
 
     </SemanticZoom.ZoomedInView>
     <SemanticZoom.ZoomedOutView>
 
     </SemanticZoom.ZoomedOutView>
</SemanticZoom>

La estructura que puedes ver en la parte superior es la estructura
básica a utilizar en un control SemanticZoom. Como puedes observar el
control contendrá otros dos controles (normalmente listas). El primero
de los controles se añadirá en la vista ZoomedOutview, vista mostrada por defecto. El segundo, se añadirá a la vista ZoomedInView. Vista que mostramos al hacer Zoom Semántico. Ambas vistas están relacionadas semánticamente.

Antes de continuar, veamos algunos miembros importantes del control:

Propiedades

  • ZoomedInView. Vista Zoomed-In del control SemanticZoom.
  • ZoomedOutView. Vista Zoomed-Out del control SemanticZoom.
  • CanChangesView. Determina si el control permite cambiar de vista (Propiedad de sólo lectura).
  • IsZoomedInViewActive. Propiedad de tipo bool nos permite definir con que vista se muestra el control SemanticZoom (ZoomIn o ZoomOut).

Eventos

  • ViewChangeStarted. Se lanza al comenzar el cambio de una vista.
  • ViewChangedCompleted. Se lanza al completarse el cambio de una vista (la nueva vista se muestra).
<SemanticZoom>
     <SemanticZoom.ZoomedInView>
          <ListView>
          </ListView>
     </SemanticZoom.ZoomedInView>
     <SemanticZoom.ZoomedOutView>
          <GridView />
     </SemanticZoom.ZoomedOutView>
</SemanticZoom>

Añadiremos en los recursos de la página un CollectionViewSource que hará binding con nuestra propiedad pública Movies creada en la viewmodel:

<CollectionViewSource
            x:Name="groupedMoviesViewSource"
            Source="{Binding Movies}"
            IsSourceGrouped="true"
            ItemsPath="Movies"/>

Pasamos a definir los DataTemplates:

<DataTemplate x:Key="MovieJumpTemplate">
     <Border Padding="5">
          <Border Background="{Binding Converter={StaticResource BackgroundConverter}}"
                  Width="82" Height="82" HorizontalAlignment="Left">
                <TextBlock Text="{Binding Group.Year}"
                           Foreground="{Binding Converter={StaticResource ForegroundConverter}}"
                           FontSize="24" Padding="6"
                           HorizontalAlignment="Center" VerticalAlignment="Center"/>
          </Border>
     </Border>
</DataTemplate>
 
<DataTemplate x:Key="MovieItemTemplate">
     <Grid>
          <Grid.ColumnDefinitions>
               <ColumnDefinition Width="Auto"/>
               <ColumnDefinition Width="*"/>
          </Grid.ColumnDefinitions>
          <Image Source="{Binding Image}" Stretch="Uniform" MaxWidth="100"
                 Margin="0, 5"/>
          <TextBlock Grid.Column="1" FontSize="32"
                     Text="{Binding Title}" TextWrapping="WrapWholeWords"
                     Margin="5" />
     </Grid>
</DataTemplate>
 
<DataTemplate x:Key="MovieGroupHeaderTemplate">
     <Border Background="Transparent" Padding="5">
          <TextBlock Text="{Binding Year}" Foreground="{StaticResource PhoneForegroundBrush}" FontSize="32" Padding="6"
                       FontFamily="{StaticResource PhoneFontFamilySemiLight}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
     </Border>
</DataTemplate>

Necesitamos varias plantillas, en nuestro ejemplo:

  • MovieJumpTemplate: Cada uno de los elementos mostrados en la vista ZoomedOutView del zoom semántico.
  • MovieItemTemplate: Elementos básicos del listado mostrados en la vista por defecto.
  • MovieGroupHeaderTemplate: Cabecera de cada grupo mostrado en la lista por defecto.

NOTA: Creamos una carpeta Themes específica para
clada plataforma, en los proyectos Windows y Windows Phone. Aqui
añadiremos recursos de diccionarios donde agruparemos los estilos y
plantillas específicar a usar en cada plataforma.

Finalmente nuestro zoom semántico quedaría:

<SemanticZoom>
     <SemanticZoom.ZoomedInView>
          <ListView
               IsHoldingEnabled="True"
               ItemsSource="{Binding Source={StaticResource groupedMoviesViewSource}}"
               ItemTemplate="{StaticResource MovieItemTemplate}">
               <ListView.GroupStyle>
                    <GroupStyle HidesIfEmpty="True"
                                HeaderTemplate="{StaticResource MovieGroupHeaderTemplate}">
                    </GroupStyle>
               </ListView.GroupStyle>
          </ListView>
     </SemanticZoom.ZoomedInView>
     <SemanticZoom.ZoomedOutView>
          <GridView ItemsSource="{Binding Source={StaticResource groupedMoviesViewSource}, Path=CollectionGroups}"
                    ItemTemplate="{StaticResource MovieJumpTemplate}"/>
     </SemanticZoom.ZoomedOutView>
</SemanticZoom>

Analicemos el código anterior:

  • Ya hemos visto el funcionamiento del zoom semántico además de conocer sus dos vistas, ZoomedInView y ZoomedOuView.
  • En la vista por defecto mostramos un control ListView. Este control
    nos permite organizar los elementos en grupos utilizando la propiedad GroupStyle. La apariencia de cada cabecera vendrá definido por la plantilla indicada en la propiedad HeaderTemplate y podemos ocultar cabeceras sin contenido gracias a la propiedad HidesIfEmpty.
  • El JumpList lo conseguimos utilizando el ListView con sus grupos junto al zoom semántico.
  • En la vista ZoomOutView mostramos un GridView bindeado a la misma fuente de información que el ListView, el CollectionViewSource pero accediendo a la propiedad CollectionGroups de la vista que contiene la colección de grupos.

El resultado del ejemplo es el siguiente:

Podemos ver el listado de películas agrupadas por año (vista ZoomInView), mostrando el año como cabecera. Al pulsar sobre cualquiera de los años:

Pasamos a la vista ZoomOutView viendo todos los años disponibles. La aplicación Windows Store mostrará un resultado similar:

Mediante un simple gesto (acercando ambos dedos alejamos el zoom, alejando los dedos lo acercamos) o pulsando la tecla CTRL del teclado mientras hacemos scroll con el ratón (o pulsamos las teclas + o -) pasamos a la vista ZoomOutView:

En la vista ZoomOutView vemos el listado de años mostrando además información extra, en este caso el número de películas disponibles en cada año.

Puedes descargar el ejemplo realizado:

Espero que lo visto en la entrada os resulte interesante. Recordar,
cualquier duda o sugerencia será bienvenida en los comentarios de la
entrada.

Conclusiones

  • El control SemanticZoom forma parte del conjunto de controles WinRT compartido entre Windows y Windows Phone.
  • A pesar de tener el control compartido entre ambas plataformas, en
    cada una de ellas se adapta para otorgar la experiencia adecuada.
  • El control nos permite trabajar con dos vistas
    distintas a la hora de mostrar colecciones con un número elevado de
    elementos. Evita scrolls muy largos, facilita la navegación e
    interacción entre los elementos.
  • ZoomedInView es la vista por defecto que muestra el Zoom Semántico.
  • ZoomedOutView es la vista mostrada al hacer Zoom Semántico.
  • Para poder utilizar un control en alguna de las vistas del SemanticZoom debe implementar la interfaz ISemanticZoomInfo.
  • Por defecto los controles que heredan de ListViewBase implementan la interfaz ISemanticZoomInfo.
  • CUIDADO: Con el uso del control Listbox que no hereda de
    ListViewBase y por lo tanto NO implementa ISemanticZoomInfo. No se puede
    utilizar un control Listbox en una de las vistas del control
    SemanticZoom.
  • Normalmente se muestra la colección de elementos en la vista
    ZoomedInView y los grupos de dicha colección en la vista ZoomedOutView.
    Por ello, también es normal que la fuente de datos del control sea un
    CollectionViewSource. Aunque no tiene porque ser siempre asi.
  • No tiene una propiedad ItemsSource. No hacemos Binding directamente a
    una colección de datos. El Binding se realiza desde los controles lista
    añadidos a cada una de las vistas del SemanticZoom.
  • Por supuesto, el contenido de las distintas vistas pueden hacer scroll.
  • No esta pensado para mostrar una colección y al pulsar sobre un
    elemento de la misma navegar a otra colección de detalles (Drill-Down).
    La información mostrada en ambas vistas es la misma.

Artículos relacionados

Más información

[Tips and Tricks] Detectar si una App Universal pasa a segundo plano

Introducción

Las aplicaciones universales corren bajo un ciclo de vida
de modo que si el usuario cambia (switch) nuestra aplicación por otra,
en un plazo de 10 segundos, el sistema pasará nuestra aplicación al
estado de suspensión notificando a la misma vía el evento Suspending. Si el usuario cambia de nuevo a nuestra aplicación, el sistema «despierta» a la misma siendo notificada por el evento Resuming.
Mientras la aplicación esta suspendida el sistema puede poner fin a la
misma por necesidad de recursos. En este caso, la aplicación lanzará el
evento Activated la próxima vez que el usuario la ejecute.

Estos eventos entre otros nos permiten controlar en la mayoría de
situaciones a la perfección el ciclo de vida de las aplicaciones. Sin
embargo, en ocasiones esto no es suficiente. A veces, necesitamos ser
notificados de manera inmediata cuando nuestra
aplicación pasa a segundo plano y no podemos confiar en el evento de
suspensión ya que se lanzará 10 segundos despues de que otra aplicación
pase a primer plano ocupando la pantalla. Por ejemplo, en el caso de un
juego, se debe hacer una pausa inmediatamente tras pasar a segundo
plano. En caso contrario sería fatal que pasasen aunque sean unos
segundos sin control sobre el mismo!

¿Que podemos hacer?

Detectar el paso a segundo plano de manera inmediata

Para detectar el paso inmediato a segundo plano podemos utilizar el evento VisibilityChanged de la ventana de la aplicación. Para ello, en el evento OnLaunched del archivo App.xaml.cs tras asignar el Frame a utilizar como contenido de la ventana:

Window.Current.Content = rootFrame;

Nos suscribimos al evento VisibilityChanged de la ventana de la aplicación:

Window.Current.VisibilityChanged += Current_VisibilityChanged;

El evento anterior se lanzará tanto cuando la ventana de la aplicación
pasa a segundo plano como cuando pasa a primer plano. Lo podremos
detectar con facilidad gracias al argumento Windows.UI.Core.VisibilityChangedEventArgs:

void Current_VisibilityChanged(object sender, Windows.UI.Core.VisibilityChangedEventArgs e)
{
     if (e.Visible)
     {
          //  App en primer plano
     }
     else
     {
          //  App en segundo plano
     }
}

El argumento VisibilityChangedEventArgs cuenta con una propiedad
booleana que nos indica si esta visible o no, de modo que podemos
diferenciar facilmente si estamos en primer plano o en segundo plano.

Fácil, ¿verdad?. Puedes descargar un pequeño ejemplo con el código anterior a continuación:


Más información

[Evento CartujaDotNet] Material de "Introducción a las aplicaciones universales" en el Sevilla Mobility Day

El evento

El pasado sábado 05 de Julio tenía lugar el Sevilla Mobility Day en el Cloud Pointing
de Microsoft situado en el Parque Empresarial Nuevo Torneo.  Un evento
con múltiples charlas relacionadas con el desarrollo móvil de CartujaDotNet,
grupo de usuarios .NET de Sevilla. Agradecer la colaboración de
Guillermo Guerrero de Microsoft por hacer que todo sea tán fácil. Con
todas las reservas agotadas, al llegar teníamos lleno absoluto:

Agradecer de entrada la gran acogida y asistencia de todos. Comenzamos
el evento con una rápida presentación donde se mostraba la agenda del
día, se presentaban a los ponentes, etc. A continuación, era mi turno
con la primera sesión técnica hablando sobre aplicaciones universales:

Comenzamos repasando la situación en el desarrollo Windows &
Windows Phone antes de la llegada de las aplicaciones universales asi
como las opciones disponibles para compartir la mayor cantidad de código
posible entre ambas plataformas. Continuamos introduciendo las
aplicaciones universales llegando de el concepto a la creación de una
aplicación. En la creación de la aplicación nos esforzamos para
compartir la mayor cantidad posible de código y elementos comunes del
proyecto centrados en el proyecto Shared. Y terminamos compartiendo XAML en el proyecto Shared en nuestra aplicación de ejemplo.

Tras la primera sesión, realizamos una pequeña parada para reponer fuerzas y volver con otra sesión de la mano de Josué Yeray. En este caso, continuábamos desarrollando para Windows y Windows Phone analizando las posibilidades de los Behaviors, Animaciones y Visual States:

Con un demo comenzada desde cero vimos que son y para que sirven los Behaviors
con algunos casos prácticos útiles como por ejemplo, realizar la
navegación entre páginas. Continuamos recalcando la importancia de la
velocidad percibida contra la velocidad real de las aplicaciones,
introduciendo las animaciones. Vimos los tipos disponibles y algun
ejemplo. Terminamos con un ejemplo de una aplicación universal
fotográfica donde con el uso de Visual States se controlaba la disposición de lsa fotos segun la orientación del dispositivo.

Tras otros minutos de descanso llegaba el turno de desarrollo de videojuegos multiplataforma con WaveEngine de la mano de Juan María Lao:

Tras una introducción a WaveEngine vimos las capacidades y desarrollo
de videojuegos 3D. Juan María realizo un ejemplo desde cero donde nos
mostraba los métodos update y draw del juego, el trabajo con componentes
cargando una tetera en 3D. Continuamos viendo las posibilidades 2D del
engine. Nos centramos en un juego disponibles en las stores estilo
Flappy Bird bastante interesante. Aqui vimos la gestión de recursos con
la herramienta Asset Exporter y como convertir el proyecto a cada una de
las plataformas soportadas (Windows Phone, Windows Store, Android, iOS,
Ouya, Mac OSX, etc.).

Para terminar el evento contábamos con una sesión sobre desarrollo de aplicaciones multiplataforma con Xamarin de la mano de Juan Cano y Marcos Cobeña:

De entrada, plantearon un dilema interesante, ante una aplicación multiplataforma con Xamarin, ¿que usamos?, ¿MVVMCross o Xamarin.Forms?.
Con un ejemplo maestro-detalle del evento donde se mostraban el listado
de ponentes y al pulsarlos en el detalle la información detallada de
cada sesión fuimos viendo como se realiza el desarrollo de la aplicación
tanto en MVVMCross como en Xamarin.Forms, detalles pros y contras. Al
final de la charla, vimos una tabla comparativa con pros y contras y
recomendaciones de cuando usar una opción u otra.

Con esta última sesión llegamos al final del evento. Un evento
bastante completo con múltiples sesiones técnicas de un nivel muy alto y
con una asistencia genial. Agradecer a todos los asistentes, a todos
los ponentes y a patrocinadores por hacer posible el evento:

Sin duda, volveremos a tener más eventos de esta índole tras el parón veraniego!

El material

En mi caso, tuve el placer de poder “arrancar” el evento con una charla sobre aplicaciones universales.

Tenéis disponible la presentación utilizada a continuación:

En el ejemplo realizado el objetivo era crear una aplicación universal “real”
(todo lo real que se puede hacer en el tiempo disponible) que conectase
con un servicio para obtener las clasificaciones tanto de escuderías
como de pilotos de la Formula 1. Los puntos importantes del ejemplo
eran:

  • Como estructurar el proyecto y como implementar el patrón MVVM.
  • Como utilizar inyección de dependencias.
  • Ver como compartir la mayor cantidad de código y recursos posibles.
  • Ver los controles comunes y como compartir XAML.

La aplicación quedo como podéis ver a continuación:

Podéis descargar este ejemplo a continuación:

A parte de este ejemplo, vimos otros pequeños como el uso de compilación condicional en XAML o el uso de VisualStates para gestionar vistas compartidas en aplicaciones universales. Lo tenéis tambien disponible:

Más información

[Tips and Tricks] Enviar un email con adjuntos desde Windows Phone 8.1

Introducción

En nuestras aplicaciones necesitamos en determinadas ocasiones enviar
emails. Ya sea desde una sección de contacto o a enviar un email en una
determinada acción de nuestra aplicación. En Windows Phone 8.0 teníamos
ciertas limitaciones que ahora han sido paliadas, como enviar adjuntos.
En este Tips and Tricks vamos a ver como se envían correos en Windows
Phone 8.1 con adjuntos.

¿Te apuntas?

EmailComposeTask

En Windows Phone 8.0 utilizábamos el launcher EmailComposeTask
para enviar emails desde nuestras aplicaciones.  Era una manera muy
fácil y sencilla de enviar emails además, utilizando launchers,
conseguíamos una manera consistente con el resto de la experiencia
otorgada por el sistema.

Para poder utilizar el launcher, lo primero que debemos hacer es añadir la siguiente referencia:

using Microsoft.Phone.Tasks;

Una vez añadida, podemos enviar un email con facilidad:

var emailComposeTask = new EmailComposeTask();
 
emailComposeTask.Subject = "Asunto";
emailComposeTask.Body = "Mensaje";
emailComposeTask.To = "to@example.com";
emailComposeTask.Cc = "cc@example.com";
emailComposeTask.Bcc = "bcc@example.com";
 
emailComposeTask.Show();

Creamos un objeto de tipo EmailComposeTask, rellenamos la información
necesaria (destinatario, asunto, mensaje, etc) y llamamos al método
Show.

NOTA: Recordar que como otros launchers, en el emulador no funcionará, necesitaremos un dispositivo real para probar el envio.

Email con adjuntos

A pesar de la sencillez, el launcher EmailComposeTask cuenta con
ciertas limitaciones de peso. Una de las más sonadas es que no permite
enviar adjuntos.

En Windows Phone 8.1 podemos enviar emails con adjuntos utilizando las clases EmailMessage y EmailManager.

La clase EmailMessage nos permite definir el email a enviar, desde el destinatario y el asunto, hasta los ficheros adjuntos a enviar. Cuenta con las siguientes propiedades:

  • To: Destinatario del email.
  • Subject: Asunto del email.
  • Cc: Destinatarios CC del email
  • Bcc: Destinatarios BCC del email.
  • Body: Cuerpo del email.
  • Attachments: Adjuntos del email. Colección de tipo EmailAttachment.

EmailManager proporciona el método ShowComposeNewEmailAsync que
acepta como parámetro un objeto de tipo EmailMessage que abre la vista
de la App de email con todos los datos del mismo rellenos permitiendo al
usuario enviar el email.

El uso es sencillo. Crearemos un servicio de envio de emails para
inyectar en nuestra viewmodel y nos permita realizar las acciones
necesarias con el correo. Tendremos en la definición del mismo un método
como el siguiente:

Task Send(string recipient, string subject, EmailAttachment attachment);

Nos permite enviar un email indicando destinatario, asunto y mensaje.

NOTA: En este ejemplo, hemos realizado el caso
más simple al enviar correo, podemos tener varias sobrecargas del mismo
método aceptando más parámetros.

La definición del método es la siguiente:

public async Task Send(string recipient, string subject, EmailAttachment attachment)
{
     var email = new EmailMessage();
     email.To.Add(new EmailRecipient(recipient));
     email.Subject = subject;
     email.Attachments.Add(attachment);
 
     await EmailManager.ShowComposeNewEmailAsync(email);
}

Creamos un objeto EmailMessage con las propiedades básicas y llamamos
el método ShowComposeNewEmailAsync de la clase EmailManager.

El archivo adjunto sera un pequeño archivo de texto:

private static async Task<StorageFile> CreateFile()
{
     var localFolder = ApplicationData.Current.LocalFolder;
     var file = await localFolder.CreateFileAsync("file.txt", CreationCollisionOption.ReplaceExisting);
     await FileIO.WriteTextAsync(file, "Ejemplo Email Adjuntos");
 
     return file;
}

Al enviar el email, crearemos el fichero, y llamaremos al método Send de nuestro propio servicio:

var attachment = new EmailAttachment("file", await CreateFile());
await _emailService.Send("test@correo.es", "Ejemplo envio email con adjuntos", attachment);

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

Más información