Mostrando las entradas con la etiqueta Rectangle. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Rectangle. Mostrar todas las entradas

martes, 24 de marzo de 2015

El Demo del Día: Graficar Datos en el Servidor en ASP.NET MVC

Graficar Datos en el Servidor en ASP.NET MVC con llamada asíncrona usando Ajax Form y Vista Parcial

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 conectado o en línea, es decir, el filtro por categoría debe hacerse directo de la base de datos y el gráfico debe crearse dinámicamente en el servidor web.

Solución

- Crear una aplicación en ASP.NET MVC con un método de acción que se conecte a la BD para traer la lista de categorías mas los tipos de gráficos.
- Crear un formulario MVC Ajax que realice una llamada asíncrona cada vez que se selecciona una categoría que llama a una acción que haga la consulta en base de datos y que cree el gráfico dinámicamente devolviendo una vista parcial.
- Para crear los diferentes tipos de gráficos usaremos la librería System.Drawing y el gráfico lo almacenaremos en un MemoryStream, a partir del cual obtendremos su array de bytes y lo almacenaremos en un ViewBag llamado Imagen con el contenido de la imagen en formato base64.

Crear los Procedimientos Almacenados en la Base de Datos Northwind de SQL Server

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

Create Procedure [dbo].[uspCategoriesListar]
As
Select CategoryID,CategoryName From Categories
Go

Create Procedure [dbo].[uspProductsListarPorCategoria]
@CategoryId int
As
Select ProductID,ProductName,SupplierID,
CategoryID,UnitPrice,UnitsInStock
From Products
Where CategoryId=@CategoryId

Crear la Librería de Clases de 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; }
    }
}

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 "daCategoria.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; //beCategoria

namespace Northwind.Librerias.AccesoDatos
{
    public class daCategoria
    {
        public List<beCategoria> listar(SqlConnection con)
        {
            List<beCategoria> lbeCategoria = null;

            SqlCommand cmd = new SqlCommand("uspCategoriesListar", 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);
                }
                drd.Close();
            }
            return (lbeCategoria);
        }
    }
}

Agregar una nueva clase a la librería llamada "daProducto.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; //beProducto

namespace Northwind.Librerias.AccesoDatos
{
    public class daProducto
    {
        public List<beProducto> listarPorCategoria(SqlConnection con,int idCategoria)
        {
            List<beProducto> lbeProducto = null;

            SqlCommand cmd = new SqlCommand("uspProductsListarPorCategoria", con);
            cmd.CommandType = CommandType.StoredProcedure;

            SqlParameter par = cmd.Parameters.Add("@CategoryId", SqlDbType.Int);
            par.Direction = ParameterDirection.Input;
            par.Value = idCategoria;

            SqlDataReader drd = cmd.ExecuteReader(CommandBehavior.SingleResult);
            if (drd != null)
            {
                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);
                }
                drd.Close();
            }

            return (lbeProducto);
        }
    }
}

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 "brCategoria.cs" y escribir el siguiente código:

using System;
using System.Data.SqlClient; //SqlConnection
using System.Collections.Generic; //List
using Northwind.Librerias.EntidadesNegocio; //beCategoria
using Northwind.Librerias.AccesoDatos; //daCategoria

namespace Northwind.Librerias.ReglasNegocio
{
    public class brCategoria
    {
        public List<beCategoria> listar()
        {
            List<beCategoria> lbeCategoria = null;
            string CadenaConexion = ConfigurationManager.ConnectionStrings["conNW"]
                                                     .ConnectionString;
            using (SqlConnection con = new SqlConnection(CadenaConexion))
            {
                try
                {
                    con.Open();
                    daCategoria odaCategoria = new daCategoria();
                    lbeCategoria = odaCategoria.listar(con);
                }
                catch (SqlException ex)
                {
                    //grabarLog(ex);
                }
                catch (Exception ex)
                {
                    //grabarLog(ex);
                }
            } //con.Close(); con.Dispose();
            return (lbeCategoria);
        }
    }
}

Agregar una nueva clase a la librería llamada "brProducto.cs" y escribir el siguiente código:

using System;
using System.Data.SqlClient; //SqlConnection
using System.Collections.Generic; //List
using Northwind.Librerias.EntidadesNegocio; //beProducto
using Northwind.Librerias.AccesoDatos; //daProducto

namespace Northwind.Librerias.ReglasNegocio
{
    public class brProducto
    {
        public List<beProducto> listarPorCategoria(int idCategoria)
        {
            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.listarPorCategoria(con,idCategoria);
                }
                catch (SqlException ex)
                {
                    //grabarLog(ex);
                }
                catch (Exception ex)
                {
                    //grabarLog(ex);
                }
            } //con.Close(); con.Dispose();
            return (lbeProducto);
        }
    }
}

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 "Demo10", luego seleccionar la opción "Vacio" y como motor de vista "Razor" y "Aceptar".

Nota: Antes de iniciar con el código hay que referenciar a las librerías creadas, sobre todo a la Regla de Negocio y las Entidades de Negocio.

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.IO;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Northwind.Librerias.EntidadesNegocio;
using Northwind.Librerias.ReglasNegocio;

namespace Demo10.Controllers
{
    public class ProductoController : Controller
    {        
        public ActionResult Lista()
        {
            brCategoria obrCategoria = new brCategoria();
            List<beCategoria> lbeCategoria = obrCategoria.listar();
            ViewBag.Categorias = lbeCategoria;
            List<string> tiposGrafico = new List<string>();
            tiposGrafico.Add("Barras");
            tiposGrafico.Add("Columnas");
            tiposGrafico.Add("Lineas");
            tiposGrafico.Add("Pie");
            ViewBag.TiposGrafico = tiposGrafico;
            return View();
        }

        public PartialViewResult ConsultaGrafico(int idCategoria,string tipoGrafico)
        {
            brProducto obrProducto = new brProducto();
            List<beProducto> lbeProducto = obrProducto.listarPorCategoria(idCategoria);
            int ancho = 800;
            int alto = 500;
            Bitmap bmp = new Bitmap(ancho, alto);
            Graphics grafico = Graphics.FromImage(bmp);
            Rectangle rect = new Rectangle(0, 0, ancho, alto);
            LinearGradientBrush degradado = new LinearGradientBrush
            (rect, Color.Aqua, Color.Blue, LinearGradientMode.BackwardDiagonal);
            grafico.FillRectangle(degradado, rect);
            int x, y;
            Font fuente=new Font("Arial",10);
            Pen lapiz=new Pen(Brushes.Yellow,5);
            StringFormat formato = new StringFormat(StringFormatFlags.DirectionVertical);
            switch (tipoGrafico)
            {
                case "Barras":
                    x = 20;
                    y=20;
                    foreach(beProducto obeProducto in lbeProducto)
                    {
                        x = 20;
                        grafico.DrawString(obeProducto.Nombre, fuente, 
                        Brushes.White, x, y);
                        x = 200;
                        rect = new Rectangle(x, y, obeProducto.Stock, 20);
                        grafico.FillRectangle(Brushes.Yellow, rect);
                        x = 220 + obeProducto.Stock;
                        grafico.DrawString(obeProducto.Stock.ToString(), fuente, 
                        Brushes.White, x, y);
                        y += (int)fuente.GetHeight() + 20;
                    }
                    break;
                case "Columnas":
                    x = 20;
                    y=350;
                    foreach(beProducto obeProducto in lbeProducto)
                    {
                        grafico.DrawString(obeProducto.Nombre, fuente, 
                        Brushes.White, x, y, formato);
                        alto = 340 - obeProducto.Stock;
                        rect = new Rectangle(x, alto, 20, obeProducto.Stock);
                        grafico.FillRectangle(Brushes.Yellow, rect);
                        grafico.DrawString(obeProducto.Stock.ToString(), fuente, 
                        Brushes.White, x, alto - 20);
                        x += 50;
                    }
                    break;
                case "Lineas":
                    x = 20;
                    y=350;
                    for(int i=0;i<lbeProducto.Count;i++)
                    {
                        grafico.DrawString(lbeProducto[i].Nombre, fuente, 
                        Brushes.White, x, y, formato);
                        if (i < lbeProducto.Count - 1)
                        {
                            grafico.DrawLine(lapiz, new Point(x, 340 - lbeProducto[i].Stock), 
                            new Point(x + 50, 340 - lbeProducto[i + 1].Stock));
                        }
                        grafico.DrawString(lbeProducto[i].Stock.ToString(), fuente, 
                        Brushes.White, x, 320 - lbeProducto[i].Stock);
                        x += 50;
                    }
                    break;
                case "Pie":
                    decimal total = lbeProducto.Sum(obj => obj.Stock);
                    float anguloInicio = 00.0F;
                    float anguloFin;
                    rect = new Rectangle(100, 10, ancho-200, alto - 20);
                    int R, G, B;
                    Color color;
                    Random oAzar = new Random();
                    for (int i = 0; i < lbeProducto.Count; i++)
                    {
                        R = oAzar.Next(255);
                        G = oAzar.Next(255);
                        B = oAzar.Next(255);
                        color = Color.FromArgb(R, G, B);
                        anguloFin = (float)((lbeProducto[i].Stock * 360) / total);
                        grafico.FillPie(new SolidBrush(color), rect, anguloInicio, anguloFin);
                        anguloInicio += anguloFin;
                    }
                    break;
            }
            byte[] imagen=null;
            using (MemoryStream ms = new MemoryStream())
            {
                bmp.Save(ms, ImageFormat.Png);
                imagen = ms.ToArray();
            }
            ViewBag.Imagen = String.Format("data:image/png;base64,{0}",
            Convert.ToBase64String(imagen));
            return PartialView("Grafico", lbeProducto);
        }
    }
}

Nota: Para el gráfico de Pie se esta creando un color al azar para cada producto.

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:gray;
}
.Titulo {
    background-color:black;
    color:white;
    font-size:larger;
}
.Subtitulo {
    background-color:white;
    color:black;
    font-size:large;
}
.AnchoTotal {
    width:100%;
}
.Centrado {
    text-align:center;
}
.FilaCabecera {
    background-color:lightgray;
    color:black;
}
.FilaDatos {
    background-color:white;
    color:black;
}

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", desmarcar todos los check ya que es una vista sin tipo y escribir el siguiente código:

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Lista</title>
    <link href="~/Content/ACME.css" rel="stylesheet" />
</head>
<body>
    <div>
        @using (Ajax.BeginForm("ConsultaGrafico", "Producto", 
         new AjaxOptions { HttpMethod = "get", InsertionMode = InsertionMode.Replace, 
         UpdateTargetId = "divGrafico" }, new { id = "frmGrafico" }))
        {
        <table class="AnchoTotal">
            <tr class="Titulo">
                <td colspan="2">Gráficar Datos en el Servidor en ASP.NET MVC 
                 con llamada asíncrona usando Ajax Form y Vista Parcial</td>
            </tr>
            <tr class="Subtitulo">
                <td colspan="2">Consulta de Productos por Categoría</td>
            </tr>
            <tr>
                <td style="width:40%">Seleccione la Categoría</td>
                <td style="width:60%">Seleccione el Tipo de Gráfico</td>
            </tr>
            <tr>
                <td>
                    @Html.DropDownList("idCategoria",
                     new SelectList(ViewBag.Categorias,"IdCategoria","Nombre"))
                </td>
                <td>
                    @Html.DropDownList("tipoGrafico",
                     new SelectList(ViewBag.TiposGrafico))
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <div id="divGrafico"></div>
                </td>
            </tr>
        </table>
        }
    </div>
    <script src="~/Scripts/jquery-1.8.2.min.js"></script>
    <script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
    <script src="~/Scripts/Rutinas.js"></script>
    <script>iniciarLista();</script>
</body>
</html>

Nota: Es mejor colocar los archivos de JavaScript al final de la página para renderizar mas rápido.

Crear la Vista Parcial con la Consulta y el Gráfico a Mostrar

Ir nuevamente al controlador y ubicarse sobre el método "Lista" (acción), clic derecho y seleccionar "Agregar vista", cambiar de nombre a la vista por "Grafico", seleccionar el check "Crear como vista parcial" y escribir el siguiente código:

@using Northwind.Librerias.EntidadesNegocio
@model List<beProducto>
<table class="AnchoTotal">
    <tr>
        <td style="vertical-align:top">
            <table class="AnchoTotal">
                <tr class="FilaCabecera">
                    <td style="width:200px">Nombre del Producto</td>
                    <td style="width:10px">Stock</td>
                </tr>
                @foreach (beProducto obeProducto in Model)
                {
                    <tr class="FilaDatos">
                        <td>@obeProducto.Nombre</td>
                        <td>@obeProducto.Stock</td>
                    </tr>
                }
            </table>
        </td>
        <td>
            <img src="@ViewBag.Imagen" width="800" height="500" />
        </td>
    </tr>
</table>

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.8.2.min.js" y "jquery.unobtrusive-ajax.min.js" (necesario para el Ajax Form), luego agregar un archivo de JavaScript llamado "Rutinas.js" y escribir el siguiente código:

function iniciarLista() {
    $("#frmGrafico").submit();
    var cboIdCategoria = document.getElementById("idCategoria");
    cboIdCategoria.onchange = function () { llamadaAsincrona(); };
    var cboTipoGrafico = document.getElementById("tipoGrafico");
    cboTipoGrafico.onchange = function () { llamadaAsincrona(); };
    function llamadaAsincrona() {
        $("#frmGrafico").submit();
    }
}

Nota: Cuando se carga la página se esta haciendo un submit al formulario "frmGrafico" via jQuery el cual es asíncrono por que el formulario en un Ajax Form. También ocurre lo mismo al seleccionar una categoría del combo.

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:


Nota: En el gráfico de Pie falta agregar una leyenda para distinguir cada producto de acuerdo al color generado. Esto queda como tarea para que aprendan mas sobre Drawing.

Comentario Final

En este post hemos visto como trabajar en forma conectada, y también hemos aprendido a crear gráficos dinámicamente usando la librería System.Drawing y las clases: Bitmap, Graphics, Rectangle, Font, Pen, StringFormat, Color, SolidBrush, las enumeraciones: Brushes y StringFormatFlags, también el Namespace: System.Drawing.Drawing2D para usar la clase: LinearGradientBrush (degradado).

En un anterior post vimos como crear gráficos en el Cliente usando HTML5 Canvas con JSON:

Aunque en apariencia los Demos son similares el anterior es desconectado de la base de datos y del servidor web, este demo es conectado o en línea para ver los últimos datos, por ejemplo muy útil para reportes gerenciales en línea.

Descarga del código

Video del Demo

Nota: El video es muy similar al código presentado en el Demo, pero la versión original era desconectada del servidor de datos y usaba una sesión, en cambio el demo es totalmente conectado, la diferencia solo es en el controller y es mínima.

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

viernes, 26 de septiembre de 2014

El Demo del Día: Visor de Imágenes con Zoom en WinForms

Visor de Imágenes con Zoom en WinForms

Este post es a pedido de un alumno que quiere una aplicación en Windows Forms que muestre una secuencia de Imágenes y pueda realizar el Zoom.

Crear la Aplicación Windows Forms

Primero crearemos una aplicación en Windows Forms en C# llamada: "VisorImagenesZoom".
Cambiar de nombre al formulario por el de "frmVisor", luego diseñar la pantalla como se muestra en la siguiente figura:


Nota: También hay que incluir un Timer llamado "tmrPreview" que cada 3 segundos muestre las imágenes del directorio seleccionado.

Escribir el siguiente código en el formulario:

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

namespace VisorImagenesZoom
{
    public partial class frmVisor : Form
    {
        private int c;
        private int n;
        private Point punto1;

        public frmVisor()
        {
            InitializeComponent();
        }

        private void abrirDirectorio(object sender, EventArgs e)
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            if (fbd.ShowDialog().Equals(DialogResult.OK))
            {
                txtDirectorio.Text = fbd.SelectedPath;
                IEnumerable<string> archivos = Directory.EnumerateFiles(fbd.SelectedPath, "*.jpg");
                lstArchivo.Items.Clear();
                lstArchivo.BeginUpdate();
                foreach (string archivo in archivos)
                {
                    lstArchivo.Items.Add(Path.GetFileNameWithoutExtension(archivo));
                }
                lstArchivo.EndUpdate();
                txtTotalArchivos.Text = lstArchivo.Items.Count.ToString();
                tmrPreview.Enabled = true;
                tmrPreview.Start();
            }
        }

        private void mostrarImagenes(object sender, EventArgs e)
        {
            lstArchivo.SelectedIndex = c;
            string archivo = Path.Combine(txtDirectorio.Text, lstArchivo.Items[c].ToString() + ".jpg");
            picPreview.LoadAsync(archivo);
            c++;
            if (c == lstArchivo.Items.Count) c = 0;
        }

        private void configurarZoom(object sender, MouseEventArgs e)
        {
            if (e.Button.Equals(MouseButtons.Left))
            {
                if (n == 0)
                {
                    punto1 = e.Location;
                    tmrPreview.Stop();
                    tmrPreview.Enabled = false;
                }
                else
                {
                    if (n == 1)
                    {
                        Point punto2 = e.Location;
                        string archivo = Path.Combine(txtDirectorio.Text,
                                                   lstArchivo.Items[c].ToString() + ".jpg");
                        Bitmap bmp = new Bitmap(picPreview.Image);
                        Bitmap bmpZoom = new Bitmap(bmp.Width, bmp.Height);
                        Graphics g = Graphics.FromImage(bmpZoom);
                        float ratioX = (bmp.Width/picPreview.Width);
                        float ratioY = (bmp.Height / picPreview.Height);
                        int posX = (int)(ratioX * punto1.X);
                        int posY = (int)(ratioY * punto1.Y);
                        int ancho = (int)(ratioX*(punto2.X-punto1.X));
                        int alto = (int)(ratioY * (punto2.Y - punto1.Y));
                        Rectangle srcRect = new Rectangle(posX, posY, ancho, alto);
                        Rectangle dstRect = new Rectangle(0, 0, bmpZoom.Width, bmpZoom.Height);
                        g.DrawImage(bmp, dstRect, srcRect, GraphicsUnit.Pixel);
                        picPreview.Image = bmpZoom;
                        tmrPreview.Enabled = true;
                        tmrPreview.Start();
                    }
                }
                n++;
                if (n == 2) n = 0;
            }
        }
    }
}

Nota: Para programar el Zoom se ha asociado al evento MouseClick del PictureBox llamado "picPreview" donde en el primer clic se captura la ubicación del punto y se guarda en una variable "punto1" y en el segundo clic se captura el punto2 y se crea un rectangulo con la imagen para el Zoom. Solo hay que tener en cuenta que el Rectangulo debe ser en base a la imagen (Bitmap) del PictureBox y No al tamaño del control (con propiedad SizeMode en StrechImage), por lo cual se crea las variables ratios.

Probar la Aplicación Creada

Grabar el proyecto y ejecutarlo con F5. Seleccionar un directorio donde se encuentren archivos jpg y se mostraran en la lista tal como aparece en la siguiente figura:


Cada 3 segundos cambiara la imagen y se seleccionara en la lista el archivo que se esta mostrando, dar el primer clic sobre la imagen y luego el segundo clic mostrará el preview de la imagen, tal como sigue:


Comentario Final

En este post hemos visto como mostrar una secuencia de imágenes usando un Timer y también como realizar el Zoom de una Imagen usando el método DrawImage de la clase Graphics para dibujar un rectángulo con la imagen especificada al dar dos clics, uno para marcar el punto de inicio y otro para marcar el punto final.

Descarga:
Demo19_VisorImagenesZoom