Requerimiento
Se desea crear una aplicación donde el usuario pueda seleccionar los campos a mostrarse en su consulta, de tal forma que este pueda agregar o quitar las columnas en una grilla de Windows.
Alternativas de Solución
1. Inicialmente podemos enlazar todos los campos del origen de datos al control e ir configurando la propiedad visible de cada columna, pero aunque no se vean, las columnas existen en memoria y consumen espacio.
2. Podemos crear en forma dinámica las columnas e ir removiendo también en forma dinámica. Esta es una mejor solución pero requiere de mas experiencia y es la que veremos a continuación.
Crear el Procedimiento Almacenado en la Base de Datos de SQL Server
Para este post trabajaremos con la tabla Empleados de Northwind, pero la aplicación esta creada de tal forma que pueda ajustarse a cualquier tabla de cualquier base de datos.
Create Procedure [dbo].[uspEmployeesListar]
As
Select EmployeeID,LastName,FirstName,
IsNull(BirthDate,'1/1/1900') As BirthDate
From Employees Order By 1
Crear la Aplicación WinForms en C#
Crear una nueva aplicación en C# llamada: "DataGridView_Columnas_Dinamicas".
Nota: Todas las clases deben crearse en librerías separadas (distribuidas), pero para simplificar la aplicación se están creando dentro de ésta.
Crear la Clase con la Entidad del Negocio
using System;
namespace DataGridView_Columnas_Dinamicas
{
public class beEmpleado
{
public int IdEmpleado { get; set; }
public string Apellido { get; set; }
public string Nombre { get; set; }
public DateTime FechaNac { get; set; }
}
}
using System;
using System.Data; //CommandType
using System.Data.SqlClient; //SqlConnection, SqlCommand, SqlDataReader
using System.Collections.Generic; //List
namespace DataGridView_Columnas_Dinamicas
{
public class daEmpleado
{
public List<beEmpleado> listar(SqlConnection con)
{
List<beEmpleado> lbeEmpleado = null;
SqlCommand cmd = new SqlCommand("uspEmployeesListar", con);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataReader drd = cmd.ExecuteReader(CommandBehavior.SingleResult);
if (drd != null)
{
lbeEmpleado = new List<beEmpleado>();
int posIdEmpleado = drd.GetOrdinal("EmployeeID");
int posApellido = drd.GetOrdinal("LastName");
int posNombre = drd.GetOrdinal("FirstName");
int posFechaNacimiento = drd.GetOrdinal("BirthDate");
beEmpleado obeEmpleado;
while (drd.Read())
{
obeEmpleado = new beEmpleado();
obeEmpleado.IdEmpleado = drd.GetInt32(posIdEmpleado);
obeEmpleado.Apellido = drd.GetString(posApellido);
obeEmpleado.Nombre = drd.GetString(posNombre);
obeEmpleado.FechaNac = drd.GetDateTime(posFechaNacimiento);
lbeEmpleado.Add(obeEmpleado);
}
drd.Close();
}
return (lbeEmpleado);
}
}
}
using System;
using System.Collections.Generic;
using System.Data.SqlClient; //SqlConnection
using System.Configuration; //ConfigurationManager
namespace DataGridView_Columnas_Dinamicas
{
public class brEmpleado
{
public string Conexion { get; set; }
public brEmpleado()
{
Conexion = ConfigurationManager.ConnectionStrings["conNW"].ConnectionString;
}
public List<beEmpleado> listar()
{
List<beEmpleado> lbeEmpleado = null;
using (SqlConnection con = new SqlConnection(Conexion))
{
try
{
con.Open();
daEmpleado odaEmpleado = new daEmpleado();
lbeEmpleado = odaEmpleado.listar(con);
}
catch (SqlException ex)
{
//Capturar el error y grabar un Log
}
} //con.Close(); con.Dispose(); con = null;
return (lbeEmpleado);
}
}
}
Nota: La aplicación debe referenciar a la librería: "System.Configuration" para usar el namespace con el mismo nombre.
Abrir el archivo App.Config y aumentar la cadena de conexión a Northwind (conNW):
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="conNW" providerName="SQLServer"
connectionString="uid=UsuarioNW;pwd=123456;
data source=DSOFT\Sqlexpress;database=Northwind"/>
</connectionStrings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
</configuration>
Crear el Formulario Windows
Cambiar el nombre del formulario a "frmConsultaEmpleado" y cambiar su titulo a "Consulta de Empleados", centrarlo y cambiarle el tamaño a 200 de ancho y 400 de alto.
Luego arrastrar el control CheckedListBox y llamarlo: "clbCampo", ubicarlo en la posición 12,12 y cambiarle el tamaño a 160 de ancho y 334 de alto.
Escribir el siguiente código en el formulario:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Reflection; //PropertyInfo
namespace DataGridView_Columnas_Dinamicas
{
public partial class frmConsultaEmpleado : Form
{
List<beEmpleado> lbeEmpleado;
DataGridView dgvEmpleado;
public frmConsultaEmpleado()
{
InitializeComponent();
}
private void listarCampos(object sender, EventArgs e)
{
brEmpleado obrEmpleado = new brEmpleado();
lbeEmpleado = obrEmpleado.listar();
if (lbeEmpleado != null&&lbeEmpleado.Count>0)
{
dgvEmpleado = new DataGridView();
dgvEmpleado.Location =
new Point(clbCampo.Left + clbCampo.Width + 20, clbCampo.Top);
dgvEmpleado.Size = new Size(100, clbCampo.Height);
dgvEmpleado.AutoGenerateColumns = false;
dgvEmpleado.DataSource = lbeEmpleado;
this.Controls.Add(dgvEmpleado);
PropertyInfo[] propiedades = lbeEmpleado[0].GetType().GetProperties();
clbCampo.CheckOnClick = true;
clbCampo.BeginUpdate();
foreach(PropertyInfo propiedad in propiedades)
{
clbCampo.Items.Add(propiedad.Name);
}
clbCampo.EndUpdate();
}
}
private void configurarColumnas(object sender, ItemCheckEventArgs e)
{
CheckState estado = e.NewValue;
string campo = clbCampo.SelectedItem.ToString();
string tipo = lbeEmpleado[0].GetType().GetProperty(campo).PropertyType.ToString();
if (estado.Equals(CheckState.Checked))
{
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.Name = campo;
col.DataPropertyName = campo;
col.HeaderText = campo;
col.Width=tipo.Contains("String")?140:80;
dgvEmpleado.Columns.Add(col);
dgvEmpleado.Width += col.Width;
this.Width = clbCampo.Left + clbCampo.Width + 20 + dgvEmpleado.Width + 50;
}
else
{
if (dgvEmpleado.Columns.Count > 0)
{
DataGridViewColumn col = dgvEmpleado.Columns[campo];
dgvEmpleado.Columns.Remove(col);
dgvEmpleado.Width -= col.Width;
if(dgvEmpleado.Columns.Count>0)
this.Width = clbCampo.Left + clbCampo.Width + 20 + dgvEmpleado.Width + 50;
else this.Width = clbCampo.Left + clbCampo.Width + 20;
}
}
this.CenterToScreen();
}
}
}
Nota: La primera función controladora: "listarCampos" esta asociada al evento "Load" del formulario y la segunda función controladora: "configurarColumnas" esta asociada al evento "ItemCheck" del CheckedListBox.
Ejecutar y Probar el Formulario Creado
Grabar la aplicación, compilar y ejecutar. En el evento load se cargan los empleados en una lista de objetos y se llena el CheckedListBox con los nombres de los campos usando Reflection.
Seleccionar cualquier campo y aparecerá la grilla a la derecha con el tamaño adecuado, además el formulario ajusta su ancho automáticamente y se centra.
Por ejemplo, aquí esta la imagen al seleccionar las 2 primeras columnas: IdEmpleado y Apellido.
Esta otra imagen es cuando se seleccionan las 4 columnas: IdEmpleado, Apellido, Nombre y Fecha Nac.
Finalmente, quitar cada columna y observar que nuevamente se ajusta el ancho de la grilla y del formulario, además que cuando no se selecciona ninguna columna se regresa al ancho original del formulario.
Comentario Final
En esta demostración vimos como podemos agregar columnas dinámicamente usando el método Add de la propiedad Columns del DataGridView y también como eliminarlas usando el método Remove de la misma propiedad Columns. Finalmente, como trabajamos con Listas de Objetos, usamos Reflection para obtener las propiedades de la clase.
Espero les guste y va dedicado a todos los desarrolladores que creen que "lo interesante es muy difícil y toma mucho tiempo".
Trataremos de publicar otro post similar pero para ASP .NET: WebForms y MVC.
Descarga
Demo10_DataGridView_Columnas_Dinamicas