WinRT. Zoom Semántico.

Introducción. ¿Qué es el Zoom Semántico?

En zoom semántico es la forma que podemos utilizar en nuestras aplicaciones Windows Store 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) o pulsando 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:

Vista reducida

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

Vista ampliada

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.

Interesante, ¿verdad?.

Manos a la obra.

Como siempre solemos hacer vamos a realizar un ejemplo lo más simple posible pero que nos sea válida para lograr nuestros objetivos. La plantilla selecciona para realizar el ejemplo lo más simple posible será “Blank Application”.

Blank Application

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 semántico mostrar los distintos años en los que están agrupadas las películas.

Comenzamos creando el modelo de nuestra aplicación. Para ello, tras crear una carpeta llamada “Models” creamos dentro la siguiente clase:

public class Film
{
     public int FilmID { get; set; }
     public string Title { get; set; }
     public int Year { get; set; }
     public string Image { get; set; }
}

Bien, tras crear el modelo nuestra vista debe ser abastecida adecuadamente por un view model. Creamos una carpeta llamada “ViewModels” y dentro creamos una clase “FilmViewModel.cs”:

public class FilmViewModel
{

}

Añadimos el constructor:

#region Constructor

public FilmViewModel()
{

}

#endregion

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 que acabamos de añadir creamos un listado de películas:

var films = new List<Film>
{
     new Film {FilmID = 1, Title = "El caballero oscuro: La leyenda renace", Year = 2012, Image = "http://ia.media-imdb.com/images/M/MV5BMTk4ODQzNDY3Ml5BMl5BanBnXkFtZTcwODA0NTM4Nw@@._V1._SY317_.jpg"},
     new Film {FilmID = 2, Title = "Cisne Negro", Year = 2011, Image = "http://ia.media-imdb.com/images/M/MV5BNzY2NzI4OTE5MF5BMl5BanBnXkFtZTcwMjMyNDY4Mw@@._V1._SY317_.jpg"},
     new Film {FilmID = 3, Title = "Drive", Year = 2011, Image = "http://ia.media-imdb.com/images/M/MV5BOTM1ODQ0Nzc4NF5BMl5BanBnXkFtZTcwMTM0MjQyNg@@._V1._SY317_.jpg"},
     new Film {FilmID = 4, Title = "Toy Story 3", Year = 2010, Image = "http://ia.media-imdb.com/images/M/MV5BMTgxOTY4Mjc0MF5BMl5BanBnXkFtZTcwNTA4MDQyMw@@._V1._SY317_CR5,0,214,317_.jpg"},
     new Film {FilmID = 5, Title = "El discurso del rey", Year = 2010, Image = "http://ia.media-imdb.com/images/M/MV5BMzU5MjEwMTg2Nl5BMl5BanBnXkFtZTcwNzM3MTYxNA@@._V1._SY317_CR1,0,214,317_.jpg"},
     new Film {FilmID = 6, Title = "Origen", Year = 2010, Image = "http://ia.media-imdb.com/images/M/MV5BMjAxMzY3NjcxNF5BMl5BanBnXkFtZTcwNTI5OTM0Mw@@._V1._SY317_.jpg"},
};

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 FilmYear
{
     public int Year { get; set; }
     public List<Film> Films { 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. LINQ nos facilitará mucho la tarea. Añadimos al view model el namespace correspondiente:

using System.Linq;

Agrupamos la colección de películas por año:

var filmsByYear = films.GroupBy(f => f.Year).Select(f => new FilmYear { Year = f.Key, Films = f.ToList() });

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

#region Properties

public List<FilmYear> Films { get; set; }

#endregion

Realizamos la asignación correspondiente:

Films = filmsByYear.ToList();

Hemos dejado el view model totalmente preparado. Llega el momento de centrarnos en la vista. La página MainPage ha sido renombrada a FilmView. Nos centramos en el code behind de la vista para conectarla con su correspondiente view model:

private FilmViewModel _viewModel;

public FilmView()
{
     this.InitializeComponent();

     _viewModel = new FilmViewModel();
     this.DataContext = _viewModel;
}

Sencillo. Se ha creado una instancia del view model y se ha asignado como data context de la página. Tenemos el view model definido y conectado con la vista. Centremonos en definir la vista. Vayamos con el XAML:

<Page.Resources>
</Page.Resources>

Añadiremos en los recursos de la página un CollecyionViewSource que hará binding con nuestra propiedad pública Films creada en el view model:

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

Vamos a definir un GridView que mostrará la colección de películas agrupadas por año:

<GridView Margin="116,0,40,46" ItemsSource="{Binding Source={StaticResource groupedFilmsViewSource}}" VerticalAlignment="Center">
     <GridView.ItemsPanel>
          <ItemsPanelTemplate>
               <VirtualizingStackPanel Orientation="Horizontal"/>
          </ItemsPanelTemplate>
     </GridView.ItemsPanel>
          <GridView.GroupStyle>
               <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                         <DataTemplate>
                              <Grid Margin="1,0,0,6">
                                   <Button Content="{Binding Year}" Style="{StaticResource TextButtonStyle}"/>
                              </Grid>
                         </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                    <GroupStyle.Panel>
                    <ItemsPanelTemplate>
                         <VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"/>
                    </ItemsPanelTemplate>
               </GroupStyle.Panel>
          </GroupStyle>
     </GridView.GroupStyle>
</GridView>

NOTA: La propiedad ItemsSource del GridView hace binding al CollectionViewSource creado anteriormente en los recursos de la página.

Debemos definir el DataTemplate correspondiente a cada elemento del GridView (apariencia visual de cada elemento):

<DataTemplate x:Key="ZoomedOutFilmItemTemplate">
     <Grid HorizontalAlignment="Left" Width="180" Height="250">
          <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
               <Image Source="{Binding Image}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
          </Border>
          <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
               <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/>
               <TextBlock Text="{Binding Year}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
          </StackPanel>
     </Grid>
</DataTemplate>

Añadimos la siguiente propiedad al GridView:

ItemTemplate="{StaticResource ZoomedOutFilmItemTemplate}"

Llegamos a este punto podemos compilar y verificar que todo esta correctamente. Pero OJO no pierdas el tiempo intentando hacer zoom semántico aun no hemos ni añadido el control SemanticZoom. Vamos a añadir nuestro control SemanticZoom:

<SemanticZoom>
     <SemanticZoom.ZoomedOutView>
          <GridView>
          </GridView>
     </SemanticZoom.ZoomedOutView>
     <SemanticZoom.ZoomedInView>
          <GridView>
          </GridView>
     </SemanticZoom.ZoomedInView>
</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).

Continuamos con el ejemplo. Definimos un segundo Gridview que añadiremos en la vista ZoomedInView:

<GridView
     Margin="116,0,40,46"
     ItemTemplate="{StaticResource ZoomedInFilmItemTemplate}"
     VerticalAlignment="Center">
</GridView>

En los recursos de la página creamos el DataTemplate para cada elemento del GridView:

<DataTemplate x:Key="ZoomedInFilmItemTemplate">
     <Grid HorizontalAlignment="Left" Width="250" Height="500">
          <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
               <TextBlock Text="{Binding Group.Films.Count}" FontSize="122" FontWeight="Black"
               HorizontalAlignment="Center" VerticalAlignment="Center"/>
          </Border>
          <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
               <TextBlock Text="{Binding Group.Year}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60"
               HorizontalAlignment="Center" Margin="15,0,15,0"/>
          </StackPanel>
     </Grid>
</DataTemplate>

NOTA: En este DataTemplate mostramos la información de cada grupo distinto formado. En nuestro ejemplo, serán los distintos años que contienen películas. Recordar que creamos el objeto FilmYear con dos propiedades. Por un lado un entero (Year) que contendrá la cabecera de cada grupo, el año que contiene las diferentes películas. Por otro lado la colección de películas (Films). En nuestro ejemplo, vamos a mostrar el nombre de cada grupo, es decir el año, junto al número de películas que contiene cada grupo. Es una buena práctica utilizar el control SemanticZoom además de para facilitar la navegación e interacción con el elementos, para ofrecer información extra.

Por último, dado que no hemos definido aún la fuente de datos del Gridview que se mostrará en la vista ZoomOut del SemanticZoom, debemos indicar el origen de datos correspondientes. En la clase FilmView.cs utilizararemos el evento OnNavigateTo para ello:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
     var collectionGroups = groupedFilmsViewSource.View.CollectionGroups;
     ((ListViewBase)this.ZoomSemantico.ZoomedOutView).ItemsSource = collectionGroups;
}

ZoomedInView

ZoomedOutView

Puedes ver el resultado conseguido en este ejemplo en movimiento a continuación:

También puedes descargar el ejemplo realizado:

Espero que lo visto en la entrada os resulte interesante (para mi es uno de mis controles favoritos) ademñas de útil. En próximas entradas seguiremos profundizando en su uso. Recordar, cualquier duda o sugerencia será bienvenida en los comentarios de la entrada.

Conclusiones

  • El control SemanticZoom 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.

Más información

8 pensamientos en “WinRT. Zoom Semántico.

  1. Hola Javier, excelente el tutorial como te dicen por ahí, muy bien explicado. Aunque tengo una duda, que espero puedas resolverme. El método onNavigateTo, ¿podrías ayudarme a pasarlo a Visual Basic?

    • Claro. A ver, si no me equivoco sería algo como lo siguiente:

      Protected Overrides Sub OnNavigatedTo(e As NavigationEventArgs)
      Dim collectionGroups = groupedFilmsViewSource.View.CollectionGroups
      DirectCast(Me.ZoomSemantico.ZoomedOutView, ListViewBase).ItemsSource = collectionGroups
      End Sub

      Un saludo.

      • Gracias por la rapidez. Lo tengo así, pero nada me da fallo en la línea

        Dim collectionGroups= groupedFilmsViewSource.View.CollectionGroups

        Se produjo una excepción de tipo ‘System.NullReferenceException’

        No se que puede ser, lo anterior siguiendo tu manual, eso si en visual basic, me compilaba sin problemas, es este método el que me trae de cabeza..jeje..

        Gracias Javier.

  2. hola Javier!

    Tengo una duda respecto a este tema, otra de ellas, una vez he creado cada uno de mis grupos, con sus respectivos ítems cada uno…como puedo conseguir que pulsando en cada ítem, realice un evento distinto.

    En mi caso, necesito que cada ítem de cada grupo, al pulsar sobre cada uno de ellos vaya a una pagina distinta.

    Podrías explicarme un poco que pasos debería seguir, que estoy bloqueada completamente.

    Gracias.

  3. Pingback: [Universal App] Uso del Zoom Semantico, creando Jumplists | Javier Suárez | Blog

  4. Pingback: [Universal App] Uso del Zoom Semantico, creando Jumplists - Javier Suárez

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s