viernes, 27 de febrero de 2015

El Demo del Día: Graficar en el ListView de WinForms

Graficar en el ListView de WinForms

En este post veremos como crear graficos dentro de un ListView en Windows Forms, por ejemplo, para listar los empleados con su foto y una imagen con un número de estrellas que muestran su desempeño.

Requerimiento

Se necesita mostrar imágenes en los sub elementos de un elemento de un ListView. Por defecto, este muestra solo textos pero no imagenes.

Solución

- Configurar la Propiedad OwnerDraw en true del ListView
- Programar los eventos DrawColumnHeader, DrawItem y DrawSubItem para dibujar las cabeceras, las filas y pintar los sub elementos (textos e imágenes cargadas de archivos de disco).

Crear una Aplicación Windows Forms en C#

En Visual Studio crear un proyecto Windows Forms en C# con el nombre de "ListView_Grafico", luego cambiar el nombre del formulario a "frmEmpleado".

Crear una carpeta llamada "Imagenes" y agregar 9 archivos jpg con el nombre de 1.jpg, 2jpg, hasta 9.jpg con las fotos de los empleados. También conseguir una imagen "Rating.png" de 334 x 281 conteniendo el rating de desempeño, similar a la siguiente figura:



Nota: Vamos a usar un solo archivo (sprite) para mostrar los desempeños que van del 1 al 4.

Crear la Clase Entidad para el Empleado

Crear la clase "beEmpleado" escribiendo el siguiente código:

namespace ListView_Grafico
{
    public class beEmpleado
    {
        public int IdEmpleado { get; set; }
        public string Nombres { get; set; }
        public int Desempeño { get; set; }
    }
}

Programar el Formulario con el ListView

Regresar al formulario "frmEmpleado" y agregar un ListView llamado "lvwEmpleado" el cual debe estar acoplado a todo el formulario cuyo tamaño es de 600 de ancho x 550 de alto y esta centrado, similar a la siguiente figura:


Escribir el siguiente código en el formulario:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;

namespace ListView_Grafico
{
    public partial class frmEmpleado : Form
    {
        List<beEmpleado> lbeEmpleado;
        Bitmap bmp;
        Rectangle rect;        
        string ruta = @"C:\Data\NET\Practicas\WinForms\ListView_Grafico\Imagenes\";

        public frmEmpleado()
        {
            InitializeComponent();
        }

        private void crearImagenRating()
        {
            string archivo = String.Format("{0}Rating.png",ruta);
            bmp = new Bitmap(archivo);
        }

        private void crearColumnasListView()
        {
            lvwEmpleado.Columns.Add("Id", 50);
            lvwEmpleado.Columns.Add("Nombre del Empleado", 220);
            lvwEmpleado.Columns.Add("Foto", 100);
            lvwEmpleado.Columns.Add("Desempeño", 100);
        }

        private void configurarListView()
        {
            //Configurar el Alto de las Filas
            ImageList imgList = new ImageList ();
            imgList.ImageSize = new Size(1, 50);
            lvwEmpleado.SmallImageList = imgList;
            //Configurar la Apariencia
            lvwEmpleado.View = View.Details;
            lvwEmpleado.FullRowSelect = true;
            lvwEmpleado.HotTracking = true;
            //Configurar el Pintado
            lvwEmpleado.OwnerDraw = true;         
            lvwEmpleado.DrawColumnHeader += 
            new DrawListViewColumnHeaderEventHandler(dibujarCabecera);
            lvwEmpleado.DrawItem += 
            new DrawListViewItemEventHandler(dibujarFila);
            lvwEmpleado.DrawSubItem += 
            new DrawListViewSubItemEventHandler(dibujarCelda);
        }

        private void obtenerEmpleados()
        {
            lbeEmpleado = new List<beEmpleado>();
            lbeEmpleado.Add(new beEmpleado { IdEmpleado = 1, 
            Nombres = "Nadine Heredia", Desempeño = 2 });
            lbeEmpleado.Add(new beEmpleado { IdEmpleado = 2, 
            Nombres = "Ollanta Humala", Desempeño = 2 });
            lbeEmpleado.Add(new beEmpleado { IdEmpleado = 3, 
            Nombres = "Valeria Maza", Desempeño = 4 });
            lbeEmpleado.Add(new beEmpleado { IdEmpleado = 4, 
            Nombres = "Cristina Fernandez", Desempeño = 3 });
            lbeEmpleado.Add(new beEmpleado { IdEmpleado = 5, 
            Nombres = "Alan Garcia", Desempeño = 1 });
            lbeEmpleado.Add(new beEmpleado { IdEmpleado = 6, 
            Nombres = "Alberto Fujimori", Desempeño = 2 });
            lbeEmpleado.Add(new beEmpleado { IdEmpleado = 7, 
            Nombres = "Alejandro Toledo", Desempeño = 3 });
            lbeEmpleado.Add(new beEmpleado { IdEmpleado = 8, 
            Nombres = "Inocencio Perez", Desempeño = 1 });
            lbeEmpleado.Add(new beEmpleado { IdEmpleado = 9, 
            Nombres = "Luis Dueñas", Desempeño = 4 });
        }

        private void llenarListViewEmpleados()
        {
            ListViewItem fila;
            foreach (beEmpleado obeEmpleado in lbeEmpleado)
            {                
                fila = lvwEmpleado.Items.Add(obeEmpleado.IdEmpleado.ToString());
                fila.SubItems.Add(obeEmpleado.Nombres);
                fila.SubItems.Add(obeEmpleado.IdEmpleado.ToString());
                fila.SubItems.Add(obeEmpleado.Desempeño.ToString());
            }
        }

        private void crearListView(object sender, EventArgs e)
        {
            crearImagenRating();
            crearColumnasListView();
            configurarListView();
            obtenerEmpleados();
            llenarListViewEmpleados();            
        }

        private void dibujarCabecera(object sender, DrawListViewColumnHeaderEventArgs e)
        {
            e.Graphics.FillRectangle(Brushes.Black, e.Bounds);
            StringFormat sf= new StringFormat();
            sf.Alignment=StringAlignment.Center;
            sf.LineAlignment = StringAlignment.Center;
            e.Graphics.DrawString(e.Header.Text, new Font("Arial", 10, FontStyle.Bold),
            Brushes.White, e.Bounds, sf);
        }

        private void dibujarFila(object sender, DrawListViewItemEventArgs e)
        {
            rect = new Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, 50);
            if ((e.State & ListViewItemStates.Selected) != 0)
            {
                e.Graphics.FillRectangle(Brushes.Black, rect);
                e.DrawFocusRectangle();
            }
        }

        private void dibujarCelda(object sender, DrawListViewSubItemEventArgs e)
        {
            if (e.ColumnIndex > -1)
            {
                rect = new Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width-20, 50);
                if (e.ColumnIndex < 2)
                {
                    Font fuente = new Font("Arial", 8);
                    Brush brocha = Brushes.Black;
                    StringFormat sf = new StringFormat();
                    sf.LineAlignment = StringAlignment.Center;
                    bool seleccionado = (e.ItemState & ListViewItemStates.Selected) != 0;
                    if (seleccionado)
                    {
                        brocha = Brushes.White;
                        fuente = new Font("Arial", 10, FontStyle.Bold);
                    }
                    e.Graphics.DrawString(e.SubItem.Text, fuente, brocha, rect,sf);
                    return;
                }
                else
                {
                    if (e.ColumnIndex == 2)
                    {
                        using (MemoryStream ms = new MemoryStream(obtenerFoto(e.SubItem.Text)))
                        {
                            Image img = Image.FromStream(ms);
                            e.Graphics.DrawImage(img, rect);
                        }
                    }
                    else
                    {
                        int n=int.Parse(e.SubItem.Text);
                        int h = 281/4;
                        int y=h*(n-1);
                        rect = new Rectangle(0, y, 334, h);
                        Image img = bmp.Clone(rect, bmp.PixelFormat);
                        e.Graphics.DrawImage(img, e.Bounds.X, e.Bounds.Y, 100, 40);
                    }
                }
            }
        }

        private byte[] obtenerFoto(string idEmpleado)
        {            
            string archivo = String.Format("{0}{1}.jpg", ruta, idEmpleado);
            byte[] foto = File.ReadAllBytes(archivo);
            return foto;
        }
    }
}

Nota: La función principal es "crearListView" y esta asociada al evento "Load" del formulario, la cual llama a casi todos los métodos usados.

Una de los problemas que se presento al momento de desarrollar fue aumentar el alto de cada fila, lo cual se logra asociando un ImageList a la propiedad SmallImageList del ListView, para lo cual se le da un tamaño al ImageList mediante su propiedad "ImageSize", por ejemplo, le damos el alto de 50.

Probar la Aplicación Windows Forms

Grabar la aplicación y ejecutarla con F5. Se mostrará la siguiente ventana:


Observar que la cabecera del ListView tiene fondo negro, texto de color blanco en negrita y centrado horizontal y alineado vertical al centro.

Los elementos del ListView cuando no están seleccionados tienen fondo blanco, texto de color negro y están alineados verticalmente al centro.

Al pasar el mouse por cada elemento este se selecciona (propiedad HotTracking=true) y el fondo es negro y el color del texto es blanco en negrita y con fuente mas grande.

En la tercera columna se muestran las fotos obtenidas de los archivos de acuerdo al código del empleado y finalmente, en la última columna se presenta una sección del archivo de Rating, el cual se dividió lógicamente en 5 filas y de acuerdo al desempeño se obtiene solo una fila.

Comentario Final

En este breve post he compartido como crear gráficos dentro de un ListView para mejorar su apariencia, hay controles de terceros muy buenos como el ObjectListView que permiten mostrar todo tipo de imágenes, y lo que quería era demostrar que también podemos hacer lo mismo configurando la propiedad OwnerDraw y programando en los eventos DrawColumnHeader, DrawItem y DrawSubItem.

Descarga

jueves, 26 de febrero de 2015

El Libro del Día: .NET Technology Guide for Business Applications

El Libro del Día: 2015-02-26

Titulo: .NET Technology Guide for Business Applications
Autor: Cesar de la Torre, David Carmona
Editorial: Microsoft Press
Nro Paginas: 70

Capítulos:
1. Key takeaways
2. Purpose of this guide
3. Overview
4. Emerging application patterns
5. Established application patterns
6. Conclusions
Appendix A: Silverlight migration paths
Appendix B: Positioning data-access technologies

Descarga:
NET_Technology_Guide_for_Business_Applications

miércoles, 25 de febrero de 2015

El Libro del Día: C# Multithreaded and Parallel Programming

El Libro del Día: 2015-02-25

Titulo: C# Multithreaded and Parallel Programming
Autor: Rodney Ringler
Editorial: Packt
Nro Paginas: 345

Capítulos:
Chapter 1: Understanding Multiprocessing and Multiple Cores
Chapter 2: Looking at Multithreaded Classes - BackgroundWorker
Chapter 3: Thread Class – Heavyweight Concurrency in C#
Chapter 4: Advanced Thread Processing
Chapter 5: Lightweight Concurrency - Task Parallel Library (TPL)
Chapter 6: Task-based Parallelism
Chapter 7: Data Parallelism
Chapter 8: Debugging Multithreaded Applications with Visual Studio
Chapter 9: Pipeline and Producer-consumer Design Patterns
Chapter 10: Parallel LINQ - PLINQ
Chapter 11: The Asynchronous Programming Model

Descarga:
C#_Multithreaded_and_Parallel_Programming

martes, 24 de febrero de 2015

El Libro del Día: Learning AngularJS Animations

El Libro del Día: 2015-02-24

Titulo: Learning AngularJS Animations
Autor: Richard Keller
Editorial: Packt
Nro Paginas: 182

Capítulos:
Chapter 1: Getting Started
Chapter 2: Understanding CSS3 Transitions and Animations
Chapter 3: Creating Our First Animation in AngularJS
Chapter 4: JavaScript Animations in AngularJS
Chapter 5: Custom Directives and the $animate Service
Chapter 6: Animations for Mobile Devices
Chapter 7: Staggering Animations
Chapter 8: Animations' Performance Optimization

Descarga:
Learning_AngularJS_Animations

lunes, 23 de febrero de 2015

Entrenamiento - Charla Tecnológica ISILTECH el 03 de Marzo

Charla Tecnológica ISILTECH el 03 de Marzo

Les adjunto el banner de la charla de este 03 de Marzo en ISILTECH, son 5 charlas gratuitas de 3:30 pm a 8:30 pm:


Para inscribirse solo hay que registrarse en la siguiente dirección:
Inscripción_Charla_ISILTECH

Recuerden que la capacidad es limitada al tamaño del auditorio y que solo se considerará a los primeros inscritos.

Más detalles en el Facebook de ISILTECH:
Facebook_ISILTECH

Saludos.

El Libro del Día: AngularJS Services

El Libro del Día: 2015-02-23

Titulo: AngularJS Services
Autor: Jim Lavin
Editorial: Packt
Nro Paginas: 153

Capítulos:
Chapter 1: The Need for Services
Chapter 2: Designing Services
Chapter 3: Testing Services
Chapter 4: Handling Cross-cutting Concerns
Chapter 5: Data Management
Chapter 6: Mashing in External Services
Chapter 7: Implementing the Business Logic
Chapter 8: Putting It All Together

Descarga:
AngularJS_Services

viernes, 20 de febrero de 2015

El Demo del Día: Graficar Datos en el Cliente en ASP.NET MVC con JSON, Canvas y JavaScript

Graficar Datos en el Cliente en ASP.NET MVC con JSON, Canvas y JavaScript

Después de un par de semanas sin publicar un demo, les presento un post muy interesante de como trabajar en forma desconectada en el cliente para realizar consultas y gráficos de datos en ASP.NET MVC para lo cual usaremos jQuery, JSON, Canvas y JavaScript.

Requerimiento

Se desea crear una aplicación web que permita consultar los productos por categoría y mostrar los datos mas gráficos de diferentes tipos: Barras, Columnas, Lineas y Pie, pero el requisito es que sea desconectada tanto del Servidor de Datos como del Servidor Web, es decir, el filtro para la consulta y el gráfico se hará en el cliente.

Solución

- Crear una aplicación en ASP.NET MVC con un método de acción que se conecte a la BD una sola vez y devuelva un objeto con 2 listas: categorías y productos.
- Devolver una vista al cliente con la lista de categorías y la lista de tipos de gráficos.
- Ni bien carga la vista en el cliente hacer una llamada asíncrona usando $.ajax de jQuery para llamar a un método de acción que devuelva la lista de productos en formato JSON.
- Cuando el usuario seleccione una Categoría o seleccione un Tipo de Gráfico se llamará a una función JavaScript que filtrará los datos por la categoría seleccionada y los presentará en una tabla además lo dibujará en un Canvas.

Crear el Procedimiento Almacenado en la Base de Datos de SQL Server

Para el ejemplo usaremos la Base de Datos Northwind de SQL Server, en la cual crearemos el siguiente Procedimiento almacenado:

Create Procedure [dbo].[uspCategoriesProductsListar]
As
Select CategoryID,CategoryName From Categories
Select ProductID,ProductName,SupplierID,CategoryID,UnitPrice,UnitsInStock From Products

Crear la Librería de Clases Entidades del Negocio

Crear la librería de clases llamada: "Northwind.Librerias.EntidadesNegocio" y agregar la clase "beCategoria.cs":

namespace Northwind.Librerias.EntidadesNegocio
{
    public class beCategoria
    {
        public int IdCategoria { get; set; }
        public string Nombre { get; set; }
    }
}

Agregar otra clase a la librería llamada "beProducto.cs":

namespace Northwind.Librerias.EntidadesNegocio
{
    public class beProducto
    {
        public int IdProducto { get; set; }
        public string Nombre { get; set; }
        public int IdProveedor { get; set; }
        public int IdCategoria { get; set; }
        public decimal PrecioUnitario { get; set; }
        public short Stock { get; set; }
    }
}

Finalmente, agregar una clase llamada "beCategoriaProducto.cs" que agrupe las 2 listas:

using System;
using System.Collections.Generic;

namespace Northwind.Librerias.EntidadesNegocio
{
    public class beCategoriaProducto
    {
        public List<beCategoria> ListaCategoria { get; set; }
        public List<beProducto> ListaProducto { get; set; }
    }
}

Grabar y compilar la Librería de Entidades del Negocio.

Crear la Librería de Acceso a Datos

Crear la librería de clases llamada: "Northwind.Librerias.AccesoDatos", referenciar a la librería de clases de entidades creada anteriormente y agregar la clase "daCategoriaProducto.cs" y escribir el siguiente código:

using System;
using System.Collections.Generic; //List
using System.Data; //CommandType, CommandBehavior
using System.Data.SqlClient; //SqlConnection, SqlCommand, SqlDataReader
using Northwind.Librerias.EntidadesNegocio; //beCategoriaProducto, beCategoria, beProducto

namespace Northwind.Librerias.AccesoDatos
{
    public class daCategoriaProducto
    {
        public beCategoriaProducto obtenerListas(SqlConnection con)
        {
            beCategoriaProducto obeCategoriaProducto = new beCategoriaProducto();
            List<beCategoria> lbeCategoria = null;
            List<beProducto> lbeProducto = null;

            SqlCommand cmd = new SqlCommand("uspCategoriesProductsListar", con);
            cmd.CommandType = CommandType.StoredProcedure;
            SqlDataReader drd = cmd.ExecuteReader();
            if (drd != null)
            {
                lbeCategoria = new List<beCategoria>();
                beCategoria obeCategoria;
                int posIdCat = drd.GetOrdinal("CategoryID");
                int posNomCat = drd.GetOrdinal("CategoryName");
                while (drd.Read())
                {
                    obeCategoria = new beCategoria();
                    obeCategoria.IdCategoria = drd.GetInt32(posIdCat);
                    obeCategoria.Nombre = drd.GetString(posNomCat);
                    lbeCategoria.Add(obeCategoria);
                }
                obeCategoriaProducto.ListaCategoria = lbeCategoria;
                if (drd.NextResult())
                {
                    lbeProducto = new List<beProducto>();
                    int posIdProducto = drd.GetOrdinal("ProductID");
                    int posNombre = drd.GetOrdinal("ProductName");
                    int posIdProveedor = drd.GetOrdinal("SupplierID");
                    int posIdCategoria = drd.GetOrdinal("CategoryID");
                    int posPrecioUnitario = drd.GetOrdinal("UnitPrice");
                    int posStock = drd.GetOrdinal("UnitsInStock");
                    beProducto obeProducto;
                    while (drd.Read())
                    {
                        obeProducto = new beProducto();
                        obeProducto.IdProducto = drd.GetInt32(posIdProducto);
                        obeProducto.Nombre = drd.GetString(posNombre);
                        obeProducto.IdProveedor = drd.GetInt32(posIdProveedor);
                        obeProducto.IdCategoria = drd.GetInt32(posIdCategoria);
                        obeProducto.PrecioUnitario = drd.GetDecimal(posPrecioUnitario);
                        obeProducto.Stock = drd.GetInt16(posStock);
                        lbeProducto.Add(obeProducto);
                    }
                    obeCategoriaProducto.ListaProducto = lbeProducto;
                }
                drd.Close();
            }
            return (obeCategoriaProducto);
        }
    }
}

Grabar y compilar la librería de acceso a datos creada.

Crear la Librería de Reglas del Negocio

Crear la librería de clases llamada: "Northwind.Librerias.ReglasNegocio", referenciar a la librería de clases de entidades y también a la de acceso a datos, luego agregar la clase "brCategoriaProducto.cs" y escribir el siguiente código:

using System;
using System.Configuration; //ConfigurationManager
using System.Data.SqlClient; //SqlConnection
using System.Collections.Generic; //List
using Northwind.Librerias.EntidadesNegocio; //beCategoriaProducto
using Northwind.Librerias.AccesoDatos; //daCategoriaProducto

namespace Northwind.Librerias.ReglasNegocio
{
    public class brCategoriaProducto:brGeneral
    {
        public beCategoriaProducto obtenerListas()
        {
            beCategoriaProducto obeCategoriaProducto = null;
            string CadenaConexion = ConfigurationManager.ConnectionStrings["conNW"]
                                                     .ConnectionString;
            using (SqlConnection con = new SqlConnection(CadenaConexion))
            {
                try
                {
                    con.Open();
                    daCategoriaProducto odaCategoriaProducto = new daCategoriaProducto();
                    obeCategoriaProducto = odaCategoriaProducto.obtenerListas(con);
                }
                catch (SqlException ex)
                {
                    //grabarLog(ex);
                }
                catch (Exception ex)
                {
                    //grabarLog(ex);
                }
            } //con.Close(); con.Dispose();
            return (obeCategoriaProducto);
        }
    }
}

Nota: También es necesario hacer una referencia a la librería "System.Configuration" para leer la cadena de conexión definida en la aplicación.

Grabar la librería de reglas de negocio creada y compilarla.

Crear una Aplicación Web de ASP.NET MVC4 en C#

Seleccionar un nuevo proyecto de tipo: "Aplicación web de ASP.NET MVC4" y escribir como nombre "Canvas_GraficosDatos", luego seleccionar la opción "Vacio" y como motor de vista "Razor" y "Aceptar".

Crear un Controlador para el Producto

Clic derecho a la carpeta Controllers y seleccionar "Agregar" y luego "Controlador", llamarle al archivo "ProductoController.cs" y en opciones de plantillas dejarlo en plantilla vacía (Vaciar Controlador MVC). Luego escribir el siguiente código:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Northwind.LibBusEntities;
using Northwind.LibBusRules;

namespace HT03_Canvas_GraficosDatos.Controllers
{
    public class ProductoController : Controller
    {
        public ActionResult Lista()
        {
            List<string> tipoGrafico = new List<string>();
            tipoGrafico.Add("Barras");
            tipoGrafico.Add("Columnas");
            tipoGrafico.Add("Lineas");
            tipoGrafico.Add("Pie");
            ViewBag.Tipo = tipoGrafico;
            brCategoriaProducto obrCategoriaProducto = new brCategoriaProducto();
            beCategoriaProducto obeCategoriaProducto = obrCategoriaProducto.Listar();
            Session["Productos"] = obeCategoriaProducto.ListaProducto;
            return View(obeCategoriaProducto.ListaCategoria);
        }

        public JsonResult Filtro()
        {
            JsonResult rpta;
            List<beProducto> lbeProducto = (List<beProducto>)Session["Productos"];
            rpta = Json(lbeProducto, JsonRequestBehavior.AllowGet);
            return (rpta);
        }
    }
}

Crear una Hoja de Estilos para la Vista

Primero crear una carpeta llamada "Content", luego clic derecho "Agregar" y luego "Hoja de estilos" y como nombre llamarle "ACME.css" y escribir el siguiente código:

body {
    background-color:lightgray;
}
.Titulo {
    background-color:black;
    color:white;
    font-size:x-large;
    text-transform:uppercase;
}
.Subtitulo {
    background-color:white;
    color:black;
    font-size:large;
    text-transform:capitalize;
    font-weight:bold;
}
.AnchoTotal {
    width:100%;
}
.FilaCabecera {
    background-color:gray;
    color:white;
}
.FilaDatos {
    background-color:white;
    color:black;
}
.Cuadro {
    background-color:white;
    border-style:double;
}

Crear la Vista Productos para mostrar los datos

Ir al controlador y ubicarse sobre el método "Lista" (acción), clic derecho y seleccionar "Agregar vista", seleccionar el check "Crear una vista fuertemente tipada" y escribir el siguiente código:

@using Northwind.LibBusEntities
@model List<beCategoria>
@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Lista</title>
    <link href="~/Content/Styles/ACME.css" rel="stylesheet" />
    <script src="~/Scripts/jquery-1.7.1.min.js"></script>
    <script src="~/Scripts/Rutinas.js"></script>
</head>
<body>
    <div>
        <table class="AnchoTotal">
            <tr class="Titulo">
                <td colspan="2">
                   Graficar Datos en el Cliente en ASP.NET MVC con JSON, Canvas y JavaScript
                </td>
            </tr>
            <tr class="Subtitulo">
                <td colspan="2">Gráfico de Precios de Productos x Categoria</td>
            </tr>
            <tr>
                <td style="width:40%">
                    Selecciona una Categoria: @Html.DropDownList("idCategoria",
                    new SelectList(Model,"Codigo","Nombre"))
                </td>
                <td style="width:60%">
                    Selecciona el Tipo de Grafico: @Html.DropDownList("tipo",
                    new SelectList(ViewBag.Tipo))
                </td>
            </tr>
            <tr>
                <td style="vertical-align:top">
                    <table class="AnchoTotal">
                        <thead>
                            <tr class="FilaCabecera">
                                <td style="width:80%">Nombre del Producto</td>
                                <td style="width:20%">Stock</td>
                            </tr>
                        </thead>
                        <tbody id="tbProducto">
                        </tbody>
                    </table>
                </td>
                <td>
                    <canvas id="canvas" width="600" height="400" class="Cuadro"/>
                </td>
            </tr>
        </table>
    </div>
</body>
</html>

Crear el archivo JavaScript con el código cliente

Antes que nada crear una carpeta llamada "Scripts" y arrastrar del explorador de Windows el archivo de jQuery: "jquery-1.7.1.min.js", luego agregar un archivo de JavaScript llamado "Rutinas.js" y escribir el siguiente código:

$(document).ready(function () {
    var rpta;
    var cboCategoria = document.getElementById("idCategoria");    
    cboCategoria.onchange = function () { crearTablaGrafico(); }
    var cboTipo = document.getElementById("tipo");
    cboTipo.onchange = function () { crearTablaGrafico(); }
    $.ajax({
        type: "post",
        url: "/Producto/Filtro",
        contentType: "application/json;charset=utf-8",
        dataType: "json",
        success: exito,
        error: error
    });

    function crearTablaGrafico() {
        var idCategoria = cboCategoria.value * 1;
        var tbProducto = document.getElementById("tbProducto");
        var tipo = cboTipo.value;
        var contenido = "";
        var canvas = document.getElementById("canvas");
        var contexto = canvas.getContext("2d");
        if (contexto != null) graficar(rpta);
        function graficar(rpta) {
            //Crear Degradado
            var deg = contexto.createLinearGradient(0, 0, canvas.width, canvas.height);
            deg.addColorStop(1, "aqua");
            deg.addColorStop(0.1, "blue");
            //Dibujar Rectangulo Degradado
            contexto.fillStyle = deg;
            contexto.fillRect(0, 0, canvas.width, canvas.height);
            //Variables para los calculos
            var x = 10;
            var y = 20;
            var valor = 0;
            var escala = 0;
            var maximo = 0;
            var total = 0;
            if (tipo != "Pie") {
                maximo = calcularMaximo(idCategoria);
                if (tipo == "Barras") escala = Math.abs((canvas.width - 180) / maximo);
                else {
                    escala = Math.abs((canvas.height - 120) / maximo);
                    x = 50;
                }
            }
            else total = calcularTotal(idCategoria);
            var centroX = Math.floor(canvas.width / 2);
            var centroY = Math.floor(canvas.height / 2);
            var radio = Math.floor(canvas.width / 4);
            var anguloInicio = 0;
            var arco = 0;
            var anguloFin = 0;
            //Dibujar el grafico de acuerdo al tipo
            for (i = 0; i < rpta.length; i++) {
                if (rpta[i].IdCategoria == idCategoria) {
                    contenido += "<tr class='FilaDatos'><td>" + rpta[i].Nombre + "</td>" +
                        "<td class='Derecha'>" + rpta[i].PrecioUnitario + "</td></tr>";
                    switch(tipo)
                    {
                        case "Barras":
                            x = 10;
                            contexto.fillStyle = "white";
                            contexto.font = "10px arial";
                            contexto.fillText(rpta[i].Nombre, x, y);
                            x = 150;
                            valor = rpta[i].PrecioUnitario * escala;
                            contexto.fillStyle = "yellow";
                            contexto.fillRect(x, y - 10, valor, 10);
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].PrecioUnitario, x + valor + 5, y);
                            y = y + 20;
                            break;
                        case "Columnas":
                            contexto.save();
                            contexto.translate(x, canvas.height - 10);
                            contexto.rotate(-(Math.PI / 3));
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].Nombre, 0, 0);
                            contexto.restore();
                            contexto.fillStyle = "yellow";
                            valor = (canvas.height - 100 - (rpta[i].PrecioUnitario*escala));
                            contexto.fillRect(x, valor, 10, rpta[i].PrecioUnitario * escala);
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].PrecioUnitario, x, valor - 10);
                            x += 40;
                            break;
                        case "Lineas":
                            contexto.save();
                            contexto.translate(x, canvas.height - 10);
                            contexto.rotate(-(Math.PI / 3));
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].Nombre, 0, 0);
                            contexto.restore();                            
                            contexto.strokeStyle = "yellow";
                            contexto.lineWidth = 3;
                            if (x== 50) {
                                contexto.beginPath();
                                valor = Math.floor(canvas.height - 100 - (rpta[i].PrecioUnitario) * escala);
                                contexto.moveTo(x, valor);
                            }
                            valor = Math.floor(canvas.height - 100 - (rpta[i].PrecioUnitario * escala));
                            contexto.lineTo(x, valor);
                            contexto.stroke();                
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].PrecioUnitario, x, valor - 10);
                            x += 40;
                            break;
                        case "Pie":
                            arco=(rpta[i].PrecioUnitario * 2 * Math.PI) / total;
                            anguloFin = anguloInicio + arco;
                            contexto.save();
                            contexto.beginPath();
                            contexto.moveTo(centroX, centroY);                            
                            contexto.arc(centroX, centroY, radio, anguloInicio, anguloFin, false);
                            contexto.fillStyle = '#' + Math.floor(Math.random() * 16777215).toString(16);
                            contexto.fill();
                            contexto.closePath();
                            contexto.restore();                            
                            contexto.save();
                            contexto.translate(centroX, centroY);
                            contexto.rotate(anguloInicio);
                            x = Math.floor(canvas.width * 0.15) - 10;
                            y = Math.floor(canvas.height * 0.05);
                            contexto.fillStyle = "white";
                            contexto.fillText(rpta[i].PrecioUnitario, x, y);
                            contexto.restore();
                            anguloInicio = anguloFin;
                            break;
                    }
                }
                tbProducto.innerHTML = contenido;
            }
        }
    }

    function calcularTotal(idCategoria) {
        var total = 0;
        for (i = 0; i < rpta.length; i++) {
            if (rpta[i].IdCategoria == idCategoria) {
                total += rpta[i].PrecioUnitario;
            }
        }
        return total;
    }

    function calcularMaximo(idCategoria) {
        var max = 0;
        for (i = 0; i < rpta.length; i++) {
            if (rpta[i].IdCategoria == idCategoria) {
                if (rpta[i].PrecioUnitario>max) max = rpta[i].PrecioUnitario;
            }
        }
        return max;
    }

    function exito(data) {
        rpta = data;
        crearTablaGrafico();
    }

    function error(data) {
        alert(data.status + " - " + data.statusText);
    }
});

Nota: La función asociada al $(document).ready se ejecuta ni bien carga la pagina y se llama en forma asíncrona al método de acción "Filtro" que devuelve un JSON con la lista de productos y por JavaScript se crea el filtro, se muestra la tabla y el gráfico de acuerdo a la categoría seleccionada y al tipo de gráfico.

Modificar el archivo web.config para incluir la cadena de conexión

<configuration>
  <connectionStrings>
    <add name="conNW" providerName="SQLServer"
         connectionString="uid=UsuarioNW;pwd=123456;
         data source=DSOFT\Sqlexpress;database=Northwind"/>
  </connectionStrings>
  .....
</configuration>

Configurar el inicio y ejecutar la aplicación web

Para probar la aplicación web debemos configurar el inicio para lo cual nos vamos a la carpeta "App_Start" y abrimos el archivo "RouteConfig.cs" cambiando el nombre del controlador y la acción tal como sigue: controller = "Producto", action = "Lista".

Ejecución y Pruebas de la Aplicación Web ASP.NET MVC

Finalmente, grabar y pulsar F5 para ejecutar la aplicación, mostrándose el resultado similar a la siguiente figura:


Por defecto se muestra la categoría "Bebidas" y en el tipo de gráfico "Barras", cambiar el tipo de gráfico a "Columnas" y se verá la siguiente figura:


Probar ahora con el gráfico de "Lineas" y se mostrará algo similar a la siguiente figura:


Finalmente, cambiar el tipo de gráfico a "Pie" y se verá lo siguiente:


Comentario Final

En este post hemos visto como trabajar en forma desconectada, y también hemos aprendido a crear grillas (tablas) y gráficos (canvas) sin usar controles, sino mediante código JavaScript.

Además hemos hecho llamada asíncrona mediante Ajax de jQuery y retornado los datos como un arreglo en notación JavaScript (JSON).

Esta forma de trabajo (desconectada) es muy útil en aplicaciones móviles donde la conexión al servidor web es limitada, por lo cual una sola vez se trae los datos y todo se maneja en el cliente (Browser).

Este demo es uno de los tantos que se realiza en el programa Web Developer. Espero les sirva.

Descarga
DemoDia_GraficoDatosCliente

Entrenamiento - Nuevos Inicios y Charlas ISILTECH

Nuevos Inicios y Charlas ISILTECH

Nuevos Inicios ISILTECH

A los interesados en capacitarse en .NET y otros cursos de Desarrollo, ISILTECH está aperturando nuevos inicios entre ellos los de .NET que yo dictaré son:

1. Visual Studio 2013 Developer

Duración: 24 semanas (144 horas)
Horario: Martes y Jueves de 19:00 a 22:00 horas (Bloque B)
Inicio: Martes 7 de Abril
Lugar: ISIL San Isidro – Av. Salaverry 2625

Cursos del Programa
1. Desarrollo de Aplicaciones con .NET Framework 4.5.1.
2. Desarrollo de Aplicaciones con ADO .NET y POO
3. Desarrollo de Aplicaciones Windows Forms.
4. Desarrollo de Aplicaciones Web con ASP .NET Web Forms.
5. Desarrollo de Aplicaciones Web con ASP .NET MVC.
6. Desarrollo de Aplicaciones con Windows Presentation Foundation (WPF).
7. Desarrollo de Servicios con Windows Communication Foundation (WCF).
8. Desarrollo de Aplicaciones de Flujos de Trabajo con Windows Workflow Foundation (WWF).

Nota: Es recomendable que para llevar llevar este programa se tenga conocimientos básicos de .NET

2. NET Web Applications Developer

Duración: 20 semanas (120 horas)
Horario: Sábado de 08:00 a 14:00 horas
Inicio: 11 de Abril
Lugar: ISIL San Isidro – Av. Salaverry 2625

Cursos del Programa
Curso 1: Desarrollando Aplicaciones Web con ASP .NET MVC 5
Curso 2: Programando en el Cliente con Javascript, jQuery, AJAX y CSS3
Curso 3: Programando en el Cliente con HTML5
Curso 4: Desarrollando Aplicaciones Orientadas a Servicios con WCF y Web API
Curso 5: Desarrollando Aplicaciones Web Móviles con ASP .NET, HTML5 y jQuery

Nota: Es recomendable que para llevar este programa primero se haya llevado el PECI.NET

Mayor información sobre estos programas u otros inicios, les paso el Link
Calendario_PECI_2015_01

Charlas en ISILTECH para Desarrolladores

Además, para promocionar los inicios esta confirmada un día de charlas técnicas gratuitas, para lo cual tendrán que confirmar su participación con Marketing. El día será el Martes 3 de Marzo de 3:30 pm a 8:30 pm y los temas serán:

1. Tema: JavaScript en el cliente y en el servidor con Node.js
    Hora: 3:30 pm
    Expositor: David Chura Olazabal

2. Tema: Desarrollo de Software con enfoque en el valor de negocio y calidad usando métodos ágiles
     Hora: 4:30 pm
     Expositor: Guino Henostroza Macedo

3.  Tema: Desarrollo de Aplicaciones Web en C# con ASP.NET MVC, Razor, jQuery y AJAX
     Hora: 5:30 pm
     Expositor: Luis Dueñas Huaroto

4.  Tema: Cloud Computing con Amazon Web Services (AWS)
     Hora: 6:30 pm
    Expositor: Carlos Chávez Villafuerte

5. Tema: Desarrollo en Android
    Hora: 7:30 pm
    Expositor: David Chura Olazabal

El día Lunes estaré publicando el Banner y el mail a donde tienen que confirmar. La capacidad es limitada y será en el Auditorio de ISIL de San Isidro (Pershing con Salaverry).

Ya saben que ISILTECH tiene una oferta variada de programas para Desarrolladores, Buenos Instructores y lo mejor a un Precio Sin Competencia (con respecto al nivel y calidad).

Los esperamos.

PD: Los que ya llevaron algún programa PECI.NET conmigo pueden hacer sus comentarios para animar a las personas que todavía dudan donde inscribirse para capacitarse.

El Libro del Día: AngularJS Essentials

El Libro del Día: 2015-02-20

Titulo: AngularJS Essentials
Autor: Rodrigo Branas
Editorial: Packt
Nro Paginas: 180

Capítulos:
Chapter 1: Getting Started with AngularJS
Chapter 2: Creating Reusable Components with Directives
Chapter 3: Data Handling
Chapter 4: Dependency Injection and Services
Chapter 5: Scope
Chapter 6: Modules
Chapter 7: Unit Testing
Chapter 8: Automating the Workflow

Descarga:
AngularJS_Essentials

jueves, 19 de febrero de 2015

El Libro del Día: CoffeeScript Application Development

El Libro del Día: 2015-02-19

Titulo: CoffeeScript Application Development
Autor: Ian Young
Editorial: Packt
Nro Paginas: 258

Capítulos:
Chapter 1: Running a CoffeeScript Program
Chapter 2: Writing Your First Lines of CoffeeScript
Chapter 3: Building a Simple Application
Chapter 4: Improving Our Application
Chapter 5: Classes in CoffeeScript
Chapter 6: Refactoring with Classes
Chapter 7: Advanced CoffeeScript Usage
Chapter 8: Going Asynchronous
Chapter 9: Debugging
Chapter 10: Using CoffeeScript in More Places
Chapter 11: CoffeeScript on the Server

Descarga:
CoffeeScript_Application_Development

miércoles, 18 de febrero de 2015

El Libro del Día: Hands-On Sencha Touch 2

El Libro del Día: 2015-02-18

Titulo: Hands-On Sencha Touch 2
Autor: Lee Boonstra
Editorial: O'Reilly
Nro Paginas: 344

Capítulos:
Part I. Sencha Touch Essentials
1. Introduction to Sencha Touch
2. Installation
3. The Fundamentals
4. The Class System
5. The Layout System
Part II. Building the FindACab App
6. Structured Code
7. Data Models
8. Remote Connections (Server Proxies)
9. Data Stores
10. Offline Storage (Client Proxies)
11. View Components
12. Forms
13. Themes and Styles
14. Builds
A. Help with iOS Certificates and Provisioning
B. Custom Stylesheet for the FindACab App

Descarga:
Hands_On_Sencha_Touch_2

martes, 17 de febrero de 2015

El Libro del Día: HTML5 Boilerplate Web Development

El Libro del Día: 2015-02-17

Titulo: HTML5 Boilerplate Web Development
Autor: Divya Manian
Editorial: Packt
Nro Paginas: 174

Capítulos:
Chapter 1: Before We Begin
Chapter 2: Starting Your Project
Chapter 3: Creating Your Site
Chapter 4: Adding Interactivity and Completing Your Site
Chapter 5: Customizing the Apache Server
Chapter 6: Making Your Site Better
Chapter 7: Automate Deployment With the Build Script
Appendix: You Are an Expert, Now What

Descarga:
HTML5_Boilerplate_Web_Development

viernes, 13 de febrero de 2015

El Libro del Día: HTML5 Cookbook

El Libro del Día: 2015-02-13

Titulo: HTML5 Cookbook
Autor: Christopher Schmitt, Kyle Simpson
Editorial: O'Reilly
Nro Paginas: 284

Capítulos:
1. Fundamental Syntax and Semantics
2. Progressive Markup and Techniques
3. Forms
4. Native Audio
5. Native Video
6. Microdata and Custom Data
7. Accessibility
8. Geolocation
9. <canvas>
10. Advanced HTML5 JavaScript
Appendix: HTML5 Resources

Descarga:
HTML5_Cookbook

jueves, 12 de febrero de 2015

El Libro del Día: HTML5 Up and Running

El Libro del Día: 2015-02-12

Titulo: HTML5 Up and Running
Autor: Mark Pilgrim
Editorial: O'Reilly
Nro Paginas: 222

Capítulos:
1. How Did We Get Here?
2. Detecting HTML5 Features
3. What Does It All Mean?
4. Let’s Call It a Draw(ing Surface)
5. Video on the Web
6. You Are Here (And So Is Everybody Else)
7. The Past, Present, and Future of Local Storage for Web Applications
8. Let’s Take This Offline
9. A Form of Madness
10. “Distributed,” “Extensibility,” and Other Fancy Words
Appendix: The All-in-One Almost-Alphabetical Guide to Detecting Everything

Descarga:
HTML5_Up_and_Running

miércoles, 11 de febrero de 2015

El Libro del Día: Mobile and Web Messaging

El Libro del Día: 2015-02-11

Titulo: Mobile and Web Messaging
Autor: Jeff Mesnil
Editorial: O'Reilly
Nro Paginas: 183

Capítulos:
1. Introduction
Part I. STOMP
2. Mobile Messaging with STOMP
3. Web Messaging with STOMP
4. Advanced STOMP
5. Beyond STOMP
Part II. MQTT
6. Mobile Messaging with MQTT
7. Web Messaging with MQTT
8. Advanced MQTT
Part III. Appendixes
A. Apache ActiveMQ
B. Mosquitto

Descarga:
Mobile_and_Web_Messaging

martes, 10 de febrero de 2015

El Libro del Día: jQuery 2.0 Development Cookbook

El Libro del Día: 2015-02-10

Titulo: jQuery 2.0 Development Cookbook
Autor: Leon Revill
Editorial: Packt
Nro Paginas: 410

Capítulos:
Chapter 1: Document Object Model Manipulation
Chapter 2: Interacting with the User by Making Use of jQuery Events
Chapter 3: Loading and Manipulating Dynamic Content with AJAX and JSON
Chapter 4: Adding Attractive Visuals with jQuery Effects
Chapter 5: Form Handling
Chapter 6: User Interface
Chapter 7: User Interface Animation
Chapter 8: Understanding Plugin Development
Chapter 9: jQuery UI
Chapter 10: Working with jQuery Mobile

Descarga:
jQuery_2.0_Development_Cookbook

lunes, 9 de febrero de 2015

El Libro del Día: JavaScript Security

El Libro del Día: 2015-02-09

Titulo: JavaScript Security
Autor: Y.E Liang
Editorial: Packt
Nro Paginas: 112

Capítulos:
Chapter 1: JavaScript and the Web
Chapter 2: Secure Ajax RESTful APIs
Chapter 3: Cross-site Scripting
Chapter 4: Cross-site Request Forgery
Chapter 5: Misplaced Trust in the Client
Chapter 6: JavaScript Phishing

Descarga:
JavaScript_Security

sábado, 7 de febrero de 2015

El Libro del Día: Mastering JavaScript Design Patterns

El Libro del Día: 2015-02-07

Titulo: Mastering JavaScript Design Patterns
Autor: Simon Timms
Editorial: Packt
Nro Paginas: 290

Capítulos:
Chapter 1: Designing for Fun and Profit
Part 1: Classical Design Patterns
Chapter 2: Organizing Code
Chapter 3: Creational Patterns
Chapter 4: Structural Patterns
Chapter 5: Behavioral Patterns
Part 2: Other Patterns
Chapter 6: Functional Programming
Chapter 7: Model View Patterns
Chapter 8: Web Patterns
Chapter 9: Messaging Patterns
Chapter 10: Patterns for Testing
Chapter 11: Advanced Patterns
Chapter 12: ES6 Solutions Today
Appendix: Conclusion

Descarga:
Mastering_JavaScript_Design_Patterns

viernes, 6 de febrero de 2015

El Demo del Día: Agrupación de Celdas del DataGridView en WinForms

Agrupación de Celdas del DataGridView en WinForms

Requerimiento

Se necesita agrupar los valores repetidos de ciertas columnas del DataGridView en una Aplicación Windows en .NET, por ejemplo para ver los productos por categorías o por proveedores sin tener que repetir varias veces el mismo valor.

Solución

Crearemos una clase personalizada llamada Grilla que herede del DataGridView y que sobre escriba los eventos "OnCellPainting" y "OnCellFormatting".
En el primer evento (CellPainting) colocaremos los bordes solo cuando el valor de la columna sea diferente y en el segundo evento (CellFormatting) borraremos los valores repetidos de cada columna.
En ambos casos solo se considera si es una columna a combinar, para lo cual crearemos una propiedad llamada "GroupColumns".

Crear el Procedimiento Almacenado en la Base de Datos de SQL Server

Para el ejemplo usaremos la Base de Datos Northwind de SQL Server, en la cual crearemos el siguiente Procedimiento almacenado:

Create Procedure uspProductsListarStock
As
Select p.ProductName,p.CategoryID,c.CategoryName,UnitsInStock
From Products p
Inner Join Categories c On c.CategoryID=p.CategoryID
Order By 2,3,1

Crear una Aplicación Windows Forms en C#

En Visual Studio crear un proyecto Windows Forms en C# con el nombre de "DataGridView_Agrupado", luego cambiar el nombre del formulario a "frmListaProductos".

Crear la Clase Entidad del Negocio

Crear la clase beProducto escribiendo el siguiente código:

namespace DataGridView_Agrupado
{
    public class beProducto
    {
        public int IdCategoria { get; set; }
        public string NombreCategoria { get; set; }
        public string NombreProducto { get; set; }
        public short Stock { get; set; }
    }
}

Crear la Clase de Acceso a Datos

Crear la clase daProducto escribiendo el siguiente código:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace DataGridView_Agrupado
{
    public class daProducto
    {
        public List<beProducto> listarStock(SqlConnection con)
        {
            List<beProducto> lbeProducto = null;
            SqlCommand cmd = new SqlCommand("uspProductsListarStock", con);
            cmd.CommandType = CommandType.StoredProcedure;
            SqlDataReader drd = cmd.ExecuteReader(CommandBehavior.SingleResult);
            if (drd != null)
            {
                lbeProducto = new List<beProducto>();
                int posNombreProducto = drd.GetOrdinal("ProductName");
                int posIdCategoria = drd.GetOrdinal("CategoryID");
                int posNombreCategoria = drd.GetOrdinal("CategoryName");
                int posStock = drd.GetOrdinal("UnitsInStock");
                beProducto obeProducto;
                while (drd.Read())
                {
                    obeProducto = new beProducto();
                    obeProducto.NombreProducto = drd.GetString(posNombreProducto);
                    obeProducto.IdCategoria = drd.GetInt32(posIdCategoria);
                    obeProducto.NombreCategoria = drd.GetString(posNombreCategoria);
                    obeProducto.Stock = drd.GetInt16(posStock);
                    lbeProducto.Add(obeProducto);
                }
                drd.Close();
            }
            return (lbeProducto);
        }
    }
}

Nota: Tiene que haberse creado el Procedimiento Almacenado "uspProductsListarStock" en la BD Northwind.

Crear la Clase de Reglas del Negocio

Crear la clase brProducto escribiendo el siguiente código:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;

namespace DataGridView_Agrupado
{
    public class brProducto
    {
        public List<beProducto> listarStock()
        {
            List<beProducto> lbeProducto = null;
            string CadenaConexion = ConfigurationManager.ConnectionStrings 
                                                      ["conNW"].ConnectionString;
            using (SqlConnection con = new SqlConnection(CadenaConexion))
            {
                try
                {
                    con.Open();
                    daProducto odaProducto = new daProducto();
                    lbeProducto = odaProducto.listarStock(con);
                }
                catch (Exception ex)
                {
                    //grabarLog(ex);
                }
            }
            return (lbeProducto);
        }
    }
}

Nota: Hay que hacer referencia a la librería "System.Configuration.dll" para usar la clase ConfigurationManager para leer el archivo de configuración.

Crear una Clase para Combinar las Celdas del DataGridView

Crear la clase "Grilla" escribiendo el siguiente código:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace DataGridView_Agrupado
{
    public class Grilla : DataGridView
    {
        public string[] GroupColumns { get; set; }

        private bool esRepetidoValorCelda(int fila, int columna)
        {
            bool exito = false;
            int c = fila - 1;
            dynamic valorCelda = this.Rows[fila].Cells[columna].Value;
            dynamic valorComparar;
            while (c > -1)
            {
                valorComparar = this.Rows[c].Cells[columna].Value;
                if (valorCelda.Equals(valorComparar))
                {
                    exito = true;
                    break;
                }
                c--;
            }
            return (exito);
        }

        private bool esColumnaCombinar(string nombreColumna)
        {
            bool exito = false;
            foreach (string columna in GroupColumns)
            {
                if (columna.Equals(nombreColumna))
                {
                    exito = true;
                    break;
                }
            }
            return (exito);
        }

        protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
        {
            base.OnCellPainting(e);
            e.AdvancedBorderStyle.Bottom = DataGridViewAdvancedCellBorderStyle.None;
            if (e.RowIndex < 1 || e.ColumnIndex < 0) return;
            string nombreColumna = this.Columns[e.ColumnIndex].Name;
            bool esCombinada = (GroupColumns!=null && esColumnaCombinar(nombreColumna));
            if ((esCombinada && !esRepetidoValorCelda(e.RowIndex, e.ColumnIndex)) 
            || !esCombinada) e.AdvancedBorderStyle.Top = AdvancedCellBorderStyle.Top;
            else e.AdvancedBorderStyle.Top = DataGridViewAdvancedCellBorderStyle.None;
        }

        protected override void OnCellFormatting(DataGridViewCellFormattingEventArgs e)
        {
            base.OnCellFormatting(e);
            bool esNumero = (e.Value is Int16||e.Value is Int32||e.Value is Int64||e.Value is int
                                          ||e.Value is decimal);
            if (esNumero)
            {
                e.CellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
                bool esDecimal = (e.Value is decimal);
                if (esDecimal) e.CellStyle.Format = "n2";
            }
            string nombreColumna = this.Columns[e.ColumnIndex].Name;
            bool esCombinada = (GroupColumns != null && esColumnaCombinar(nombreColumna));
            if (esCombinada&&esRepetidoValorCelda(e.RowIndex, e.ColumnIndex))
            {
                e.Value = string.Empty;
                e.FormattingApplied = true;
            }
        }
    }
}

Nota: La clase Grilla hereda del DataGridView y sobre escribe los eventos "CellPainting" y "CellFormatting". Ademas se crea una propiedad llamada "GroupColumns" para especificar las columnas que se desean combinar o agrupar sus valores repetidos, para eso creamos 2 funciones: "esRepetidoValorCelda" y "esColumnaCombinar".

Usar la Clase Grilla en el Formulario

Regresar al formulario "frmListaProductos" y escribir el siguiente código:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

namespace DataGridView_Agrupado
{
    public partial class frmListaProductos : Form
    {
        public frmGraficoProductos()
        {
            InitializeComponent();
        }

        private void listarProductos(object sender, EventArgs e)
        {
            brProducto obrProducto = new brProducto();
            List<beProducto> lbeProducto = obrProducto.listarStock();
            Grilla dgvProducto = new Grilla();
            dgvProducto.Dock = DockStyle.Fill;
            dgvProducto.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            //dgvProducto.GroupColumns = new string[] { "IdCategoria", "NombreCategoria"};
            dgvProducto.DataSource = lbeProducto;
            this.Controls.Add(dgvProducto);
        }
    }
}

Nota: En el evento "load" del formulario se llama a la función "listarProductos" que obtiene los datos de los productos y lo almacena en una lista de objetos, luego esta se enlaza al control Grilla creado.
Inicialmente la línea de código donde se configura la propiedad GroupColumns esta comentada.

Modificar el archivo App.Config para incluir la Cadena de Conexión

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
      <add name="conNW" providerName="System.Data.SqlClient" connectionString="uid=UsuarioNW;pwd=123456;data source=DSOFT\Sqlexpress;initial catalog=Northwind"/>
    </connectionStrings>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>

Probar la Aplicación Windows Forms

Grabar la aplicación y ejecutarla con F5. Se mostrará la siguiente ventana:


Observar que los valores de las columnas IdCategoria y NombreCategoria salen repetidos, que es la forma normal como funciona el DataGridView.

Detener la ejecución de la aplicación, descomentar la línea comentada:
dgvProducto.GroupColumns = new string[] { "IdCategoria", "NombreCategoria"};

Probar nuevamente y se mostrará una ventana similar a la siguiente figura:


Observar que las columnas IdCategoria y NombreCategoria no se repite los valores ya que se agrupo mediante la propiedad "GroupColumns".

Detener nuevamente la ejecución de la aplicación y eliminar el campo "NombreCategoria" de la propiedad GroupColumns, tal como sigue:
dgvProducto.GroupColumns = new string[] { "IdCategoria" };

Probar nuevamente y se mostrará una ventana similar a la siguiente figura:


Observar que solo la primera columna esta agrupada. Si deseas puedes aumentar nuevos productos con nombres iguales y aumentar a la propiedad "GroupColumns" el campo "NombreProducto".

Comentario Final

En este post hemos visto como agrupar celdas repetidas en una o varias columnas del DataGridView de Windows Forms para lo cual sobre escribimos los eventos CellPainting y CellFormatting que son los 2 principales eventos de dicho control.

En realidad la agrupación de valores repetidos No ha combinado las celdas sino solo ha borrado los valores repetidos y ha puesto el borde superior. Si deseas combinar las celdas se tendría que programar la escritura en el evento Paint causando demora al renderizar, es decir, la técnica que estamos usando es la mas eficiente aunque no se vea fusionada la celda.

Descarga