|
Paginacion
Cómo implementar la paginación de los listados
Featured IntroduccionA continuación se detalla un método que he encontrado para habilitar la paginación de los listados de una manera sencilla, y creo que bastante eficiente. PasosPara habilitar la paginación vamos a tener que tocar lamentablemente todas las capas, excepto los DTOs, pero los cambios son sencillos, y no tiene por qué darnos mayores problemas. Pondremos como ejemplo la entidad PERMISO, que es sencilla y es con la que ya he hecho las pruebas, cuyo código ya está disponible en el repositorio para chequearlo. CADPaso 1 En primer lugar, lo más evidente es que hay que modificar el CAD para que los métodos de listados nos devuelvan tan sólo la página de datos que queremos. Para ello, lo primero que haremos será implementar un método auxiliar que nos devuelva el total de registros, dato necesario para calcular el número de páginas que tenemos. Para ello, implementamos el siguiente método: public static int numRecords()
{
return cInterfazBD.countRecords("Permiso");
}Este método llama al método estático "countRecords" del InterfazBD, y le pasa el nombre de la tabla de la que queremos obtener todos los registros. En el caso de tener un listado condicionado (P. ej. Permisos por perfil), necesitaríamos crearnos un método propio que ejecute un "Select count()..." con la cláusula WHERE necesaria. Paso 2 En segundo lugar, sobrecargamos el método "listado()" que nos va a devolver el conjunto de datos que necesitamos, y le pasaremos dos variables Int, que representarán el tamaño de la página, y el índice del registro por el que queremos que empiece a devolver datos. Como en los tableAdapters no podemos ejecutar querys dinámicas, necesitaremos utilizar el InterfazBD para obtener un SQLDataReader: public static ArrayList listado(int pageSize, int skip)
{
ArrayList permisos = null;
try
{
SqlDataReader dr;
//UsuariosDataSet.PermisoDataTable dt = new UsuariosDataSet.PermisoDataTable();
string sql = "SELECT TOP " + pageSize + " id, permiso, descripcion FROM dbo.Permiso ";
sql += " WHERE id NOT IN ";
sql += "(SELECT TOP " + skip + " id FROM dbo.Permiso ORDER BY id) ";
sql += "ORDER BY id";
dr = cInterfazBD.ejecutaReader(sql, null);
rellenaListado(ref permisos, dr);
dr.Close();
dr.Dispose();
}
catch (Exception ex)
{
throw ex;
}
return permisos;
}Lo que hace esta llamada es que para un tamaño de página 10, y un valor de skip de 20, nos devuelve los primeros 10 registros que no estén entre los 20 primeros. El valor de skip lo obtendremos posteriormente multiplicando el tamaño de página por la página actual. Una vez sobrecargado el método de listado, necesitaremos sobrecargar también las funciones auxiliares "rellenaListado(ref permisos, dr);" y "cargarEN(ref p, dr);" para poder pasarles un DataReader, en lugar de un DataRow: private static void rellenaListado(ref ArrayList permisos, SqlDataReader dr)
{
if (dr.HasRows)
{
permisos = new ArrayList();
while (dr.Read())
{
cPermiso p = new cPermiso();
cargarEN(ref p, dr);
permisos.Add(p.DTO);
}
}
}
private static void cargarEN(ref cPermiso permiso, SqlDataReader fila)
{
permiso.Id = (int)fila["id"];
if (fila["descripcion"] != System.DBNull.Value)
permiso.Descripcion = (string)fila["descripcion"];
if (fila["permiso"] != System.DBNull.Value)
permiso.Permiso = (string)fila["permiso"];
}Con estos cambios, ya tenemos el CAD preparado para poder devolver resultados paginados. Ahora nos queda propagar los cambios a los ENs y CPs. ENUna vez tenemos el CAD preparado, pasamos a implementar en el EN las llamadas correspondientes a los nuevos métodos: numRecords() y listado(int pageSize, int skip): public static ArrayList listado(int pageSize, int skip)
{
ArrayList permisos = null;
try
{
permisos = CADPermiso.listado(pageSize, skip);
}
catch (Exception ex)
{
throw ex;
}
return permisos;
}
public static int numRecords()
{
int retorno = 0;
try
{
retorno = CADPermiso.numRecords();
}
catch (Exception)
{
throw;
}
return retorno;
}
CPY lo mismo para el CP: public ArrayList listado(int pageSize, int skip)
{
Console.WriteLine("CPPermiso: listado paginado");
ArrayList retorno = null;
try
{
retorno = cPermiso.listado(pageSize,skip);
}
catch (Exception ex)
{
Console.WriteLine("[ERROR] " + ex.Source + "\n" + ex.Message + "\n" + ex.StackTrace);
retorno = null;
}
return retorno;
}
public int numRecords()
{
Console.WriteLine("CPPermiso: numRecords");
int retorno = 0;
try
{
retorno = cPermiso.numRecords();
}
catch (Exception ex)
{
Console.WriteLine("[ERROR] " + ex.Source + "\n" + ex.Message + "\n" + ex.StackTrace);
retorno = 0;
}
return retorno;
}Compilamos el proyecto Negocio y NoticiasComPlus y comprobamos que no hay errores. Si todo está correcto, procedemos a registrar de nuevo el componente COM+ para exponer los nuevos métodos. CP en ClienteEn el proxy del COM+ en el proyecto ClienteRemoto también tenemos que exponer los nuevos métodos para que el Interfaz los pueda utilizar: public static ArrayList listado(int pageSize, int skip)
{
object[] argumentos = new object[] { pageSize, skip };
ArrayList resultado = null;
try
{
rem = ConnectRemoting(servicioID);
resultado = (ArrayList)rem.invocar("listado", argumentos);
}
catch (Exception ex)
{
throw ex;
}
return resultado;
}
public static int numRecords()
{
object[] argumentos = null;
int resultado = 0;
try
{
rem = ConnectRemoting(servicioID);
resultado = (int)rem.invocar("numRecords", argumentos);
}
catch (Exception ex)
{
throw ex;
}
return resultado;
}En este punto, sólo nos queda utilizar los nuevos métodos desde el Interfaz de Usuario. Aplicación WindowsPara utilizar la paginación en la aplicación de Windows, he implementado un control de usuario que se encarga de gestionar todos las propiedades de la paginación: Registros totales, página actual, tamaño de página, etc. Está implementado en el proyecto "cliente/ControlLibrary", y se llama ctrlPaginacion". Para utilizarlo, lo primero que haremos será añadir el control al "Cuadro de herramientas". Para ello, pulsamos con el botón derecho en el cuadro de herramientas y seleccionamos "Elegir elementos...". En el cuadro de diálogo que se nos abre, en la pestaña "Componentes de .Net Framework", pulsaremos examinar y buscaremos la DLL del control, que está en "ControlLibrary\bin\debug\ControlLibrary.dll". Con esto nos aparecerá un nuevo control disponible: "ctrlPaginacion". Una vez lo tenemos localizado, lo arrastramos al formulario y veremos que nos crea una barra de herramientas con los controles de Navegación por páginas. Para el ejemplo, llamaremos al control "ctrlPaginacion". Vamos a suponer que en el formulario que estamos implementando, ya hemos agregado un grid donde cargar los datos, y que tenemos una función "cargarListado()" que se encarga de rellenar el grid. Mirad el código para más detalles de implementación, que se corresponde al formulario "frmPermisos". El primer paso necesario es inicializar el control, que lo haremos en la carga del formulario: private int results = -1;
private void frmPermisos_Load(object sender, EventArgs e)
{
results = CPPermiso.numRecords();
ctrlPaginacion.init(results);
}Para inicializarlo, le pasamos el total de registros que tenemos, utilizando el nuevo método que hemos implementado en las capas de negocio. El control ya se encarga de calcular el número de páginas, partiendo de un tamaño de página inicial de 10 registros. Una vez inicializado, sólo nos queda suscribirnos a los 6 eventos que genera este control, y que nos servirán para recargar el listado según vayamos cambiando de páginas o de tamaño de página: #region Paginacion
private void ctrlPaginacion_FirstClicked()
{
cargarListado();
}
private void ctrlPaginacion_PrevClicked()
{
if (ctrlPaginacion.paginacion.PageCount > 0)
cargarListado(true);
}
private void ctrlPaginacion_NextClicked()
{
cargarListado();
}
private void ctrlPaginacion_LastClicked()
{
cargarListado();
}
private void ctrlPaginacion_PageChanged()
{
cargarListado();
}
private void ctrlPaginacion_PageSizeChanged()
{
cargarListado();
}
#endregionEl siguiente paso será suscribir los eventos del control de Paginación con la función correspondiente de las anteriormente indicadas: FirstClicked -> ctrlPaginacion_FirstClicked PrevClicked -> ctrlPaginacion_PrevClicked NextClicked -> ctrlPaginacion_NextClicked LastClicked -> ctrlPaginacion_LastClicked PageChanged -> ctrlPaginacion_PageChanged PageSizeChanged -> ctrlPaginacion_PageSizeChanged desde la ventana de propiedades del control, seleccionando la opción de eventos. Todos los eventos llaman a la función "cargarListado()" que es la encargada de realizar el binding de datos con el Grid, y que llama al nuevo método de listado() sobrecargado con las propiedades de la paginación que nos proporciona el control: private void cargarListado()
{
this.Cursor = Cursors.WaitCursor;
dTOPermisoBindingSource.Clear();
try
{
ArrayList permisos = CPPermiso.listado(ctrlPaginacion.paginacion.PageSize, ctrlPaginacion.paginacion.Skip);
if (permisos != null)
{
foreach (DTOPermiso p in permisos)
{
dTOPermisoBindingSource.Add(p);
}
}
}
catch (Exception ex)
{
utils.Error(ex.Message);
}
this.Cursor = Cursors.Default;
}Y con esto ya tenemos los grids paginados, reutilizando el mismo control en todas las páginas que lo requieran. Finalmente, en el caso de que añadamos registros al listado, o eliminemos, tendremos que notificárselo al control para que actualice sus registros: Añadimos un registro: ctrlPaginacion.paginacion.refresh(ctrlPaginacion.paginacion.TotalRecords + 1);
ctrlPaginacion.actualizaPaginacion();Al añadir uno, se le sumamos al número de registros totales que tenía el control, y lo refrescamos para que recalcule sus datos. Eliminamos registros ctrlPaginacion.paginacion.refresh(ctrlPaginacion.paginacion.TotalRecords - dTOPermisoDataGridView.SelectedRows.Count);
ctrlPaginacion.actualizaPaginacion();Al eliminar registros, le restamos al numero total de registros los que hemos eliminado, y volvemos a refrescar. Posibles mejorasEsta implementación tan sólo realiza la paginación, pero no tiene en cuenta la ordenación de los elementos. Como mejora se podría implementar un campo de ordenación, pasándolo como parámetro desde el interfaz hasta el CAD, y añadiendo el campo "order by" correspondiente a la SELECT que realiza la paginación. |