domingo, 10 de mayo de 2015

Paginación con LinQ



Después del último post dedicado a los operadores de Partición, me ha parecido interesante el parar un poco con toda la ristra de descripción de operadores y entrar un poco en un ejemplo más jugoso de la vida real. En este ejemplo utilizaremos dos de los operadores descritos en el último post, Take y Skip, y veremos lo enormemente sencillo que puede llegar a ser, el realizar paginación de datos con el uso de estos operadores.


Para el ejemplo he utilizado una aplicación WPF. No entraré demasiado en su implementación y en las características de esta tecnología, para aparte de no confundir, centrarme en lo realmente significativo del post. Pero que nadie se preocupe si está interesado en la última tecnología de desarrollo de aplicaciones de escritorio de Microsoft, ya que si el tiempo y la dedicación me lo permiten, cuando acabe todo este tinglao de LinQ, la idea es empezar con WPF, ya que creo que el vacío de información en castellano es bastante grande, y no hay nada realmente didáctico.








Recuerda que aquí tienes el indice de todos los posts del Curso de LinQ.



Paginación

Como he comentado en la introducción, la forma de realizar paginación con LinQ, se realiza mediante la combinación de los operadores Skip y Take.

La paginación se basa en desmembrar un conjunto de datos en grupos más pequeños del mismo número de elementos, a excepción del último que en caso de coincidir con una partición exacta contendrá los elementos restantes.

Colección de 205 elementos, paginados en grupos de 50, nos resultarían 4 grupos completos de 50 elementos y uno último de 5.


Para realizar este ejemplo en código sería algo tan sencillo como esto:


var resultado = coleccion.Skip(pagAMostrar * numElementosPorPagina).Take(numElementosPorPagina);


Indicar que pagAMostrar tiene que ser de índice 0, ósea que la primera será la cero y no la uno.



Aplicación

La aplicación de ejemplo, es una aplicación muy simple que crea una colección de enteros de 308 elementos y genera las acciones correspondientes para dividir en grupos de 20 elementos cada uno.

Está compuesta por 4 métodos, todos ellos añadidos al CodeBehind, para que sea más sencilla su comprensión. Iré explicando cada uno de ellos y al final del todo pondré un enlace con el código por si alguien quiere probarla.

Vamos con el método de carga, el LOAD:


private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
    List<int> numeros = Enumerable.Range(1, 308).ToList(); /// Creamos la colección principal
    this.CrearEnlacesPaginacion(numeros, 20);              /// Creamos las labels de los enlaces (20 elementos por pag)
}


En este método (evento), simplemente creamos la colección de números principal y llamamos al método general que realizará todo el trabajo. Como curiosidad comprobar que la colección principal, no se guarda en ningún campo de clase o propiedad, solo se crea y se pasa por parámetros. Como descubriremos un poquito más adelante no es necesario.


Ahora le toca el turno al método general, que creará los enlaces para la paginación:


/// <summary>
/// Crea las etiquetas de los enlaces y las añade dinámicamente al 'Window' (Form en WPF)
/// </summary>
/// <param name="numeros">Colección Principal a particionar</param>
/// <param name="elementosPorPagina">nº de elementos de cada grupo</param>
        private void CrearEnlacesPaginacion(List<int> numeros, int elementosPorPagina)
        {
            /// Hayamos el número de labels a crear
            int numerosEnlaces = numeros.Count / elementosPorPagina + 1;
            /// Creamos las labels
            for (int i = 1; i <= numerosEnlaces; i++)
            {
                /// Hayamos el primer elemento para el grupo
                int primerNumero = (i - 1) * elementosPorPagina;
                /// Hayamos el último elemento para el grupo, comprobando que no sea el último, 
                /// ya que este tiene un trato especial
                int ultimoNumero = primerNumero + elementosPorPagina <= numeros.Count
                                 ? primerNumero + elementosPorPagina
                                 : primerNumero + (numeros.Count - primerNumero);
                /// Creamos el enlace
                var enlace = this.CrearEnlace(numeros, i, primerNumero, ultimoNumero);
                /// Lo añadimos al Window
                this.PanelEnlaces.Children.Add(enlace);
            }
        }


Sencillo:

  1. Hayamos el número de enlaces, según el número de elementos.
  2. Recorremos cada uno de ellos.
  3. Hayamos el primer elemento del grupo.
  4. Hayamos el último elemento del grupo (teniendo en cuenta si es el último grupo)
  5. Creamos el enlace.
  6. Lo añadimos al formulario



El siguiente método es el que crea cada una de las labels por solitario:


/// <summary>
/// Creamos la etiqueta del enlace
/// </summary>
/// <param name="numeros">Colección Principal a particionar</param>
/// <param name="posicion">´nº de orden del grupo</param>
/// <param name="indicePrimerElemento">primer elemento del grupo</param>
/// <param name="indiceUltimoElemento">último elemento del grupo</param>
/// <returns>Etiqueta configurada</returns>
private TextBlock CrearEnlace(List<int> numeros, int posicion, int indicePrimerElemento, int indiceUltimoElemento)
{
    /// Creamos la etiqueta y su configuración gráifca
    TextBlock etiqueta  = new TextBlock();
    etiqueta.Foreground = Brushes.DodgerBlue;
    etiqueta.Cursor     = Cursors.Hand;
    etiqueta.FontSize   = 18;
    etiqueta.Text       = string.Format(" {0} ", posicion.ToString());
    /// Añadimos dinámicamente el comportamiento de su evento click
    etiqueta.MouseDown +=
        (sender, e) => this.CrearAccionClickEnlace(numeros, posicion, indicePrimerElemento, indiceUltimoElemento)();
 
    return etiqueta;
}


  1. Crea la etiqueta y su configuración gráfica.
  2. Añade dinámicamente el comportamiento de su evento click.




Acabaremos con el método que crea la acción para cada una de las etiquetas de enlace:


/// <summary>
/// Creamos el comportamiento de cada una de las etiquetas
/// </summary>
/// <param name="numeros">Colección Principal a particionar</param>
/// <param name="posicion">´nº de orden del grupo</param>
/// <param name="indicePrimerElemento">primer elemento del grupo</param>
/// <param name="indiceUltimoElemento">último elemento del grupo</param>
/// <returns>Acción a realizar</returns>
private Action CrearAccionClickEnlace(List<int> numeros, int posicion, int indicePrimerElemento, int indiceUltimoElemento)
{
    /// Calculamos el nº de elementos por enlace, normalmente tendría que ser una constante, pero el último puede cambiar
    int numeroElementosParaEsteEnlace = indiceUltimoElemento - indicePrimerElemento;
 
    Action resultado = () =>
    {
        /// Creamos la lista que bindearemos al grid
        List<int> conjuntoCorrespondienteAlEnlaceActual =
            numeros.Skip(indicePrimerElemento).Take(numeroElementosParaEsteEnlace).ToList();
        /// Bindeamos la lista y refrescamos los datos
        this.listNumeros.ItemsSource = conjuntoCorrespondienteAlEnlaceActual;
        this.listNumeros.Items.Refresh();
        /// Actualizamos la label (grande) del fondo del formulario que informará de la elección actual
        this.txtPosicionActual.Text = posicion.ToString();
    };
 
    return resultado;
}


  1. Hayamos el nº de elementos que tendrá cada enlace.
  2. Creamos la lista que contendrá los elementos del grupo, aquí es donde reside la lógica del post y de todo lo que hace LinQ, Skip y Take por nosotros.
  3. Bindeamos y refrescamos los datos del ListBox.
  4. Actualizamos la etiqueta informativa (grande) del fondo del grid, que indica cual es la etiqueta pulsada.



No ha sido necesario guardar la colección principal en memoria, porque ha viajado por parámetros hasta el último método donde se realiza la creación de las acciones. Ya que esto solo se ejecuta una vez y no se vuelve a consultar más, no es necesario guardar en memoria.


Os pongo los pantallazos de resultado:












































































...




























Os dejo un enlace con el código por si alguien le quiere echar un ojo.