[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

MVP Windows Platform Development

Microsoft MVP 2014

Microsoft me ha sorprendido con un correo que empezaba:

Estimado/a Javier Suarez,

Enhorabuena. Nos complace presentarle el programa de nombramiento MVP de Microsoft® de 2014.

Comunicándome que paso a ser MVP de Windows Platform Development.
Para mi es un gran honor entrar en este grupo y en esta categoría
(nueva) donde tengo a tantos referentes del que espero ser merecedor.
Continuaré intentando aprender lo máximo posible para contribuir con la
comunidad con eventos, artículos técnicos y ofrecer toda la ayuda que
este en mi mano.

Agradecimientos

Quisiera agradecer a
muchas partes que me han ayudado en este camino. Comienzo por vosotros,
si, si hablo de ti querido lector. Gracias por leer estos artículos
técnicos y ayudarme a aprender intercambiando opiniones día a día.
Gracias a todos mis compañeros en Icinetic donde
puedo “luchar” todos los días con desarrollo en Windows y Windows
Phone, además de contar con todo su apoyo, a mi compañero y también MVP
en Windows Platform Development, Josué Yeray por ser una referencia y ayudarme siempre que lo he necesitado, a los grupos de usuario WPSUG y CartujaDotNet donde colaboro y me permiten aprender más y más cada día con fantásticos compañeros, agradecer también a Braulio Diez por su apoyo desde el inicio, al gran Jose María Aguilar por su ayuda en todo momento, además de dejarme meter las “manazas” en ejemplos de su último libro y a Cristina González,
MVP lead, por su labor además de a los chicos de Microsoft España. Podría continuar nombrando a muchos amigos y
compañeros, pero me temo que me extendería en exceso y al final y al
cabo sabéis quienes sois 😉

Por último, no quisiera terminar sin
agradecer a mi familia y sobretodo a mi pareja que siempre me apoya y me
cede tiempo para que pruebe esa última versión de algun remoto SDK que acaba de salir o de participar en X evento, en definitiva,  probar, jugar con la tecnología y divertirme.

Ahora,
a continuar aprendiendo y disfrutando compartiendo todo lo posible en
blogs, comunidades técnicas y otras vías con más ganas y más ilusión.

¿Me acompañas?

Felicidades

También
me gustaría felicitar a todos los MVPs veteranos que han sido renovados
asi como aquellos que como yo entran a formar parte desde este momento.
Enhorabuena!