lunes, 14 de julio de 2014

El Demo del Día: DataGridView con Columnas Dinámicas en WinForms

DataGridView con Columnas Dinámicas en WinForms

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; }
    }
}

Crear la Clase de Datos

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);
        }
    }
}

Crear la Clase con las Reglas del Negocio

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.
En la siguiente figura se muestra el diseño de como quedaría el formulario:


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

El Libro del Día: MCPD Exam Ref 70-519

El Libro del Día: 2014-07-14

Titulo: MCPD Exam Ref 70-519 Web Applications NET Framework 4
Autor: Tony Northrup
Editorial: Microsoft
Nro Paginas: 302

Capítulos:
Chapter 1 Designing the Application Architecture
Chapter 2 Designing the User Experience
Chapter 3 Designing Data Strategies and Structures
Chapter 4 Designing Security Architecture and Implementation
Chapter 5 Preparing for and Investigating Application Issues
Chapter 6 Designing a Deployment Strategy

Descarga:
https://drive.google.com/file/d/0B_u1rzdqYCnzZGlJX3VFSmFoUTg/edit?usp=sharing