[W8] Scroll infinito ( II )

 

Continúo la serie de scroll infinito. En la implementación del artículo anterior, usaba un LoadItemsResult para devolver la carga asíncrona de la fuente de datos. Sin embargo, hay ocasiones en que esto se nos queda corto, como por ejemplo cuando ocurre una excepción en la carga o queremos limitar los elementos que se van cargando. Es por ello que crearemos un nuevo tipo que implemente IAsyncOperation<LoadMoreItemsResult> que nos permita tener más control sobre lo que devolvemos. Esta interfaz dispone de un método y una propiedad:

  • GetResults: Devuelve el resultado de la operación
  • Completed: Una propiedad de tipo IAsyncOperationHandler.

La idea es sencilla. Anteriormente teníamos un método que se encargaba de obtener los datos. Ahora nosotros vamos a hacer ese método a través de esa interfaz, permitiendo el control absoluto de lo que queremos devolver. Tendremos un método asíncrono que es el encargado de obtener los datos y añadirlos al IncrementalSource (que recordemos, es una ObservableCollection) tal que así:

Code Snippet
  1. private async void LoadItems(IncrementalSource<T> source, uint count)
  2.         {
  3.             try
  4.             {
  5.                 var result = await SourceManager.Load(count);
  6.                 foreach (var item in result)
  7.                 {
  8.                     source.Add(item);
  9.                 }
  10.  
  11.                 results.Count = count;
  12.                 asyncStatus = AsyncStatus.Completed;
  13.                 if (Completed != null)
  14.                     this.Completed(this, asyncStatus);
  15.             }
  16.             catch (Exception ex)
  17.             {
  18.                 // Gesti?n de excepciones
  19.             }
  20.         }

Básicamente lo que hacemos es cargar los datos y cuando ya lo están:

  • Notificar cuántos elementos se han cargado.
  • Lanzar el evento Completed para notificar que la carga se ha completado. Le el objeto actual como sender y un parámetro de AsyncStatus que lo asignamos como Completed, lo cual notifica al subscriptor del evento que la parte asíncrona ya ha terminado. El resto de la clase sería así:
Code Snippet
  1. public class DataAsyncLoader<T> : IAsyncOperation<LoadMoreItemsResult>
  2.     {
  3.         private AsyncStatus asyncStatus = AsyncStatus.Started;
  4.         private LoadMoreItemsResult results;
  5.  
  6.         public DataAsyncLoader(IncrementalSource<T> source, uint count)
  7.         {
  8.             LoadItems(source, count);
  9.         }
  10.  
  11.         public AsyncOperationCompletedHandler<LoadMoreItemsResult> Completed { get; set; }
  12.  
  13.         public LoadMoreItemsResult GetResults()
  14.         {
  15.             return results;
  16.         }
  17.  
  18.         public AsyncStatus Status
  19.         {
  20.             get { return this.asyncStatus; }
  21.         }
  22.     }

Y por último y recordando el artículo anterior, en el método dentro de IncrementalSource que teníamos la carga de elementos debemos cambiarlo por:

Code Snippet
  1. public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
  2.         {
  3.             return new DataAsyncLoader<T>(this, count);
  4.         }

Y ya está. Con esto estamos externalizando la carga de elementos y teniendo un control absoluto de lo que ocurre antes, durante y después de la carga.

[W8] Scroll infiinito ( I )

 

Una de las mayores bazas de una interfaz de usuario es cómo se muestran los elementos y cómo se van cargando. En el caso de Windows 8 (y Windows Phone) disponer de elementos en un ListView es algo común. Pero cuando llegamos al final de la lista, podemos adoptar dos alternativas:

  • Colocar un botón tipo “Cargar más elementos”
  • O hacer que automáticamente cargue los elementos según lo necesite. Evidentemente, este es el caso que vamos a comentar.

En primer lugar tenemos un ListView con un ItemSource asociado a un ObservableCollection<T>. Para detectar si hay más elementos, el ListView dispone de una propiedad llamada IncrementalLoadingTrigger. Esta propiedad por defecto está ajustada a Edge. Si queremos que no haya ningún tipo de carga incremental, hay que ajustarla a None.

En segundo lugar, tenemos que lograr que los datos nos indiquen cuándo es necesario obtener más elementos. Como hasta ahora usamos un ObservableCollection<T> como binding, necesitamos cambiarlo un poco. En este punto se introduce la interfaz ISupportIncrementalLoading, que se encargará de notificar al destino del binding si hay más elementos disponibles para cargar y un método para cargar los datos en función de la cantidad de elementos previamente cargados:

HasMoreItems: Indica si hay más elementos para cargar.

LoadMoreItemsAsync: Carga asíncrona de los nuevos elementos.

Partiendo de lo anterior, necesitamos crear un nuevo tipo que implemente la interfaz ISupportIncrementalLoading y la ObservableCollection<T> para poder bindear los datos. Veamos un ejemplo que yo he llamado IncrementalSource:

Code Snippet
  1. public class IncrementalSource<T> : ObservableCollection<T>, ISupportIncrementalLoading
  2.         {
  3.             private int VirtualCount { get; set; }
  4.  
  5.             public IncrementalSource()
  6.             {
  7.  
  8.             }
  9.  
  10.             #region ISupportIncrementalLoading
  11.  
  12.             public bool HasMoreItems
  13.             {
  14.                 get { return this.VirtualCount > 0; }
  15.             }
  16.  
  17.             public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
  18.             {
  19.  
  20.             }
  21.             #endregion
  22.         }

Sólo tenemos que rellenar el método de LoadMoreItemAsync., que devuelve un IAsyncOperation de LoadMoreResultItems. Esta estructura tiene un único campo Count que indica la cantidad de elementos que han sido cargados. De este modo se va notificando al destino del binding qué elementos se van cargando de forma asíncrona para que los vaya obteniendo y renderizando. Voy a adjuntar un ejemplo teórico de cómo se podría rellenar ese método:

Code Snippet
  1. public IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
  2.         {
  3.             return Task.Run<LoadMoreItemsResult>(
  4.                 async () =>
  5.                 {
  6.                     var result = await SourceManager.Load(count);
  7.                     foreach(var items in result)
  8.                     {
  9.                         this.Add(result);     
  10.                     }
  11.                     return new LoadMoreItemsResult() { Count = (uint)result.Count };
  12.                 }).AsAsyncOperation<LoadMoreItemsResult>();
  13.         }

Y si el ListView está dentro de un ScrollViewer, ya tendremos el scroll infinito funcionando.