domingo, 30 de noviembre de 2014

El Libro del Día: Microsoft Sharepoint 2013 Developer Reference

El Libro del Día: 2014-11-30

Titulo: Microsoft Sharepoint 2013 Developer Reference
Autor: Paolo Pialorsi
Editorial: Microsoft
Nro Paginas: 794

Capítulos:
Part I GETTING STARTED
Chapter 1 Microsoft SharePoint 2013: A quick tour
Chapter 2 SharePoint data fundamentals
Part II DEVELOPING SHAREPOINT SOLUTIONS
Chapter 3 Data provisioning
Chapter 4 SharePoint features and solutions
Chapter 5 Server Object Model
Chapter 6 LINQ to SharePoint
Chapter 7 Client-side technologies
Part III DEVELOPING SHAREPOINT APPS
Chapter 8 SharePoint apps
Chapter 9 The new SharePoint REST API
Chapter 10 Remote event receivers
Part IV EXTENDING SHAREPOINT
Chapter 11 Developing Web Parts
Chapter 12 Customizing the UI
Chapter 13 Web templates
Chapter 14 Business Connectivity Services
Part V DEVELOPING WORKFLOWS
Chapter 15 Windows Workflow Foundation
Chapter 16 SharePoint workflow fundamentals
Chapter 17 Developing workflows
Chapter 18 Advanced workflows

Descarga:
Microsoft_Sharepoint_2013_Developer_Reference

sábado, 29 de noviembre de 2014

El Libro del Día: Microsoft Sharepoint 2013 App Development

El Libro del Día: 2014-11-29

Titulo: Microsoft Sharepoint 2013 App Development
Autor: Scot Hillier, Ted Pattison
Editorial: Microsoft
Nro Paginas: 204

Capítulos:
Chapter 1 Introducing SharePoint apps
Chapter 2 Client-side programming
Chapter 3 SharePoint app security
Chapter 4 Developing SharePoint apps

Descarga:
Microsoft_Sharepoint_2013_App_Development

viernes, 28 de noviembre de 2014

El Demo del Día: Preview de Imágenes en un TreeView de Directorios en WebForms

Preview de Imágenes en un TreeView de Directorios en WebForms

En este breve post veremos como mostrar los directorios de una cierta carpeta de la Aplicación Web, por ejemplo de las Imágenes. Para esto usaremos el control TreeView para mostrar jerárquicamente los directorios y el control Repeater para listar los archivos y ver las imágenes del directorio seleccionado.

Crear la Aplicación Web en ASP .NET Web Form

Crear un "Nuevo Sitio web vacío de ASP .NET" en C# llamado "TreeViewDirectorios_PreviewImagenes". Crear la siguiente estructura de carpetas en el proyecto:
- Estilos: Contiene el archivo de hoja de estilo ACME.css
- Imagenes: Contiene las carpetas Empleados y Productos
  - Empleados: Contiene archivos de los empleados del 01.jpg al 08..jpg
  - Productos: Contiene las siguientes carpetas
    - Bebidas: Contiene archivos jpg con bebidas.
    - Condimentos: Contiene archivos jpg con condimentos.
    - Confecciones: Contiene archivos jpg con confecciones.
- Paginas: Contiene el formulario web VisorImagenes.aspx

Crear el Archivo de Hoja de Estilo

En la carpeta "Estilos" agregar un archivo de hoja de estilo llamado "ACME.css" y escribir lo siguiente:

body {
    background-color:lightgray;
}
.Titulo {
    background-color:black;
    color:white;
    text-transform:uppercase;
    font-size:xx-large;
    font-weight:bold;
}
.Subtitulo {
    background-color:white;
    color:black;
    text-transform:capitalize;
    font-size:x-large;
    font-weight:bold;
}
.Imagen {
    width:120px;
    height:100px;
    border:solid;
    border-width:20px;
    border-color:lightgray;
}

.Imagen:hover {
    cursor:pointer;
    border:solid;
    border-width:20px;
    border-color:yellow;
}

Crear la Pagina como Formulario WebForm

En la carpeta "Paginas" agregar un formulario WebForm llamado: "VisorImagenes.aspx" y escribir lo siguiente:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="VisorImagenes.aspx.cs" Inherits="TreeViewDirectorios_PreviewImagenes.Paginas.VisorImagenes" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
    <link href="../Estilos/ACME.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <table>
            <tr class="Titulo">
                <td colspan="2">Preview de Imagenes en un TreeView de Directorios</td>
            </tr>
            <tr class="Subtitulo">
                <td colspan="2">Creado Por: Luis Duenas</td>
            </tr>
            <tr>
                <td style="vertical-align:top">
                    <asp:TreeView ID="tvwDirectorio" runat="server"
                        OnSelectedNodeChanged="mostrarImagenesDirectorio"/>
                </td>
                <td>
                    <asp:Repeater ID="rptImagen" EnableViewState="false" runat="server">
                        <ItemTemplate>
                            <img src="<%#obtenerUrl(Container.DataItem.ToString())%>"                              
                                title="<%#obtenerNombre(Container.DataItem.ToString())%>"
                                class="Imagen" alt="" />
                        </ItemTemplate>
                    </asp:Repeater>
                </td>
            </tr>
        </table>
    </div>
    </form>
</body>
</html>

Nota: Es importante que se desactive el ViewState del Repeater para dismunir el HTML generado en el cliente, ya que no es necesario, porque en el servidor se esta enlazando el control Repeater cada vez que se elige un directorio (optmización del HTML enviado al cliente).

El diseño de la pagina debe mostrase similar a la siguiente figura:

Escribir el siguiente código C# para la pagina:

using System;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;

namespace TreeViewDirectorios_PreviewImagenes.Paginas
{
    public partial class VisorImagenes : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                string ruta = Server.MapPath("../Imagenes");
                IEnumerable<string> listaDirectorios = Directory.EnumerateDirectories(ruta);
                TreeNode nodoRaiz = new TreeNode("Imagenes");
                tvwDirectorio.Nodes.Add(nodoRaiz);
                llenarNodos(nodoRaiz, listaDirectorios);
                tvwDirectorio.ExpandAll();
                tvwDirectorio.SelectedNodeStyle.BackColor = Color.Yellow;
                tvwDirectorio.SelectedNodeStyle.ForeColor = Color.Red;
            }
        }

        private void llenarNodos(TreeNode nodoPadre, IEnumerable<string> listaDirectorios)
        {
            foreach(string directorio in listaDirectorios)
            {
                TreeNode nodo = new TreeNode(Path.GetFileName(directorio));
                nodoPadre.ChildNodes.Add(nodo);
                IEnumerable<string> lista = 
                Directory.EnumerateDirectories(Path.Combine(Server.MapPath("../"), 
                nodo.ValuePath));
                if(lista.Count() > 0) llenarNodos(nodo, lista);
            }
        }

        protected void mostrarImagenesDirectorio(object sender, EventArgs e)
        {
            string directorio = Path.Combine(Server.MapPath("../"),
            tvwDirectorio.SelectedNode.ValuePath.Replace(@"/", @"\"));
            IEnumerable<string> archivos = Directory.EnumerateFiles(directorio, "*.jpg");
            rptImagen.DataSource = archivos;
            rptImagen.DataBind();
        }

        protected string obtenerUrl(string archivo)
        {
            return String.Format("../{0}/{1}", 
            tvwDirectorio.SelectedNode.ValuePath,Path.GetFileName(archivo));
        }

        protected string obtenerNombre(string archivo)
        {
            return Path.GetFileName(archivo);
        }
    }
}

Nota: Para leer directorios estamos usando Directory.EnumerateDirectories y para leer archivos estamos usando Directory.EnumerateFiles que es la forma mas eficiente de leer en .NET ya que solo crea un registro a la vez (consume menos memoria y es mas rápido).
Además se usa una función recursiva para llenar el TreeView con los directorios llamada "llenarNodos" que usa la propiedad ValuePath que obtiene la ruta del nodo actual.
También se definen 2 métodos protegidos: "obtenerUrl" y "obtenerNombre" que se usan en el HTML del Repeater.

Probar la Pagina Web

Guardar el Sitio Web, clic derecho a la Pagina "VisorImagenes.aspx" y seleccionar "Ver en el explorador". Se mostrará una ventana similar a la siguiente figura:


Observar como se muestra todas lar carpetas creadas dentro de la carpeta "Imagenes" en el TreeView, ahora seleccionar un directorio, por ejemplo "Empleados" y se mostrará las imágenes que esta contiene, similar a la siguiente figura:


Notar que al poner el mouse sobre una imagen se muestra como tooltip el nombre del archivo de imagen.

Ahora seleccionar otro directorio, por ejemplo "Bebidas" y disminuir el ancho del navegador (como si fuera una Tabla) y se muestra menos columnas pero el Diseño Web es Adaptativo (Responsive Web Design), tal como se ve en la siguiente figura:



Para finalizar, seleccionar otro directorio, por ejemplo "Condimentos" y disminuir aún mas el ancho del navegador (como si fuera un Teléfono Celular) y nuevamente el diseño se adapta mostrando una sola columna, tal como se ve en la siguiente figura:



Comentario Final

En este pequeño demo, hemos visto como llenar un TreeView en WebForms con los directorios de una cierta carpeta usando una función recursiva, además de como hacer el preview de su contenido usando un control Repeater y Plantillas de Datos con código incrustado en el servidor (las 2 funciones obtener). Además para mejorar la apariencia se hace usado hojas de estilo (CSS).

A los visitantes del Blog que no han sido alumnos míos y que a veces tienen pedidos que se hacen en clases en el programa PECI .NET o Web Developer, no es mi intención repetir lo que se ve en clases, sino, publicaríamos un Demo diario y el tiempo no me alcanzaría.

Saludos a todos y recuerden la frase: "Somos lo que pensamos de nosotros mismos".

Descarga


El Libro del Día: Professional Sharepoint 2013 Development

El Libro del Día: 2014-11-28

Titulo: Professional Sharepoint 2013 Development
Autor: Reza Alirezaei, Brendon Schwartz, Matt Ranlett, 
           Scot Hillier, Brian Wilson, Jeff Fried, Paul Swider
Editorial: Wrox
Nro Paginas: 820

Capítulos:
CHAPTER 1 Architectural Overview of SharePoint 2013
CHAPTER 2 What’s New in SharePoint 2013
CHAPTER 3 Developer Tools for SharePoint 2013
CHAPTER 4 Application Lifecycle Management in SharePoint 2013
CHAPTER 5 Introducing Windows Azure and SharePoint 2013 Integration
CHAPTER 6 Getting Started with Developing Apps in SharePoint 2013
CHAPTER 7 Further Developing Apps in SharePoint 2013
CHAPTER 8 Developing Social Applications in SharePoint 2013
CHAPTER 9 Building Search-Based Applications in SharePoint 2013
CHAPTER 10 Web Content Management
CHAPTER 11 Using InfoPath with SharePoint 2013
CHAPTER 12 Enterprise Document Management
CHAPTER 13 Introducing Business Connectivity Services
CHAPTER 14 Advanced Business Connectivity Services
CHAPTER 15 Workflow Development in SharePoint 2013
CHAPTER 16 Integrating Reporting Services
CHAPTER 17 Developing Excel Applications in SharePoint 2013
CHAPTER 18 PerformancePoint Dashboards
CHAPTER 19 Developing Applications with Access
APPENDIX Additional Help and Resources

Descarga:
Professional_Sharepoint_2013_Development

jueves, 27 de noviembre de 2014

El Libro del Día: Beginning Sharepoint 2013 Development

El Libro del Día: 2014-11-27

Titulo: Beginning Sharepoint 2013 Development
Autor: Steve Fox, Chris Johnson, Donovan Follette
Editorial: Wrox
Nro Paginas: 460

Capítulos:
PART I GETTING STARTED WITH SHAREPOINT 2013
CHAPTER 1 Introduction to SharePoint 2013
CHAPTER 2 Overview of the SharePoint 2013 App Model
CHAPTER 3 Developer Tooling for SharePoint 2013
CHAPTER 4 Understanding Your Development Options
CHAPTER 5 Overview of Windows Azure for SharePoint
PART II FUNDAMENTAL SHAREPOINT 2013 DEVELOPMENT BUILDING BLOCKS
CHAPTER 6 Developing, Integrating, and Building Applications in SharePoint 2013
CHAPTER 7 Packaging and Deploying SharePoint 2013 Apps
CHAPTER 8 Distributing SharePoint 2013 Apps
CHAPTER 9 Overview of the Client-Side Object Model and REST APIs
CHAPTER 10 Overview of OAuth in SharePoint 2013
PART III ADVANCED DEVELOPER TOPICS IN SHAREPOINT 2013
CHAPTER 11 Developing Integrated Apps for Office and SharePoint Solutions
CHAPTER 12 Remote Event Receivers in SharePoint 2013
CHAPTER 13 Building Line-of-Business Solutions Using Business Connectivity Services
CHAPTER 14 Developing Applications Using Office Services
CHAPTER 15 Developing Workflow Applications for SharePoint 2013

Descarga:
Beginning_Sharepoint_2013_Development

miércoles, 26 de noviembre de 2014

Entrenamiento - Primera Charla .NET (HTE ACEE)

Primera Charla .NET (HTE ACEE)

Solo para recordar que el día Miércoles 3 de Diciembre iniciamos una serie de charlas orientadas a mejorar la calidad de las aplicaciones de todos los profesionales dedicados al Desarrollo de Software, en especial en Microsoft .NET.

Actualmente, la forma como la mayoría de profesionales desarrolla no es la mas adecuada, sin embargo es usada como un Estándar en la mayoría de Empresas, las razones pueden ser diversas: por costumbre, legado de conocimientos, facilidad y no como debería ser, que es por eficiencia: Performance (Menos Consumo de Procesador y Menos Memoria) y Escalabilidad (Concurrencia).

Es por eso que existen Frameworks de todo tipo, para "Facilitar" el trabajo del desarrollador y No para hacerlo mas eficiente, tales como:
- Acesso a Datos: SQL Helper, Enterprise Library, Application Blocks, LINQ, Entity Framework, MyBatis, NHibernate, etc.
- Interface de Usuario: Telerik, Kendo, Infragistics, Dev Express, Component One, etc
- JavaScript: jQuery, AJAX Control Toolkit, etc
- HTML, CSS: Bootstrap, jQuery UI, Node.js, Extend.js, YUI, etc.

Si bien es cierto, estos Frameworks ayudan a construir fácilmente la aplicación, pero, no es lo mas conveniente, ya que la mayoría son pesados, generan mucho código  y no son totalmente personalizables; cuando es código propio uno tiene el control del desarrollo.

En esta serie de charlas se aprenderá las Mejores Practicas de Programación en cada tema, iniciando con .NET Framework, WinForms y ADO.NET en la primera charla. La segunda sera exclusiva para ASP.NET WebForm y MVC. Al siguiente año continuaremos con Programación Asíncrona, Reflection, MVC4, Web API, HTML5, Google Maps, You Tube, Drive, Polymer, Web Componentes, etc.

Solo falta una semana para la primera charla y ya quedan pocas vacantes. Los que no se han registrado, todavía tienen la oportunidad de pertenecer a un grupo diferente y selecto de personas que harán el cambio, la idea es formar una comunidad sólida y mejorar el nivel del desarrollo de software en el país.

Te esperamos.


Frase de Día: "La Suerte No existe, Lo que se logra, es producto del Esfuerzo".

El Libro del Día: Pro SharePoint 2013 App Development

El Libro del Día: 2014-11-26

Titulo: Pro SharePoint 2013 App Development
Autor: Steve Wright
Editorial: Apress
Nro Paginas: 419

Capítulos:
Chapter 1: Introduction to SharePoint Apps
Chapter 2: Creating and Debugging Apps
Chapter 3: Managing the App Life Cycle
Chapter 4: Client-Side Logic with JavaScript
Chapter 5: Accessing the SharePoint Environment
Chapter 6: SharePoint App Security
Chapter 7: Web Services with REST and OData
Chapter 8: Business Connectivity Services
Chapter 9: App Logic Components
Chapter 10: Developing the User Experience
Chapter 11: Accessing SharePoint Search
Chapter 12: Using SharePoint’s Social Features
Chapter 13: Enhancing Apps with SharePoint Services
Chapter 14: Using Other App Environments

Descarga:
Pro_SharePoint_2013_App_Development

martes, 25 de noviembre de 2014

El Libro del Día: HTML5 Games Most Wanted

El Libro del Día: 2014-11-25

Titulo: HTML5 Games Most Wanted
Autor: Egor Kuryanovich, Shy Shalom, ...
Editorial: Friendsoft
Nro Paginas: 278

Capítulos:
Chapter 1: The State of Open Web Games
Chapter 2: Complexity from Simplicity
Chapter 3: How to Make Multi-Platform HTML5 Games from Scratch
Chapter 4: Creating, Saving, and Loading Tracks
Chapter 5: 3D CSS Tutorial
Chapter 6: Particle Systems
Chapter 7: Beginning WebGL
Chapter 8: CycleBlob: A WebGL Lightcycle Game
Chapter 9: A Real-Time Multiplayer Game Using WebSockets
Chapter 10: Hard-Pressed for a Choice of Technology

Descarga:
HTML5_Games_Most_Wanted

lunes, 24 de noviembre de 2014

El Libro del Día: Pro HTML5 Games

El Libro del Día: 2014-11-24

Titulo: Pro HTML5 Games
Autor: Aditya Ravi Shankar
Editorial: Apress
Nro Paginas: 357

Capítulos:
Chapter 1: HTML5 and JavaScript Essentials
Chapter 2: Creating a Basic Game World
Chapter 3: Physics Engine Basics
Chapter 4: Integrating The Physics Engine
Chapter 5: Creating the RTS Game World
Chapter 6: Adding Entities to Our World
Chapter 7: Intelligent Unit Movement
Chapter 8: Adding More Game Elements
Chapter 9: Adding Weapons and Combat
Chapter 10: Wrapping Up the Single-Player Campaign
Chapter 11: Multiplayer with WebSockets
Chapter 12: Multiplayer Gameplay

Descarga:
Pro_HTML5_Games

domingo, 23 de noviembre de 2014

El Libro del Día: HTML & CSS. The Complete Reference

El Libro del Día: 2014-11-23

Titulo: HTML & CSS: The Complete Reference (5th Edition)
Autor: Thomas A. Powell
Editorial: McGraw Hill
Nro Paginas: 857

Capítulos:
Part I Core Markup
1 Traditional HTML and XHTML
2 Introducing HTML5
3 HTML and XHTML Element Reference
Part II Core Style
4 Introduction to CSS
5 CSS Syntax and Property Reference
6 CSS3 Proprietary and Emerging Features Reference
Part III Appendixes
A Character Entities
B Fonts
C Colors
D URLs
E Reading a Document Type Definition

Descarga:
HTML_CSS_The_Complete_Reference

sábado, 22 de noviembre de 2014

El Libro del Día: Hello! HTML5 & CSS3

El Libro del Día: 2014-11-22

Titulo: Hello! HTML5 & CSS3
Autor: Rob Crowther
Editorial: Manning
Nro Paginas: 561

Capítulos:
PART 1 LEARNING HTML5
1 Introducing HTML5 markup
2 HTML5 forms
3 Dynamic graphics
4 Audio and video
5 Browser-based APIs
6 Network and location APIs
PART 2 LEARNING CSS3
7 New CSS language features
8 Layout with CSS3
9 Motion and color
10 Borders and backgrounds with CSS3
11 Text and fonts

Descarga:
Hello_HTML5_CSS3

viernes, 21 de noviembre de 2014

El Demo del Día: Filtrar y Ordenar en Cabeceras del DataGridView en WinForms

Filtrar y Ordenar en Cabeceras del DataGridView en WinForms

En este post aprenderemos como cambiar las cabeceras del control DataGridView de Windows Forms para incluir TextBoxs que permitan filtrar registros, así como etiquetas que permitan ordenar ascendente y descendentemente los datos de cada columna.

De repente muchos preguntaran porque tantos demos con Grillas y Cabeceras sobre estas, tanto en Windows y Web Forms y la respuesta es que este es el control mas usado y estas funcionalidades son muy usadas pero no es tan simple como configurar una propiedad y muchos eligen usar un control de terceros, pero con la ayuda de estos post aprenderás tu mismo a crearlos.

Requerimiento

Se necesita crear una consulta en Windows con los siguientes requerimientos:

- La consulta debe ser desconectada y no ir a a cada momento al servidor de datos.

- Los filtros deben ser por todos los campos mostrados y deben ocupar poco espacio.

- Se debe poder ordenar cada columna en forma ascendente y descendente.

- Se debe mostrar el símbolo de ordenación: "▲" para ascendente y "▼" para descendente.

Solución

- Usaremos el control DataGridView y crearemos columnas personalizadas.

- Crearemos una clase DataGridViewTextBoxHeaderColumn para cada columna de tipo TextBox.

- Crearemos una clase DataGridViewTextBoxHeaderCell para la cabecera de cada columna, la cual contendrá un LinkLabel para ordenar y un TextBox para filtrar.

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

Create Procedure uspProductsListar
As
Select ProductID,ProductName,SupplierID,CategoryID,UnitPrice,UnitsInStock
From Products

Crear una Aplicación Windows Forms en C#

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

Arrastrar un DataGridView llamado "dgvProducto" y acoplarlo en todo el formulario, el diseño se mostrará similar a la siguiente figura:


Crear la Clase Entidad del Negocio

Crear la clase beProducto escribiendo el siguiente código:

using System;
namespace DataGridView_FiltroOrdenCabecera
{
    public class beProducto
    {
        public beProducto()
        {
            IdProducto = -1;
            IdProveedor = -1;
            IdCategoria = -1;
            PrecioUnitario = -1;
            Stock = -1;
        }
        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; }
    }
}

Nota: Se ha creado un constructor para iniciar las propiedades numéricas en -1 y asi poder permitir la búsqueda del cero (0) en los filtros.

Crear la Clase de Acceso a Datos

Crear la clase daProducto escribiendo el siguiente código:

using System;
using System.Data; //CommadType
using System.Data.SqlClient; //SqlConnection, SqlCommand, SqlDataReader
using System.Collections.Generic; //List
namespace DataGridView_FiltroOrdenCabecera
{
    public class daProducto
    {
        public List<beProducto> listar(SqlConnection con)
        {
            List<beProducto> lbeProducto = null;
            SqlCommand cmd = new SqlCommand("uspProductsListar", con);
            cmd.CommandType = CommandType.StoredProcedure;
            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);
        }
    }
}

Nota: Tiene que haberse creado el Procedimiento Almacenado "uspProductsListar" 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; //List
using System.Data.SqlClient; //SqlConnection
using System.Configuration; //ConfigurationManager
namespace DataGridView_FiltroOrdenCabecera
{
    public class brProducto
    {
        public List<beProducto> listar()
        {
            List<beProducto> lbeProducto = null;
            string conexion = ConfigurationManager.ConnectionStrings["conNW"].ConnectionString;
            using (SqlConnection con = new SqlConnection(conexion))
            {
                try
                {
                    con.Open();
                    daProducto odaProducto = new daProducto();
                    lbeProducto = odaProducto.listar(con);
                }
                catch (SqlException ex)
                {                
                    foreach (SqlError err in ex.Errors)
                    {
                        //Capturar cada error y grabar un Log
                    }
                }
            } //con.Close(); con.Dispose(); con = null;
            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 las Cabeceras de las Columnas del DataGridView

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

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

namespace DataGridView_FiltroOrdenCabecera
{
    public class DataGridViewTextBoxHeaderCell : DataGridViewColumnHeaderCell
    {
        public TextBox txt;
        public LinkLabel lbl;
        public event EventHandler OrdenaCampo;
        public event EventHandler CambiaTexto;

        public DataGridViewTextBoxHeaderCell()
        {
            lbl = new LinkLabel();
            lbl.Click += new EventHandler(ordenaCampo);
            txt = new TextBox();          
            txt.TextChanged += new EventHandler(cambiaTexto);
        }

        protected override void Paint(Graphics graphics, Rectangle clipBounds,Rectangle cellBounds,
            int rowIndex, DataGridViewElementStates dataGridViewElementState,
            object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle,
            DataGridViewAdvancedBorderStyle advancedBorderStyle,
            DataGridViewPaintParts paintParts)
        {
            base.Paint(graphics, clipBounds, cellBounds, rowIndex, dataGridViewElementState,
            value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
            lbl.Location = new Point(cellBounds.X + 2, cellBounds.Y+5);
            lbl.Size = new Size(cellBounds.Size.Width - 4, 13);
            lbl.BackColor = Color.Transparent;
            lbl.Font=new Font("Arial",8,FontStyle.Bold);
            lbl.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
            txt.Location = new Point(cellBounds.X + 2,cellBounds.Y + 25);
            txt.Size = new Size(cellBounds.Size.Width-4,20);
        }

        public string ValorEtiqueta
        {
            get
            {
                return (lbl.Text);
            }
            set
            {
                lbl.Text = value;
            }
        }

        private void ordenaCampo(object sender, EventArgs e)
        {
            if (OrdenaCampo != null)
            {
                OrdenaCampo(this, e);
            }
        }

        public string ValorTexto
        {
            get
            {
                return (txt.Text);
            }
            set
            {
                txt.Text = value;
            }
        }

        private void cambiaTexto(object sender, EventArgs e)
        {
            if (CambiaTexto != null)
            {
                CambiaTexto(this, e);
            }
        }
    }
}

Nota: La clase creada hereda de "DataGridViewColumnHeaderCell" y crea 2 controles: un LinkLabel "lbl" para ordenar y un TextBox "txt" para filtrar. Al "lbl" se le crea el evento "OrdenaCampo" y al "txt" se le crea el evento "CambiaTexto" para filtrar.
Además se crean 2 propiedades para exponer los valores de las etiquetas de las cabeceras: "ValorEtiqueta" y el valor de los textos: "ValorTexto".

Crear una Clase para las Columnas del DataGridView

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

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

namespace DataGridView_FiltroOrdenCabecera
{
    public class DataGridViewTextBoxHeaderColumn: DataGridViewTextBoxColumn
    {
        DataGridViewTextBoxHeaderCell textoCabecera = new DataGridViewTextBoxHeaderCell();      
        public DataGridViewTextBoxHeaderColumn()
        {
            this.CellTemplate = new DataGridViewTextBoxCell();
            this.HeaderCell = textoCabecera;          
        }

         protected override void OnDataGridViewChanged()
        {
            if (this.DataGridView != null)
            {
                this.DataGridView.Controls.Add(textoCabecera.lbl);
                this.DataGridView.Controls.Add(textoCabecera.txt);
            }
        }

        public DataGridViewTextBoxHeaderCell Texto
        {
            get
            {
                return this.textoCabecera;
            }
        }
    }
}

Nota: La clase creada hereda de "DataGridViewTextBoxColumn" y crea un objeto "textoCabecera" de tipo "DataGridViewTextBoxHeaderCell" que se configura como propiedad "HeaderCell" de la columna. Este se expone como una propiedad llamada "Texto".
Además en el evento "OnDataGridViewChanged" se agrega los controles creados en la cabecera de la celda: el LinkLabel "lbl" y el TextBox "txt".

Usar las Clases creadas en el DataGridView del Formulario

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

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

namespace DataGridView_FiltroOrdenCabecera
{
    public partial class frmConsultaProductos : Form
    {
        private List<beProducto> lbeProducto;
        private List<beProducto> lbeFiltro;
        DataGridViewTextBoxHeaderColumn col1,col2,col3,col4,col5,col6;

        public frmConsultaProductos()
        {
            InitializeComponent();
        }

        private void cargarProductos(object sender, EventArgs e)
        {
            brProducto obrProducto = new brProducto();
            lbeProducto = obrProducto.listar();
            dgvProducto.AutoGenerateColumns = false;
            dgvProducto.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            dgvProducto.ColumnHeadersHeight = 50;
            dgvProducto.ColumnHeadersHeightSizeMode =
            DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
            lbeFiltro = lbeProducto;
            dgvProducto.DataSource = lbeFiltro;
            crearColumnasCabecera();
        }

        private void crearColumnasCabecera()
        {
            col1 = new DataGridViewTextBoxHeaderColumn();
            col1.DataPropertyName = "IdProducto";          
            col1.Width = 70;
            col1.Texto.ValorEtiqueta = "Código";
            col1.Texto.OrdenaCampo += new EventHandler(ordenarProductos);
            col1.Texto.CambiaTexto += new EventHandler(filtrarProductos);
            dgvProducto.Columns.Add(col1);
         
            col2 = new DataGridViewTextBoxHeaderColumn();
            col2.DataPropertyName = "Nombre";
            col2.Width = 300;
            col2.Texto.ValorEtiqueta = "Descripción del Producto";
            col2.Texto.OrdenaCampo += new EventHandler(ordenarProductos);
            col2.Texto.CambiaTexto += new EventHandler(filtrarProductos);
            dgvProducto.Columns.Add(col2);
         
            col3 = new DataGridViewTextBoxHeaderColumn();
            col3.DataPropertyName = "IdProveedor";
            col3.Width = 70;
            col3.Texto.ValorEtiqueta = "Id Prov";
            col3.Texto.OrdenaCampo += new EventHandler(ordenarProductos);
            col3.Texto.CambiaTexto += new EventHandler(filtrarProductos);
            dgvProducto.Columns.Add(col3);
         
            col4 = new DataGridViewTextBoxHeaderColumn();
            col4.DataPropertyName = "IdCategoria";
            col4.Width = 70;
            col4.Texto.ValorEtiqueta = "Id Cat";
            col4.Texto.OrdenaCampo += new EventHandler(ordenarProductos);
            col4.Texto.CambiaTexto += new EventHandler(filtrarProductos);
            dgvProducto.Columns.Add(col4);
         
            col5 = new DataGridViewTextBoxHeaderColumn();
            col5.DataPropertyName = "PrecioUnitario";
            col5.Width = 70;
            col5.Texto.ValorEtiqueta = "Pre Unit";
            col5.Texto.OrdenaCampo += new EventHandler(ordenarProductos);
            col5.Texto.CambiaTexto += new EventHandler(filtrarProductos);
            dgvProducto.Columns.Add(col5);
         
            col6 = new DataGridViewTextBoxHeaderColumn();
            col6.DataPropertyName = "Stock";
            col6.Width = 70;
            col6.Texto.ValorEtiqueta = "Stock";
            col6.Texto.OrdenaCampo += new EventHandler(ordenarProductos);
            col6.Texto.CambiaTexto += new EventHandler(filtrarProductos);
            dgvProducto.Columns.Add(col6);
        }

        private bool buscarProductos(beProducto obeProducto)
        {
            bool exitoIdProducto = true;
            bool exitoNombre = true;
            bool exitoIdProveedor = true;
            bool exitoIdCategoria = true;
            bool exitoPrecioUnitario = true;
            bool exitoStock = true;
            if (!col1.Texto.Equals("")) exitoIdProducto =
            (obeProducto.IdProducto.ToString().Contains(col1.Texto.ValorTexto));
            if (!col2.Texto.Equals("")) exitoNombre =
            (obeProducto.Nombre.ToLower().Contains(col2.Texto.ValorTexto.ToLower()));
            if (!col3.Texto.Equals("")) exitoIdProveedor =
            (obeProducto.IdProveedor.ToString().Contains(col3.Texto.ValorTexto));
            if (!col4.Texto.Equals("")) exitoIdCategoria =
            (obeProducto.IdCategoria.ToString().Contains(col4.Texto.ValorTexto));
            if (!col5.Texto.Equals("")) exitoPrecioUnitario =
            (obeProducto.PrecioUnitario.ToString().Contains(col5.Texto.ValorTexto));
            if (!col6.Texto.Equals("")) exitoStock =
            (obeProducto.Stock.ToString().Contains(col6.Texto.ValorTexto));
            return (exitoIdProducto && exitoNombre && exitoIdProveedor && exitoIdCategoria
            && exitoPrecioUnitario && exitoStock);
        }

        protected void filtrarProductos(object sender, EventArgs e)
        {
            Predicate<beProducto> pred = new Predicate<beProducto>(buscarProductos);
            lbeFiltro = lbeProducto.FindAll(pred);
            dgvProducto.DataSource = lbeFiltro;
        }

        protected void ordenarProductos(object sender, EventArgs e)
        {
            DataGridViewTextBoxHeaderCell cabecera = (DataGridViewTextBoxHeaderCell)sender;
            DataGridViewTextBoxHeaderColumn columna =
            (DataGridViewTextBoxHeaderColumn )cabecera.OwningColumn;
            string campo = columna.DataPropertyName;
            string etiqueta = cabecera.ValorEtiqueta.Replace("▲","").Replace("▼","").Trim();
            int n = 0;
            string simbolo = "▲";
            if (columna.Tag != null)
            {
                if (columna.Tag.Equals(0))
                {
                    simbolo = "▼";
                    n = 1;
                }
            }
            columna.Tag = n;
            cabecera.ValorEtiqueta = String.Format("{0} {1}", etiqueta, simbolo);
            if(n.Equals(0)) lbeFiltro = lbeFiltro.OrderBy(x=>x.GetType().
                 GetProperty(campo).GetValue(x,null)).ToList();
            else lbeFiltro = lbeFiltro.OrderByDescending(x => x.GetType().
                 GetProperty(campo).GetValue(x, null)).ToList();
            dgvProducto.DataSource = lbeFiltro;
        }
    }
}

Nota: En el evento "load" del formulario se llama a la función controladora "cargarProductos" que obtiene los datos y configura el DataGridView "dgvProducto" sobre todo creando las columnas personalizadas en la función "crearColumnasCabecera".

En el evento "OrdenaCampo" de la cabecera de cada columna se asocia la función "ordenarProductos" y en el evento "CambiaTexto" se asocia a la función "filtrarProductos".

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:


En las cabeceras de cada columna aparecen unos LinkLabels y TextBoxs, probamos la ordenación dando clic a los LinkLabel y la primera vez se ordenará en forma ascendente y la siguiente en forma descendente apareciendo su respectivo símbolo, tal como se muestra en la siguiente figura:


Escribir en la cabecera del código el "1" y en la del nombre "ma" y cada vez que se escribe se realiza el filtro desconectado, tal como se muestra a continuación:


Comentario Final

En este post hemos aprendido como sobre escribir las clases "DataGridViewColumnHeaderCell" y "DataGridViewTextBoxColumn" para crear columnas con cabeceras personalizadas que contengan controles: LinkLabels para ordenar y TextBox para filtrar.

En Windows Forms crear cabeceras en el DataGridView es un poco mas trabajoso que en ASP.NET WebForms y MVC que usan plantillas y que en WPF que usa estilos y es mas fácil personalizar.

Pero de todas formas no es imposible y lo mejor es que puedes hacer los cambios que deseas ya que tienes el fuente del código. Espero les sirva a todos los visitantes del Blog.

Creo que después de este buen Demo algunos se animarán a ir al Seminario de Buenas Prácticas en .NET, sobre todo los que nunca han llevado cursos conmigo.

Descarga
Demo26_DataGridView_FiltroOrdenCabecera

El Libro del Día: Start Here! Learn HTML5

El Libro del Día: 2014-11-21

Titulo: Start Here! Learn HTML5
Autor: Faithe Wempen
Editorial: Microsoft
Nro Paginas: 360

Capítulos:
Part I Getting Started with HTML
Chapter 1 HTML Basics: The Least You Need to Know
Chapter 2 Setting Up the Document Structure
Chapter 3 Formatting Text with Tags
Chapter 4 Using Lists and Backgrounds
Chapter 5 Creating Hyperlinks and Anchors
Part II Style Sheets and Graphics
Chapter 6 Introduction to Style Sheets
Chapter 7 Formatting Text with CSS
Chapter 8 Formatting Paragraphs with CSS
Chapter 9 Inserting Graphics
Part III Page Layout and Navigation
Chapter 10 Creating Navigational Aids
Chapter 11 Creating Division-Based Layouts
Chapter 12 Creating Tables
Chapter 13 Formatting Tables
Chapter 14 Creating User Forms
Chapter 15 Incorporating Sound and Video
Chapter 16 HTML and Microsoft Expression Web
Part IV Appendices
Appendix A Designing for Usability
Appendix B Designing for Accessibility
Appendix C Quick Reference

Descarga:
Start_Here_Learn_HTML5

jueves, 20 de noviembre de 2014

El Libro del Día: Building Web Applications with SVG

El Libro del Día: 2014-11-20

Titulo: Building Web Applications with SVG
Autor: David Dailey, Jon Frost, Domenico Strazzullo
Editorial: Microsoft
Nro Paginas: 293

Capítulos:
Chapter 1 SVG Basics
Chapter 2 Creating and Editing SVG Graphics
Chapter 3 Adding Text, Style, and Transforms
Chapter 4 Motion and Interactivity
Chapter 5 SVG Filters
Chapter 6 SVG Tools and Resources
Chapter 7 Building a Web Application: Case Studies

Descarga:
Building_Web_Applications_with_SVG

miércoles, 19 de noviembre de 2014

El Libro del Día: HTML5 Programming for ASP.NET Developers

El Libro del Día: 2014-11-19

Titulo: HTML5 Programming for ASP.NET Developers
Autor: Bipin Joshi
Editorial: Apress
Nro Paginas: 379

Capítulos:
Chapter 1: Overview of HTML5 and ASP.NET 4.5
Chapter 2: Overview of jQuery
Chapter 3: Working with Audio and Video
Chapter 4: Drawing with the Canvas
Chapter 5: Working with Forms and Controls
Chapter 6: Using History API and Custom Data Attributes
Chapter 7: Storing Data in Web Storage
Chapter 8: Developing Offline Web Applications
Chapter 9: Dealing with Local Files Using the File API
Chapter 10: Multithreading in Web Pages Using Web Workers
Chapter 11: Using the Communication API and Web Sockets
Chapter 12: Finding Location with the Geolocation API
Chapter 13: Styling Web Forms and Views with CSS3
Appendix A: HTML5 Learning Resources

Descarga:
HTML5_Programming_ASPNET_Developers

martes, 18 de noviembre de 2014

El Libro del Día: Pro HTML5 Performance

El Libro del Día: 2014-11-18

Titulo: Pro HTML5 Performance
Autor: Jay Bryant, Mike Jones
Editorial: Apress
Nro Paginas: 285

Capítulos:
Part 1: Introduction
Chapter 1: Introduction
Part 2: Performance Basics
Chapter 2: Development Principles
Chapter 3: Performance Guidelines
Chapter 4: Responsive Web Design
Chapter 5: Understanding the Web Reuse Pattern
Part 3: Building a Web Site
Chapter 6: Page Template
Chapter 7: Navigation
Chapter 8: Masthead
Chapter 9: Footer
Chapter 10: Fractal Design Patterns
Chapter 11: Link Control
Chapter 12: Sidebox Control
Chapter 13: Button Control
Chapter 14: Price Control
Chapter 15: Product Control
Chapter 16: Table Control
Chapter 17: Tab Control
Chapter 18: Form Controls

Descarga:
Pro_HTML5_Performance

lunes, 17 de noviembre de 2014

El Libro del Día:Practical WPF Charts and Graphics

El Libro del Día: 2014-11-17

Titulo: Practical WPF Charts and Graphics
Autor: Jack Xu
Editorial: Apress
Nro Paginas: 709

Capítulos:
1. Overview of WPF Programming
2. 2D Transformations
3. WPF Graphics Basics in 2D
4. Colors and Brushes
5. 2D Line charts
6. Specialized 2D Charts
7. Stock Charts
8. Interactive 2D Charts
9. 2D Chart Controls
10. Data Interpolations
11. Curve Fitting
12. 3D Transformations
13. WPF Graphics Basics in 3D
14. 3D Charts with the WPF 3D Engine
15. 3D Charts Without the WPF 3D Engine
16. Specialized 3D Charts

Descarga:
Practical_WPF_Charts_Graphics

domingo, 16 de noviembre de 2014

El Libro del Día: Building Enterprise Applications with WPF and The MVVM Pattern

El Libro del Día: 2014-11-16

Titulo: Building Enterprise Applications with WPF and The MVVM Pattern
Autor: Raffaele Garofalo
Editorial: Microsoft
Nro Paginas: 225

Capítulos:
1 Introduction to Model View ViewModel and Line of Business Applications
2 Design Patterns
3 The Domain Model
4 The Data Access Layer
5 The Business Layer
6 The UI Layer with MVVM
7 MVVM Frameworks and Toolkits

Descarga:
Building_Enterprise_Applications_with_WPF_and_The_MVVM_Pattern

sábado, 15 de noviembre de 2014

El Libro del Día: XAML Developer Reference

El Libro del Día: 2014-11-15

Titulo: XAML Developer Reference
Autor: Mamta Dalal, Ashish Ghoda
Editorial: Microsoft
Nro Paginas: 342

Capítulos:
Part I XAML Basics
Chapter 1 Introducing XAML
Chapter 2 Object Elements and Attributes
Chapter 3 XAML Properties and Events
Part II Enhancing User Experience
Chapter 4 Markup Extensions and Other Features
Chapter 5 Resources, Styles, and Triggers
Part III XAML User Interface Controls
Chapter 6 Layout and Positioning System
Chapter 7 Form and Functional Controls
Part IV Content Integration and Animation
Chapter 8 Data Binding
Chapter 9 Media, Graphics, and Animation
Part V Appendixes
Appendix A Major Namespaces and Classes
Appendix B XAML Editors and Tools

Descarga:
XAML_Developer_Reference

viernes, 14 de noviembre de 2014

El Libro del Día: Professional WPF Programming

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

Titulo: Professional WPF Programming
Autor: Chris Andrade, Shawn Livermore, Mike Meyers, Scott Van Vliet
Editorial: Wrox
Nro Paginas: 482

Capítulos:
Chapter 1: Overview of Windows Presentation Foundation
Chapter 2: WPF and .NET Programming
Chapter 3: Anatomy of a WPF-Enabled Application
Chapter 4: Building a Rich UI with Microsoft Expression Blend - Part I
Chapter 5: Building a Rich UI with Microsoft Expression Blend - Part II
Chapter 6: Special Effects
Chapter 7: Custom Controls
Chapter 8: Using WPF in the Enterprise
Chapter 9: Security
Chapter 10: WPF and Win32 Interop
Chapter 11: Advanced Development Concepts

Descarga:
Professional_WPF_Programming

jueves, 13 de noviembre de 2014

El Demo del Día: Filtrar en Cabeceras del GridView con Totales en el Pie y Filas de Color

Filtrar en Cabeceras del GridView con Totales en el Pie y Filas de Color

El post que viene es muy interesante ya que es un 3 en 1. Se trata de una GridView en ASP.NET Web Forms que permita filtrar mediante controles en la cabecera, mostrar el número de registros encontrados en el pie y mostrar de color las palabras buscadas en los filtros en los registros encontrados.

Requerimiento

Se necesita crear una consulta web con los siguientes requerimientos:
- La consulta debe ser desconectada y no ir a a cada momento al servidor de datos.
- Los filtros deben ser por todos los campos mostrados y deben ocupar poco espacio.
- Se debe ver de color rojo la palabra buscada en cada columna donde se ingrese un criterio de búsqueda.
- Se debe mostrar la cantidad de registros encontrados en la parte inferior.

Solución

- Usaremos el control GridView y personalizaremos la cabecera, el pie y los registros usando plantilla de datos en Web Forms (Data Template).

- Además usaremos el Enlace de Datos para conservar los valores de los campos buscados en las cabeceras ya que se pierden al ir al servidor web.

- Usaremos funciones de lado del servidor para formatear la cabecera a mostrar asi como los valores de los registros que coincidan con la palabra buscada. En esta última parte usaremos Reflection para obtener el valor de cada propiedad del objeto cabecera.

Nota: Muchos desarrolladores usan controles de terceros para cubrir este requerimiento porque no conocen bien el tema de plantillas de datos, código incrustado del servidor y por supuesto Reflection.

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

Create Procedure uspProductsListar
As
Select ProductID,ProductName,SupplierID,CategoryID,UnitPrice,UnitsInStock
From Products

Crear la Aplicación Web en ASP .NET Web Form

Crear un "Nuevo Sitio web vacío de ASP .NET" en C# llamado "GridView_FiltroCabecera_RptaColor".
Para simplificar el Demo no he creado librerías de clases y las clases las he incluido dentro de la aplicación Web.

Crear la Clase Entidad del Negocio

Crear la clase beProducto escribiendo el siguiente código:

using System;
namespace GridView_FiltroCabecera_RptaColor
{
    public class beProducto
    {
        public beProducto()
        {
            IdProducto = -1;
            IdProveedor = -1;
            IdCategoria = -1;
            PrecioUnitario = -1;
            Stock = -1;
        }
        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; }
    }
}

Nota: Se ha creado un constructor para iniciar las propiedades numéricas en -1 y asi poder permitir la búsqueda del cero (0) en los filtros.

Crear la Clase de Acceso a Datos

Crear la clase daProducto escribiendo el siguiente código:

using System;
using System.Data; //CommadType
using System.Data.SqlClient; //SqlConnection, SqlCommand, SqlDataReader
using System.Collections.Generic; //List
namespace GridView_Paginado_Checks
{
    public class daProducto
    {
        public List<beProducto> listar(SqlConnection con)
        {
            List<beProducto> lbeProducto = null;
            SqlCommand cmd = new SqlCommand("uspProductsListar", con);
            cmd.CommandType = CommandType.StoredProcedure;
            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);
        }
    }
}

Nota: Tiene que haberse creado el Procedimiento Almacenado "uspProductsListar" 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; //List
using System.Data.SqlClient; //SqlConnection
using System.Configuration; //ConfigurationManager
namespace GridView_Paginado_Checks
{
    public class brProducto
    {
        public List<beProducto> listar()
        {
            List<beProducto> lbeProducto = null;
            string conexion = ConfigurationManager.ConnectionStrings["conNW"].ConnectionString;
            using (SqlConnection con = new SqlConnection(conexion))
            {
                try
                {
                    con.Open();
                    daProducto odaProducto = new daProducto();
                    lbeProducto = odaProducto.listar(con);
                }
                catch (SqlException ex)
                {                  
                    foreach (SqlError err in ex.Errors)
                    {
                        //Capturar cada error y grabar un Log
                    }
                }
            } //con.Close(); con.Dispose(); con = null;
            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 el Archivo de Hoja de Estilo

Agregar un archivo de hoja de estilo llamado ACME.css y escribir lo siguiente:

body {
    background-color:aqua;
}
.Titulo {
    background-color:black;
    color:white;
    text-transform:uppercase;
    font-size:xx-large;
    font-weight:bold;
}
.AnchoTotal {
    width:100%;
}
.Centrado {
    text-align:center;
}
.Subtitulo {
    background-color:white;
    color:blue;
    text-transform:capitalize;
    font-size:x-large;
    font-weight:bold;
}
.FilaCabecera {
    background-color: blue;
    color:white;
}
.FilaDatos {
    background-color: white;
    color:blue;
}

Crear la Pagina ASP .NET como un Formulario Web Form

Agregar un Formulario Web Form al proyecto llamado: "ConsultaProductos.aspx" y escribir lo siguiente:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ConsultaProductos.aspx.cs" Inherits="GridView_FiltroCabecera_RptaColor.ConsultaProductos" %>
<%@ Import Namespace="GridView_FiltroCabecera_RptaColor" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>Consulta de Productos</title>
    <link href="ACME.css" rel="stylesheet" type="text/css" />
    <script>
        function filtrar(event) {
            var keyCode = ('which' in event) ? event.which : event.keyCode;
            if (keyCode == 13) {
                var btn = document.getElementById("btnFiltrar");
                if (btn != null) btn.click();
                return false;
            }
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <table class="AnchoTotal">
            <tr class="Titulo">
                <td>GridView Filtros Cabecera, Total Pie y Filas Color</td>
            </tr>
            <tr class="Subtitulo">
                <td>Consulta Desconectada de Productos (Autor: Luis Dueñas)</td>
            </tr>
            <tr>
                <td>
                    <asp:GridView ID="gvProducto" AutoGenerateColumns="false" ShowFooter="true"
                        Width="700px" ShowHeaderWhenEmpty="true" runat="server">
                        <HeaderStyle CssClass="FilaCabecera" />
                        <RowStyle CssClass="FilaDatos" />
                        <FooterStyle CssClass="FilaCabecera" />
                        <Columns>
                            <asp:TemplateField HeaderText="" ItemStyle-Width="80px"
                              ItemStyle-HorizontalAlign="Right">
                                <HeaderTemplate>
                                    Código<br />
                                    <asp:TextBox ID="txtIdProducto"
                                      Text="<%#formatearNumero(obeProductoCab.IdProducto)%>"
                                      onkeypress="return filtrar(event);" Width="80px" runat="server" />
                                </HeaderTemplate>
                                <ItemTemplate>
                                    <%#formatearCelda(((beProducto)Container.DataItem).
                                            IdProducto.ToString(),"IdProducto")%>
                                </ItemTemplate>
                            </asp:TemplateField>
                            <asp:TemplateField ItemStyle-Width="300px"
                              FooterStyle-HorizontalAlign="Right">
                                <HeaderTemplate>
                                    Descripción del Producto<br />
                                    <asp:TextBox ID="txtNombre" Text="<%#obeProductoCab.Nombre%>"
                                      onkeypress="return filtrar(event);" Width="300px" runat="server" />
                                </HeaderTemplate>
                                <ItemTemplate>
                                    <%#formatearCelda(((beProducto)Container.DataItem).
                                            Nombre,"Nombre")%>
                                </ItemTemplate>
                                <FooterTemplate>
                                    <b>Total de Registros encontrados: </b>
                                </FooterTemplate>
                            </asp:TemplateField>
                            <asp:TemplateField ItemStyle-Width="80px" ItemStyle-HorizontalAlign="Right"
                              FooterStyle-HorizontalAlign="Right">
                                <HeaderTemplate>
                                    Id Prov<br />
                                    <asp:TextBox ID="txtIdProveedor"
                                      Text="<%#formatearNumero(obeProductoCab.IdProveedor)%>"
                                      onkeypress="return filtrar(event);" Width="80px" runat="server" />
                                </HeaderTemplate>
                                <ItemTemplate>
                                    <%#formatearCelda(((beProducto)Container.DataItem).
                                            IdProveedor.ToString(),"IdProveedor")%>
                                </ItemTemplate>
                                <FooterTemplate>
                                    <b><%#lbeFiltro.Count%></b>
                                </FooterTemplate>
                            </asp:TemplateField>
                            <asp:TemplateField ItemStyle-Width="80px" ItemStyle-HorizontalAlign="Right">
                                <HeaderTemplate>
                                    Id Cat<br />
                                    <asp:TextBox ID="txtIdCategoria"
                                       Text="<%#formatearNumero(obeProductoCab.IdCategoria)%>"
                                      onkeypress="return filtrar(event);" Width="80px" runat="server" />
                                </HeaderTemplate>
                                <ItemTemplate>
                                    <%#formatearCelda(((beProducto)Container.DataItem).
                                            IdCategoria.ToString(),"IdCategoria")%>
                                </ItemTemplate>
                            </asp:TemplateField>
                            <asp:TemplateField ItemStyle-Width="80px"
                               ItemStyle-HorizontalAlign="Right">
                                <HeaderTemplate>
                                    Pre Unit<br />
                                    <asp:TextBox ID="txtPrecioUnitario"
                                      Text="<%#formatearNumero(obeProductoCab.PrecioUnitario)%>"
                                      onkeypress="return filtrar(event);" Width="80px" runat="server" />
                                </HeaderTemplate>
                                <ItemTemplate>
                                    <%#formatearCelda(((beProducto)Container.DataItem).
                                           PrecioUnitario.ToString(),"PrecioUnitario")%>
                                </ItemTemplate>
                            </asp:TemplateField>
                            <asp:TemplateField ItemStyle-Width="80px" ItemStyle-HorizontalAlign="Right">
                                <HeaderTemplate>
                                    Stock<br />
                                    <asp:TextBox ID="txtStock"
                                      Text="<%#formatearNumero(obeProductoCab.Stock)%>"
                                      onkeypress="return filtrar(event);" Width="80px" runat="server" />
                                </HeaderTemplate>
                                <ItemTemplate>
                                    <%#formatearCelda(((beProducto)Container.DataItem).
                                            Stock.ToString(),"Stock")%>
                                </ItemTemplate>
                            </asp:TemplateField>
                        </Columns>
                    </asp:GridView>
                </td>
            </tr>
        </table>
        <asp:Button ID="btnFiltrar" OnClick="filtrarProductos" runat="server" />
    </div>
    </form>
</body>
</html>

Nota: El script del cliente "filtrar" se llama al dar Enter a cualquier control de la cabecera y ejecuta el clic del botón "btnFiltrar:" que es el que permite realizar el filtro y esta oculto por código.

El preview del diseño se mostrará similar a la siguiente figura:


Escribir el siguiente código C# en la página:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace GridView_FiltroCabecera_RptaColor
{
    public partial class ConsultaProductos : System.Web.UI.Page
    {
        private List<beProducto> lbeProducto;
        protected List<beProducto> lbeFiltro;
        protected beProducto obeProductoCab;

        protected void Page_Load(object sender, EventArgs e)
        {
            btnFiltrar.Attributes.Add("style", "display:none");
            obeProductoCab = new beProducto();
            if (!Page.IsPostBack)
            {
                brProducto obrProducto = new brProducto();
                lbeProducto = obrProducto.listar();
                lbeFiltro = lbeProducto;
                Session["Productos"] = lbeProducto;
                gvProducto.DataSource = lbeFiltro;
                gvProducto.DataBind();
            }
        }

        protected string formatearNumero(dynamic campo)
        {
            string cabecera = "";
            if (campo > -1) cabecera = campo.ToString();
            return (cabecera);
        }

        private bool buscarProductos(beProducto obeProducto)
        {
            bool exitoIdProducto = true;
            bool exitoNombre = true;
            bool exitoIdProveedor = true;
            bool exitoIdCategoria = true;
            bool exitoPrecioUnitario = true;
            bool exitoStock = true;
            if (obeProductoCab.IdProducto>-1) exitoIdProducto = (obeProducto.IdProducto.ToString().Contains(obeProductoCab.IdProducto.ToString()));
            if (!obeProductoCab.Nombre.Equals("")) exitoNombre = (obeProducto.Nombre.ToLower().Contains(obeProductoCab.Nombre.ToLower()));
            if (obeProductoCab.IdProveedor > -1) exitoIdProveedor = (obeProducto.IdProveedor.ToString().Contains(obeProductoCab.IdProveedor.ToString()));
            if (obeProductoCab.IdCategoria > -1) exitoIdCategoria = (obeProducto.IdCategoria.ToString().Contains(obeProductoCab.IdCategoria.ToString()));
            if (obeProductoCab.PrecioUnitario > -1) exitoPrecioUnitario = (obeProducto.PrecioUnitario.ToString().Contains(obeProductoCab.PrecioUnitario.ToString()));
            if (obeProductoCab.Stock > -1) exitoStock = (obeProducto.Stock.ToString().Contains(obeProductoCab.Stock.ToString()));
            return (exitoIdProducto && exitoNombre && exitoIdProveedor && exitoIdCategoria && exitoPrecioUnitario && exitoStock);
        }

        protected void filtrarProductos(object sender, EventArgs e)
        {
            guardarFiltros();
            lbeProducto=(List<beProducto>)Session["Productos"];
            Predicate<beProducto> pred = new Predicate<beProducto>(buscarProductos);
            lbeFiltro = lbeProducto.FindAll(pred);
            gvProducto.DataSource = lbeFiltro;
            gvProducto.DataBind();
        }

        private void guardarFiltros()
        {
            string sIdProducto = ((TextBox)gvProducto.HeaderRow.Cells[0].Controls[1]).Text;
            obeProductoCab.IdProducto = (sIdProducto.Equals("") ? -1 : int.Parse(sIdProducto));
            obeProductoCab.Nombre = ((TextBox)gvProducto.HeaderRow.Cells[1].Controls[1]).Text;
            string sIdProveedor = ((TextBox)gvProducto.HeaderRow.Cells[2].Controls[1]).Text;
            obeProductoCab.IdProveedor = (sIdProveedor.Equals("") ? -1 : int.Parse(sIdProveedor));
            string sIdCategoria = ((TextBox)gvProducto.HeaderRow.Cells[3].Controls[1]).Text;
            obeProductoCab.IdCategoria = (sIdCategoria.Equals("") ? -1 : int.Parse(sIdCategoria));
            string sPrecioUnitario = ((TextBox)gvProducto.HeaderRow.Cells[4].Controls[1]).Text;
            obeProductoCab.PrecioUnitario = (sPrecioUnitario.Equals("") ? -1 : decimal.Parse(sPrecioUnitario));
            string sStock = ((TextBox)gvProducto.HeaderRow.Cells[5].Controls[1]).Text;
            obeProductoCab.Stock = (sStock.Equals("") ? short.Parse("-1") : short.Parse(sStock));
        }

        protected string formatearCelda(string valor,string campo)
        {
            StringBuilder rpta = new StringBuilder();
            string celda = valor.ToLower();
            dynamic cabecera = obeProductoCab.GetType().GetProperty(campo).
            GetValue(obeProductoCab, null);
            if (cabecera != null)
            {
                if (!(cabecera is string) && (cabecera == -1)) return valor;
                if ((cabecera is string) && cabecera.Equals("")) return valor;
                string busca = cabecera.ToString().ToLower();
                int posInicio =0;
                int pos = celda.IndexOf(busca);
                if (pos > -1)
                {
                    while (true)
                    {
                        pos = celda.IndexOf(busca, pos);
                        if (pos > -1)
                        {
                            rpta.Append(valor.Substring(posInicio,pos-posInicio));
                            rpta.Append("<span style='color:red;font-bold:true'>");
                            rpta.Append(valor.Substring(pos, busca.Length));
                            rpta.Append("</span>");
                            posInicio = pos + busca.Length;
                            pos += 1;
                        }
                        else break;
                    }
                    rpta.Append(valor.Substring(posInicio, valor.Length - posInicio));
                }
                else rpta.Append(valor);
            }
            else rpta.Append(valor);
            return (rpta.ToString());
        }
    }
}

Nota: El código mas complejo se encuentra en la función "formatearCelda" que se llama desde cada campo de cada registro mostrado en el filtro y que pasa como parámetro el valor de la celda a presentar y el campo a buscar para la cabecera. Aquí usamos Reflection para obtener el valor de cada propiedad del objeto cabecera e iniciar la búsqueda de la palabra en el valor e ir agregando en un StringBuilder la cadena con formato (negrita y colo rojo).

También es necesario antes de "filtrarProductos" llamar a la función "guardarFiltros" que se encarga de pasar los valores de los controles de la cabecera de la grilla en el objeto enlazado a la cabecera llamado: "obeProductoCab", de lo contrario se perderían los valores ingresados.

Finalmente, hay que aclarar que para poder usar desde el código HTML (aspx) un objeto o variables es necesario definirla como protected (por herencia), este es el caso de "lbeFiltro" y "obeProductoCab".

Modificar el Archivo Web.Config para especificar la cadena de conexión a Northwind

<?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>
    <system.web>
      <compilation debug="true" targetFramework="4.5" />
      <httpRuntime targetFramework="4.5" />
    </system.web>
</configuration>

Probar la Pagina Web

Guardar el Sitio Web, clic derecho a la Pagina "ConsultaProductos.aspx" y seleccionar "Ver en el explorador". Se mostrará una ventana similar a la siguiente figura:


Se listarán todos los productos de Northwind, ahora escribir sobre el control TextBox de la cabecera del nombre la palabra "ma" y pulsar "Enter" y se filtrarán los registros apareciendo la palabra "ma" de color rojo y en negrita, tal como se muestra a continuación:


Sobre este filtro aumentar un filtro más ingresando sobre la cabecera del código del producto el número "1" y se filtrará por los 2 criterios apareciendo el número "1" de color rojo y en negrita igual que la palabra "ma", tal como se muestra en la figura de abajo:


Aumentar otro filtro para el Id de la Categoría escribiendo el número "4" y se filtrará por 3 criterios, reduciéndose mas el resultado y apareciendo de color rojo y en negrita el número "4" de la categoría, tal como se ve en la figura:


Finalmente, probemos un filtro mas que no muestre ningún registro, para lo cual aumentar como cuarto filtro que el Stock tenga el número "5", lo cual no genera ningún resultado, pero la cabecera se mantiene gracias a la propiedad: ShowHeaderWhenEmpty="true".


Si deseas mostrar todos los registros borrar cada campo de la cabecera y dar Enter en cualquiera.

Comentario Final

En este post, vimos como se puede crear en un GridView cabeceras y pies personalizados, en este caso para filtrar en forma desconectada usando plantillas de datos y código incrustado del servidor, también vimos como resaltar la palabra buscada en cada columna, aunque se aprecia mejor en columnas de tipo cadena que numérico.

Espero les guste el Demo y recuerden que No es tan complicado crear su propio control Grilla Personalizada, aunque muchos No les gusta hacerse problemas y usan sus controles jGrid, trueGrid, softGrid, superGrid, etc, pero lo mejor seria el yourSelfGrid (Grid de ti mismo).

Nota: En mis cursos creo en la cabecera DropDownList (Combos) para filtrar por Proveedor y Categoría, pero no pinto de colores los registros encontrados.

PD: Los que llevan el curso de MVC los Sábados ya tienen una ayudita para terminar lo que les deje.

Descarga:
GridView_FiltroCabecera_RptaColor