Scroll Infinito en Windows 8.1 con XAML y C#

viernes, 7 de marzo de 2014


Cuando se trabaja con grandes cantidades de datos y se tiene que mostrar elementos en cualquier tipo de listado, es muy útil mostrarlos de forma paginada. Un scroll infinito es una solución bastante útil, donde a medida que el usuario se acerca al final del scroll se carga la página siguiente.

Vamos a ver como podemos hacer scroll infinito en windows 8.1.

Creamos un proyecto de tipo Windows Store en blanco.

Creamos el modelo de los elementos que vamos a representar en un listado.

public class ImageItem
{
    public string Title { get; set; }
}

Para poder hacer scroll infinito necesitamos un contenedor de elementos como un GridView o un ListView en nuestra página, vamos a añadir para este ejemplo un GridView y vamos a definir un DataTemplate con un TextBlock para el título y un border.

<GridView ItemsSource="{Binding Items}"
    IncrementalLoadingThreshold="0" 
    IncrementalLoadingTrigger="Edge" 
    DataFetchSize="1"
    Margin="80,80,0,20">
    <GridView.ItemTemplate>
        <DataTemplate>
            <Grid Width="200" Height="300" Margin="0">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>
                <TextBlock Text="{Binding Title}"/>
                <Border Grid.Row="1" Background="LightGreen">
                    
                </Border>
            </Grid>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>


Como se ve en el código, nuestra propiedad enlazada con el ViewModel se llamará Items y debe ser un tipo de colección que implemente el interface ISupportIncrementalLoading para permitir scroll infinito.

En base a la resolución de pantalla y a los datos de dimensiones de cada elemento del GridView, el control calcula el número de elementos a cargar por cada página. Hay 3 propiedades del GridView que van a afectar al scroll infinito

IncrementalLoadingThreshold: esta relacionado el punto del scroll donde se van a cargar más elementos, cuando más bajo sea este valor, en un punto más cercano al final del scroll se solicitará carga de nuevos elementos.

IncrementalLoadingTrigger: viene a ser un on y off para establecer si el control tendrá la funcionalidad de cargar más elementos de forma virtual, el valor por defecto es Edge y el otro posible valor en none.

DataFechSize: una vez calculado por el control el número de elementos por cada página, esta propiedad lo que indica es el número de paginas que se solicita a la lista enlazada en cada nueva petición.

Siempre existe la posibilidad de no utilizar el número de páginas a cargar que nos va a solicitar  el control y llevar una gestion propia en el ViewModel.

Por defecto en Windows 8.1 no viene ningún tipo de colección que implemente ISupportIncrementalLoading, así que nos tenemos que crear una.

Creamos una clase PaginatedCollection que herede de ObservableCollection y también que implemente ISupportIncrementalLoading.
public class PaginatedCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading
{
    private Func<uint, Task<IEnumerable<T>>> load;
    public bool HasMoreItems { get; protected set; }

    public PaginatedCollection(Func<uint, Task<IEnumerable<T>>> load)
    {
        HasMoreItems = true;
        this.load = load;
    }

    public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
    {
        return Task.Run<LoadMoreItemsResult>(async () =>
        {
            var data = await load(count);

            foreach (var item in data)
            {
                await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
                () =>
                {
                    Add(item);
                });
            }

            HasMoreItems = data.Any();

            return new LoadMoreItemsResult()
            {
                Count = (uint)data.Count(),
            };
        }).AsAsyncOperation<LoadMoreItemsResult>();
    }
}

Al implementar el interface tenemos que definir una propiedad HasMoreItems y un método LoadMoreItemsAsync que recibe un parámetro Count que es el número de elementos a cargar, devuelve un objeto de tipo IAsyncOperation<LoadMoreItemsResult>.

Para poder reutilizar esta colección en el futuro en lugar de implementar directamente la carga de elementos en la propia clase, delegamos en el cliente consumidor la definición de esta carga. Para conseguir esto nos hemos definido una Func<T> que recibe en el constructor y que invoca dentro de LoadMoreItemsAsync.
Para evitar el típico error que sucede al modificar una colección desde un subproceso distinto del que fue creado, accedemos al dispacher para añadir desde ahí cada nuevo item a la colección.

Ya solo nos falta crear el ViewModel con el método de carga.

public class MainViewModel
{
    private uint itemsCount;

    public PaginatedCollection<Item> Items { get; set; }

    public MainViewModel()
    {
        Items = new PaginatedCollection<Item>(LoadData);
    }

    private async Task<IEnumerable<Item>> LoadData(uint count)
    {
        List<Item> products = new List<Item>();

        if (itemsCount < 150)
        {

            for (int i = 0; i < count; i++)
            {
                products.Add(new Item() { Title = "Titulo del item" + (itemsCount + (i + 1)) });
            }
        }
        itemsCount += count;

        return products;
    }
}

Como se puede apreciar en el código del ViewModel no se realiza ninguna carga inicial de elementos en la colección. El GridView al detectar que su colección enlazada implementa ISupportIncrementalLoading realiza una primera petición de 1 elemento para calular el número de elementos por cada página, despues realizará peticiones de elementos a medida que el usuario se mueva con el scroll.

El parámetro Count será el número de elementos correspondientes con el número de páginas a cargar con cada petición desde el control GridView según el valor de la propiedad DataFetchSize. Por ejemplo si el control calcula según la resolución de la pantalla y las dimensiones el item devuelto en la primera llamada, que cada página va a tener 20 elementos y en la propiedad DataFetchSize hemos indicado un 2, con cada nueva petición el control nos solicitara 40 elementos.

Código Fuente

Libros Relacionados

Windows 8.1 Apps with XAML and C# Unleashed

Programming Windows: Writing Windows 8 Apps With C# and XAML

Desarrollo en Windows 8 y Windows Phone 8 con XAML y C#

No hay comentarios:

Publicar un comentario