Freigeben über


XAML | C# | SemanticZoom : Cómo hacer zoom sobre un ítem específico

Avanzado

Para realizar SemanticZoom únicamente podemos hacer uso de componentes que hereden de ListViewBase, estos componentes son:

  • ListView
  • GridView

Este es un ejemplo de SemanticZoom implementado.

 
<SemanticZoom Grid.Row="1" IsZoomedInViewActive="False">
    <SemanticZoom.ZoomedOutView>
        <GridView ItemsSource="{Binding Source={StaticResource BlogRssConsumer},Path=FeedSource}"
                SelectionMode="None">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border Width="250" Height="250" Background="DarkSlateBlue">
                        <StackPanel>
                            <Image Source="{Binding ImageUrl}" HorizontalAlignment="Stretch" MaxHeight="125"
                                    MinHeight="125" Stretch="UniformToFill" />
                            <TextBlock Text="{Binding Titulo}" FontSize="20" TextWrapping="Wrap" />
                            <TextBlock Text="{Binding Autor}" FontSize="15" />
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </GridView>
    </SemanticZoom.ZoomedOutView>
    <SemanticZoom.ZoomedInView>
        <GridView ItemsSource="{Binding Source={StaticResource BlogRssConsumer},Path=FeedSource}"
                SelectionMode="None">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Border Width="1024" VerticalAlignment="Stretch">
                        <StackPanel>
                            <TextBlock Text="{Binding Titulo}" FontSize="50" TextWrapping="Wrap" />
                            <Image Source="{Binding ImageUrl}" HorizontalAlignment="Stretch" MaxWidth="600"
                                    MinWidth="600" MaxHeight="300" MinHeight="300" Stretch="UniformToFill" />
                            <TextBlock  HorizontalAlignment="Stretch" Text="{Binding Content}" FontSize="20"
                                    TextWrapping="Wrap" Height="600" />
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </GridView>

    </SemanticZoom.ZoomedInView>
</SemanticZoom>

Sin embargo esta implementación, que es la que nos enseñan en msdn, tiene algunos problemas.

Intuitivamente cuando hacemos Zoom esperamos que aunque se muestren varios elementos el objeto central o principal sea aquel sobre el que hicimos zoom directamente, sin embargo al hacer zoom de esta forma siempre aparecemos en la visualización por defecto del otro grid, esto es en el item inicial, así que al hacer zoom quedamos desubicados pues no quedamos donde intuitivamente creimos que quedariamos.

Esto se debe a que los controles ListView y GridView en escencia no tienen una funcionalidad para establecer que elemento debe estar visible en pantalla en un momento determinado.

El único control que permite establecer cual es el ítem que se visualiza actualmente es es FlipView. Pero acá tenemos un gran problema recuerden el parrafo inicial de este artículo :

Para realizar SemanticZoom únicamente podemos hacer uso de componentes que hereden de ListViewBase, estos componentes son:

* ListView

* GridView

Si intentamos poner un FlipView dentro de un SemanticZoom tenemos un error en tiempo de diseño.

Así que hay que buscar la forma de poder usar otros elementos dentro del SemanticZoom, es un tema un poco complejo pero por fortuna Luis Guerrero ya se tomo el trabajo de revisarlo:

Usando un ContentControl como hijo de un Control SemanticZoom en XAML+C# de Windows 8

Una vez implementada esa funcionalidad, el código queda así:

 
<SemanticZoom.ZoomedInView>
    <ui:SemanticZoomHost>
        <FlipView ItemsSource="{Binding Source={StaticResource BlogRssConsumer},Path=FeedSource}">
            <FlipView.ItemTemplate>
                <DataTemplate>
                    <Border Width="1024" VerticalAlignment="Stretch">
                        <StackPanel>
                            <TextBlock Text="{Binding Titulo}" FontSize="50" TextWrapping="Wrap" />
                            <Image Source="{Binding ImageUrl}" HorizontalAlignment="Stretch" MaxWidth="600"
                                    MinWidth="600" MaxHeight="300" MinHeight="300" Stretch="UniformToFill" />
                            <TextBlock  HorizontalAlignment="Stretch" Text="{Binding Content}" FontSize="20"
                                    TextWrapping="Wrap" Height="600" />
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </FlipView.ItemTemplate>
        </FlipView>
    </ui:SemanticZoomHost>
</SemanticZoom.ZoomedInView>

Ya tenemos solucionada una parte del problema, pero aún falta más, hay que ahora si, establecer el item actual del FlipView, esto lo logramos modificando el atributo SelectedItem, pero que valor debemos asignarle? Cómo identificamos el item actual?

SemanticViewZoom tiene un evento ViewChangeStarted que se dispara cuando apenas se hace Zoom.

 
<SemanticZoom Grid.Row="1" IsZoomedInViewActive="False" ViewChangeStarted="SemanticZoom_ViewChangeStarted_1">

Allí en los argumentos debemos detectar cuando estamos cambiando de Zoom out a Zoom in, lo cual es sencillo de hacer:

 
private void SemanticZoom_ViewChangeStarted_1(object sender, SemanticZoomViewChangedEventArgs e)
{
    if (!e.IsSourceZoomedInView)
    {

    }
}

Ahora debemos indagar acerca del item que ha disparado el evento, esto lo encontramos en e.SourceItem.Item.

Ahora no resta sino manipular el FlipView para que establezca el item actual. La solución más rápida (pero de mala calidad) es colocarle nombre al FlipView para poder modificarle desde code behind dicha propiedad

 
private void SemanticZoom_ViewChangeStarted_1(object sender, SemanticZoomViewChangedEventArgs e)
{
    if (!e.IsSourceZoomedInView)
    {
        flipView.SelectedItem = e.SourceItem.Item;
    }
}

La forma más elegante y conveniente de hacerlo es utilizando Binding, para ello creamos un objeto que implemente INotifyPropertyhanged, en lo personal utilizo BindableBase una de las clases incluida en la plantilla GridApplication de Visual Studio.

 
public class CurrentItemStorage:BindableBase
{
    private object _currentItem = new object();

    public object CurrentItem
    {
        get {
            return _currentItem;
        }
        set { SetProperty<object>(ref _currentItem, value); }
    }
}

Ahora desde XAML creamos una intancia de la clase:

 
<Page.Resources>
    <model:CurrentItemStorage x:Key="CurrentItemStorage" x:Name="CurrentItemStorage" />
</Page.Resources>

Y hacemos Binding de dicho objeto con la propiedad SelectedItem del FlipView, teniendo especial cuidado en hacer Binding con el objeto sino con el atributo que contiene llamado CurrentItem.

 
<FlipView ItemsSource="{Binding Source={StaticResource BlogRssConsumer},Path=FeedSource}"
        x:Name="flipView"
        SelectedItem="{Binding Source={StaticResource CurrentItemStorage},Path=CurrentItem, Mode= TwoWay}">

También es muy importante establecer el modo de Binding en TwoWay, esto porque esta propiedad debe también reflejar cambios en el modelo para conservar el estado del FlipView.

Hechos estos cambios es momento de regresar al manejador del evento y ponerlo a tono con ellos:

 
private void SemanticZoom_ViewChangeStarted_1(object sender, SemanticZoomViewChangedEventArgs e)
{
    if (!e.IsSourceZoomedInView)
    {
        CurrentItemStorage.CurrentItem = e.SourceItem.Item;
    }
}

El resultado es evidente, al desplegar el grid y hacer Zoom sobre uno de sus items, inmediatamente vemos ese ítem ampliado mostrando mayor información y podemos navegar a los demás items utilizando la funcionalidad de navegación del FlipView.

Grid normal

Luego al hacer zoom...

Grid al utilizar Semantic Zoom