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

miércoles, 29 de noviembre de 2017

El Manual del Día: Windows Presentation Foundation Parte 1

MSDN - Manual de Windows Presentation Foundation Parte 1

Titulo: Manual de Windows Presentation Foundation Parte 1
Fuente Original: Microsoft Developer Network (MSDN)
Compilado Por: Luis Dueñas Huaroto (Lduenas)
Año de Compilación: 2008
Número de Páginas: 445

Indice del Manual de Windows Presentation Foundation (Parte 1)

1. Introducción a WPF
  1.1. Introducción a Windows Presentation Foundation
  1.2. Información General sobre Windows Presentation Foundation
  1.3. Lo Nuevo en Windows Presentation Foundation Versión 3.5
2. Desarrollo de Aplicaciones
  2.1. Información General sobre el Desarrollo de Aplicaciones
  2.2. Administración de Aplicaciones
    2.2.1. Información General sobre la Administración de Aplicaciones
    2.2.2. Archivos c Windows Presentation Foundation
    2.2.3. URIs de Paquete en Windows Presentation Foundation
    2.2.4. Temas Cómo del Modelo de Aplicaciones
      2.2.4.1. Cómo: Obtener y Establecer Propiedades en el Ambito de Aplicación
      2.2.4.2. Cómo: Obtener y Establecer Recursos en el Ambito de Aplicación
      2.2.4.3. Cómo: Obtener y Establecer el Código de Salida de Aplicaciones
      2.2.4.4. Cómo: Obtener los Argumentos de Línea de Comandos
      2.2.4.5. Cómo: Obtener el Objeto de Aplicación Actual
      2.2.4.6. Cómo: Conservar y Restaurar Propiedades en el Ambito de Aplicación a través de Sesiones de Aplicación
      2.2.4.7. Cómo: Cerrar una Aplicación
      2.2.4.8. Cómo: Iniciar una Aplicación
      2.2.4.9. Cómo: Usar un Diccionario de Recursos en el Ambito de Aplicación
  2.3. Ventanas en Aplicaciones de Windows Presentation Foundation
    2.3.1. Información General sobre Ventanas de WPF
    2.3.2. Información General sobre Cuadros de Diálogo
    2.3.3. Temas Cómo de la Administración de Ventanas
      2.3.3.1. Cómo: Cambiar Automáticamente el Tamaño de una Ventana para que se Ajuste a su Contenido
      2.3.3.2. Cómo: Obtener Todas las Ventanas de una Aplicación
      2.3.3.3. Cómo: Obtener y Establecer la Ventana Principal de una Aplicación
      2.3.3.4. Cómo: Abrir un Cuadro de Diálogo
      2.3.3.5. Cómo: Abrir un Cuadro de Mensaje
      2.3.3.6. Cómo: Abrir una Ventana
      2.3.3.7. Cómo: Devolver el Resultado de un Cuadro de Diálogo
  2.4. Navegación en Aplicaciones de Windows Presentation Foundation
    2.4.1. Información General sobre Navegación
    2.4.2. Información General sobre la Navegación Estructurada
    2.4.3. Información General sobre Topologías de Navegación
    2.4.4. Temas Cómo de Navegación
      2.4.4.1. Cómo: Llamar a una Función de Página
      2.4.4.2. Cómo: Determinar si una Página está Hospedada en un Explorador
      2.4.4.3. Cómo: Obtener el Valor Devuelto por una Función de Página
      2.4.4.4. Cómo: Navegar hacia Delante por el Historial de Navegación
      2.4.4.5. Cómo: Navegar hacia Atrás por el Historial de Navegación
      2.4.4.6. Cómo: Navegar hasta una Página
      2.4.4.7. Cómo: Actualizar una Página
      2.4.4.8. Cómo: Devolver Resultados de una Función de Página
      2.4.4.9. Cómo: Establecer el Alto de una Ventana desde una Página
      2.4.4.10. Cómo: Establecer el Ancho de una Ventana desde una Página
      2.4.4.11. Cómo: Establecer el Título de una Ventana desde una Página
      2.4.4.12. Cómo: Detener la Carga de una Página
      2.4.4.13. Cómo: Usar Mailto: para Enviar Correo desde una Página
  2.5. Extensibilidad de Aplicaciones
    2.5.1. Información General sobre Complementos de WPF
    2.5.2. Temas Cómo de Complementos de WPF
      2.5.2.1. Cómo: Crear un Complemento que Devuelva una Interfaz de Usuario
      2.5.2.2. Cómo: Crear un Complemento que sea una Interfaz de Usuario
  2.6. Hospedar Aplicaciones de Windows Presentation Foundation
    2.6.1. Información General sobre Aplicaciones de Explorador XAML de WPF
    2.6.2. Información General sobre Aplicaciones Hospedadas en Windows Media Center para WPF
    2.6.3. Host de Windows Presentation Foundation (PresentationHost.exe)
    2.6.4. API No Administrada de WpfHostSupport
      2.6.4.1. IEnumRAWINPUTDEVICE
      2.6.4.2. IWpfHostSupport
  2.7. Generar e Implementar Aplicaciones de WPF
    2.7.1. Generar una Aplicación de WPF
    2.7.2. Implementar una Aplicación de WPF
    2.7.3. Temas Cómo de Generar e Implementar
      2.7.3.1. Cómo: Crear un Archivo de Proyecto de Ejemplo para una Aplicación Independiente de WPF
      2.7.3.2. Cómo: Crear un Archivo de Proyecto de Ejemplo para una Aplicación de Navegación Independiente de WPF
      2.7.3.3. Cómo: Crear un Archivo de Proyecto de Ejemplo para una Aplicación de Explorador XAML de WPF
      2.7.3.4. Cómo: Crear un Archivo de Proyecto de Ejemplo para una Biblioteca de Controles Personalizados de WPF
      2.7.3.5. Cómo: Crear un Archivo de Proyecto de Ejemplo para una Aplicación Independiente de WPF que sólo contiene Código
      2.7.3.6. Cómo: Configurar IIS 5.0 e IIS 6.0 para Implementar Aplicaciones de WPF
      2.7.3.7. Configurar Visual Studio 2005 para Depurar una Aplicación de Explorador XAML
      2.7.3.8. Configurar Visual Studio para Depurar una Aplicación de Explorador XAML y Llamar a un Servicio Web
      2.7.3.9. Cómo: Determinar la Versión Instalada de Windows Presentation Foundation
      2.7.3.10. Cómo: Detectar si .NET Framework 3.0 está Instalado
      2.7.3.11. Cómo: Detectar si .NET Framework 3.5 está Instalado
      2.7.3.12. Cómo: Detectar si está Instalado el Complemento XBAP para Firefox
  2.8. Rendimiento
    2.8.1. Niveles de Representación de Gráficos
    2.8.2. Optimizar WPF: Rendimiento de Aplicaciones
      2.8.2.1. Planear para Mejorar el Rendimiento de Aplicaciones
      2.8.2.2. Aprovechar el Hardware
      2.8.2.3. Presentación y Diseño
      2.8.2.4. Imágenes y Gráficos 2D
      2.8.2.5. Comportamiento de Objetos
      2.8.2.6. Recursos de Aplicación
      2.8.2.7. Texto
      2.8.2.8. Enlace de Datos
      2.8.2.9. Otras Recomendaciones
      2.8.2.10. Herramientas y Recursos de Rendimiento de WPF
3. Elementos Fundamentales de WPF
  3.1. Arquitectura de WPF
  3.2. XAML
    3.2.1. Información General sobre XAML
    3.2.2. Terminología de la Sintaxis de XAML
    3.2.3. Código Subyacente y XAML
    3.2.4. XAML y Clases Personalizadas
    3.2.5. Extensiones de Marcado y XAML
    3.2.6. Espacios de Nombres y Asignación de Espacios de Nombres de XAML
    3.2.7. Ambitos de Nombres de WPF
    3.2.8. Estilos y Plantillas Insertados
    3.2.9. Procesamiento de Espacios en Blanco en XAML
    3.2.10. Clases TypeConverter y XAML
    3.2.11. Entidades de Caracteres XML y XAML
    3.2.12. Características de lenguaje (x:) de espacios de nombres XAML
      3.2.12.1. Extensión de Marcado x:Array
      3.2.12.2. Atributo x:Class
      3.2.12.3. Atributo x:ClassModifier
      3.2.12.4. Elemento de Directiva x:Code de XAML
      3.2.12.5. Atributo x:FieldModifier
      3.2.12.6. Atributo x:Key
      3.2.12.7. Atributo x:Name
      3.2.12.8. Extensión de Marcado x:Null
      3.2.12.9. Atributo x:Shared
      3.2.12.10. Extensiones de Marcado x:Static
      3.2.12.11. Atributo x:Subclase
      3.2.12.12. Extensión de Marcado x:Type
      3.2.12.13. Atributo x:TypeArguments
      3.2.12.14. Atributo x:Uid
      3.2.12.15. Elemento de Directiva x:XData de XAML
      3.2.12.16. Gramática de XamlName
      3.2.12.17. Secuencia de Escape / Extensión de Marcado {}
      3.2.12.18. Control de xml:lang en XAML
      3.2.12.19. Control de xml:space en XAML
    3.2.13. Extensiones de XAML de Espacio de Nombres de WPF
      3.2.13.1. Enlazar Extensión de Marcado
      3.2.13.2. Extensión de Marcado ColorConvertedBitmap
      3.2.13.3. Extensión de Marcado ComponentResourceKey
      3.2.13.4. Extensión de Marcado DynamicResource
      3.2.13.5. Extensión de Marcado RelativeSource
      3.2.13.6. Extensión de Marcado StaticResource
      3.2.13.7. Extensión de Marcado TemplateBinding
      3.2.13.8. Extensión de Marcado ThemeDictionary
      3.2.13.9. Sintaxis de PropertyPath de XAML
      3.2.13.10. Atributo PresentationOptions:Freeze
    3.2.14. Características del Lenguaje de Compatibilidad de Marcado (mc:)
      3.2.14.1. Atributo mc:Ignorable
      3.2.14.2. Atributo mc:ProcessContent
  3.3. Elementos Base
    3.3.1. Información General sobre Elementos Base
    3.3.2. Información General sobre Objetos Freezable
    3.3.3. Información General sobre Alineación, Márgenes y Relleno
    3.3.4. Temas Cómo sobre Elementos Base
      3.3.4.1. Cómo: Hacer que un Elemento UIElement sea Transparente o Semitransparente
      3.3.4.2. Cómo: Animar el Tamaño de un Elemento FrameworkElement
      3.3.4.3. Cómo: Determinar si un Elemento Freezable está Inmovilizado
      3.3.4.4. Cómo: Controlar un Evento Cargado
      3.3.4.5. Cómo: Definir Márgenes de Elementos y Controles
      3.3.4.6. Cómo: Hacer que un Elemento Freezable sea de Sólo Lectura
      3.3.4.7. Cómo: Obtener una Copia Modificable de un Elemento Freezable de Sólo Lectura
      3.3.4.8. Cómo: Voltear un Control UIElement Horizontal o Verticalmente
      3.3.4.9. Cómo: Utilizar un Objeto ThicknessConverter
      3.3.4.10. Cómo: Usar un Objeto LengthConverter
      3.3.4.11. Cómo: Controlar el Evento ContextMenuOpening
  3.4. Arbol de Elementos y Serialización
    3.4.1. Arboles en WPF
    3.4.2. Limitaciones en la Serialización de XamlWriter.Save
    3.4.3. Inicialización de Elementos de Objeto No Incluidos en un Arbol de Elementos
    3.4.4. Temas Cómo sobre el Arbol de Elementos y la Serialización
      3.4.4.1. Cómo: Buscar un Elemento por su Nombre
      3.4.4.2. Cómo: Invalidar el Arbol Lógico
  3.5. Propiedades
    3.5.1. Información General sobre las Propiedades de Dependencia
    3.5.2. Información General sobre Propiedades Asociadas
    3.5.3. Devoluciones de Llamada y Validación de las Propiedades de Dependencia
    3.5.4. Propiedades de Dependencia Personalizadas
    3.5.5. Metadatos de las Propiedades de Dependencia
    3.5.6. Metadatos de las Propiedades de Marco de Trabajo
    3.5.7. Prioridad de los Valores de Propiedades de Dependencia
    3.5.8. Propiedades de Dependencia de Sólo Lectura
    3.5.9. Herencia de Valores de Propiedad
    3.5.10. Seguridad de las Propiedades de Dependencia
    3.5.11. Modelos de Constructores Seguros para Objetos DependencyObject
    3.5.12. Propiedades de Dependencia de Tipo de Colección
    3.5.13. Carga de XAML y Propiedades de Dependencia
    3.5.14. Temas Cómo sobre Propiedades
      3.5.14.1. Cómo: Implementar una Propiedad de Dependencia
      3.5.14.2. Cómo: Restaurar el Valor Predeterminado de una Propiedad de Dependencia
      3.5.14.3. Cómo: Agregar un Tipo de Propietario para una Propiedad de Dependencia
      3.5.14.4. Cómo: Registrar una Propiedad Asociada
      3.5.14.5. Cómo: Invalidar Metadatos en una Propiedad de Dependencia
  3.6. Eventos
    3.6.1. Información General sobre Eventos Enrutados
    3.6.2. Información General sobre Eventos Asociados
    3.6.3. Eventos de Duración de Objetos
    3.6.4. Marcar Eventos Enrutados como Controlados y Control de Clases
    3.6.5. Eventos de Vista Previa
    3.6.6. Eventos de Cambio de Propiedades
    3.6.7. Control de Eventos en Visual Basic y WPF
    3.6.8. Modelos WeakEvent
    3.6.9. Temas Cómo sobre Eventos
      3.6.9.1. Cómo: Agregar un Controlador de Eventos mediante Código
      3.6.9.2. Cómo: Controlar un Evento Enrutado
      3.6.9.3. Cómo: Crear un Evento Enrutado Personalizado
      3.6.9.4. Cómo: Buscar el Elemento de Origen en un Controlador de Eventos
      3.6.9.5. Cómo: Agregar Control de Clases a un Evento Enrutado
  3.7. Acciones del Usuario y Comandos
    3.7.1. Información General sobre Acciones del Usuario
    3.7.2. Información General sobre Comandos
    3.7.3. Información General sobre el Foco
    3.7.4. Aplicar Estilo a los Controles al Recibir el Foco y FocusVisualStyle
    3.7.5. Temas Cómo sobre Acciones del Usuario y Comandos
      3.7.5.1. Cómo: Habilitar un Comando
      3.7.5.2. Cómo: Cambiar el Tipo de Cursor
      3.7.5.3. Cómo: Detectar el Estado de un Botón del Mouse
      3.7.5.4. Cómo: Cambiar el Color de un Elemento mediante Eventos de Foco
      3.7.5.5. Cómo: Aplicar FocusVisualStyle a un Control
      3.7.5.6. Cómo: Detectar cuando se Presiona la Tecla Enter
      3.7.5.7. Cómo: Crear un Efecto de Activación mediante Eventos
      3.7.5.8. Cómo: Crear un Objeto que Siga el Puntero del Mouse
      3.7.5.9. Cómo: Agregar un Comando a un Elemento de Menú
      3.7.5.10. Cómo: Crear un Comando RoutedCommand
      3.7.5.11. Cómo: Implementar ICommandSource
      3.7.5.12. Cómo: Enlazar un Comando a un Control sin la Compatibilidad de Comandos
      3.7.5.13. Cómo: Enlazar un Comando a un Control con la Compatibilidad de Comandos
  3.8. Recursos WPF
    3.8.1. Información General sobre Recursos
    3.8.2. Recursos y Código
    3.8.3. Diccionarios de Recursos Combinados
    3.8.4. Temas Cómo de Recursos
      3.8.4.1. Cómo: Definir y Hacer Referencia a un Recurso
      3.8.4.2. Cómo: Usar Recursos de Aplicaciones
      3.8.4.3. Cómo: Utilizar SystemFonts
      3.8.4.4. Cómo: Usar Claves de Fuentes del Sistema
      3.8.4.5. Cómo: Utilizar SystemParameters
      3.8.4.6. Cómo: Utilizar Claves de Parámetros del Sistema
  3.9. Estilos y Plantillas: Conceptual
    3.9.1. Aplicar Estilos y Plantillas
    3.9.2. Cómo: Buscar Elementos Generados por un Objeto ControlTemplate
  3.10. Sistema de Diseño
  3.11. Ajuste de Píxeles en Aplicaciones de WPF
  3.12. Modelo de Subprocesos

Descarga
Manual_Windows_Presentation_Foundation_Parte1

martes, 24 de junio de 2014

El Demo del Día: Comparación de Rendimiento al Llenar Grillas Jerárquicas en WinForms

Comparación de Rendimiento al Llenar Grillas Jerárquicas en WinForms

Hoy veremos un caso muy interesante que es crear Grillas Jerárquicas en WinForms para realizar consultas desconectadas de 3 niveles. En el presente Demo veremos como consultar los Detalles de las Ordenes de los Clientes de la Base de Datos Northwind.

Problema con Grillas Jerárquicas en WinForms

En WinForms hay 2 problemas que se presentan cuando queremos trabajar con Grillas Jerárquicas:
1. El Control DataGridView no tiene soporte para listas jerárquicas.
2. Cual es la mejor forma de llenar la grilla:
    2.1. Crear una lista de objetos jerárquicos que tengan como propiedades otras listas de objetos.
    2.2. Crear varias listas de objetos lineales, es decir No jerárquicos y realizar el filtro en la App.

Solución del Problema con Grillas Jerárquicas en WinForms

1. Podemos agregar un Control DataGridView a otro Control DataGridView usando el método Add de su colección Controls, ocultarlo y ubicarlo dentro de la grilla usando el método SetBounds.
2. La mejor forma de llenar una grilla WinForms es la segunda, es decir, tener varias listas en un objeto con todas las listas (lineales No jerárquicas), ya que la carga es más rápido y consume menos memoria.

Creación del Procedimiento Almacenado que devuelva los 3 Selects

Create Procedure [dbo].[uspClienteOrdenDetalleListar]
As
Select CustomerID,CompanyName,Address,IsNull(ContactName,'') As ContactName From Customers
Select OrderID,OrderDate,CustomerID,EmployeeID From Orders
Select d.OrderID,d.ProductID,p.ProductName,d.UnitPrice,d.Quantity
From Order_Details d Inner Join Products p On d.ProductID=p.ProductID

Creación de las Clases de Tipo Entidades del Negocio (be: Business Entities)

Creamos una aplicación WinForms llamada: "Grillas_Jerarquicas_3Niveles" y agregamos las sgtes clases:

1. Clase beDetalle
using System;
namespace Grillas_Jerarquicas_3Niveles
{
    public class beDetalle
    {
        public int IdOrden { get; set; }
        public int IdProducto { get; set; }
        public string NombreProducto { get; set; }
        public decimal PrecioUnitario { get; set; }
        public short Cantidad { get; set; }
        public decimal PrecioTotal {
            get
            {
                return (PrecioUnitario * Cantidad);
            }
        }
    }
}

2. Clase beOrden
using System;
using System.Collections.Generic;

namespace Grillas_Jerarquicas_3Niveles
{
    public class beOrden
    {
        public int IdOrden { get; set; }
        public DateTime FechaOrden { get; set; }
        public string IdCliente { get; set; }
        public int IdEmpleado { get; set; }
        public List<beDetalle> Detalles { get; set; }
    }
}

3. Clase beCliente
using System;
using System.Collections.Generic;
namespace Grillas_Jerarquicas_3Niveles
{
    public class beCliente
    {
        public string IdCliente { get; set; }
        public string Nombre { get; set; }
        public string Direccion { get; set; }
        public string Contacto { get; set; }
        public List<beOrden> Ordenes { get; set; }
    }
}

4. Clase beListas
using System;
using System.Collections.Generic;
namespace Grillas_Jerarquicas_3Niveles
{
    public class beListas
    {
        public List<beCliente> Clientes {get; set;}
        public List<beOrden> Ordenes { get; set; }
        public List<beDetalle> Detalles { get; set; }
    }
}

Nota: Por un tema de simplificación del demo las clases han sido incluidas dentro de la misma aplicación WinForms, pero lo ideal es que estén en Librerías.

Creación de la Clase de Acceso a Datos (da: Data Access)

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
namespace Grillas_Jerarquicas_3Niveles
{
    public class daCliente
    {
        public beListas listar(SqlConnection con)
        {
            beListas obeListas = new beListas();
            List<beCliente> lbeCliente = new List<beCliente>();
            List<beOrden> lbeOrden = new List<beOrden>();
            List<beDetalle> lbeDetalle = new List<beDetalle>();
            beCliente obeCliente = null;
            beOrden obeOrden = null;
            beDetalle obeDetalle = null;
            //Llenar las 3 Listas desde el SP de SQL Server
            SqlCommand cmd = new SqlCommand("uspClienteOrdenDetalleListar", con);
            SqlDataReader drd = cmd.ExecuteReader();
            if (drd != null)
            {
                //Leer el Primer Select de Clientes
                while (drd.Read())
                {
                    obeCliente = new beCliente();
                    obeCliente.IdCliente = drd.GetString(0);
                    obeCliente.Nombre = drd.GetString(1);
                    obeCliente.Direccion = drd.GetString(2);
                    obeCliente.Contacto = drd.GetString(3);
                    lbeCliente.Add(obeCliente);
                }
                //Leer el Segundo Select de Ordenes
                if (drd.NextResult())
                {
                    while (drd.Read())
                    {
                        obeOrden = new beOrden();
                        obeOrden.IdOrden = drd.GetInt32(0);
                        obeOrden.FechaOrden = drd.GetDateTime(1);
                        obeOrden.IdCliente = drd.GetString(2);
                        obeOrden.IdEmpleado = drd.GetInt32(3);
                        lbeOrden.Add(obeOrden);
                    }
                }
                //Leer el Tercer Select de Detalles
                if (drd.NextResult())
                {
                    while (drd.Read())
                    {
                        obeDetalle = new beDetalle();
                        obeDetalle.IdOrden = drd.GetInt32(0);
                        obeDetalle.IdProducto = drd.GetInt32(1);
                        obeDetalle.NombreProducto = drd.GetString(2);
                        obeDetalle.PrecioUnitario = drd.GetDecimal(3);
                        obeDetalle.Cantidad = drd.GetInt16(4);
                        lbeDetalle.Add(obeDetalle);
                    }
                }
            }
            //Llenar las 3 Listas al objeto
            obeListas.Clientes = lbeCliente;
            obeListas.Ordenes = lbeOrden;
            obeListas.Detalles = lbeDetalle;
            return (obeListas);
        }
    }
}

Creación de la Clase de Reglas de Negocios (br: Business Rules)

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

namespace Grillas_Jerarquicas_3Niveles
{
    public class brCliente
    {
        public beListas listarLineal()
        {
            beListas obeListas;
            string conexion = ConfigurationManager.ConnectionStrings["conNW"].ConnectionString;
            using (SqlConnection con = new SqlConnection(conexion))
            {
                con.Open();
                daCliente odaCliente = new daCliente();
                obeListas = odaCliente.listar(con);
            } //con.Close(); con.Dispose(); con=null;
            return (obeListas);
        }

        public List<beCliente> listarJerarquico()
        {
            beListas obeListas = listarLineal();
            foreach (beCliente unCliente in obeListas.Clientes)
            {
                foreach (beOrden unaOrden in obeListas.Ordenes)
                {
                    unaOrden.Detalles = obeListas.Detalles.FindAll
                    (x => x.IdOrden.Equals(unaOrden.IdOrden));
                }
                unCliente.Ordenes = obeListas.Ordenes.FindAll
                (x => x.IdCliente.Equals(unCliente.IdCliente));
            }
            return (obeListas.Clientes);
        }
    }
}

Nota: Esta clase tiene 2 métodos: listarLineal() que devuelve un objeto con las 3 listas y listarJerarquico() que devuelve una lista de clientes que a su vez tiene una lista de ordenes y esta a su vez tiene una lista de detalles de cada orden.

Aumentar la cadena de conexión en el archivo de configuración

Abrir el archivo App.Config y debajo de <configuration> escribir lo siguiente:
  <connectionStrings>
    <add name="conNW" providerName="System.Data.SqlClient" 
      connectionString="uid=UsuarioNW;pwd=123456;data source=DSOFT\Sqlexpress;
      initial catalog=Northwind"/>
  </connectionStrings>

Nota: Cambiar el usuario y password, también el data source con el servidor que se tenga disponible.

Formulario de Prueba de Grillas Jerárquicas llenadas con Listas Lineales

Crear un formulario llamado: "frmGrillaListasLineales" con una Grilla llamada "dgvCliente" acoplada a todo el formulario, asociar al evento "Load" del formulario la función: "cargarDatos", tal como se muestra en el siguiente código:

using System.Windows.Forms;
using System.Diagnostics;

namespace Grillas_Jerarquicas_3Niveles
{
    public partial class frmGrillaListasLineales : Form
    {
        private beListas obeListas;
        private DataGridView dgvOrden;
        private DataGridView dgvDetalle;

        public frmGrillaListasLineales()
        {
            InitializeComponent();
        }

        private void cargarDatos(object sender, EventArgs e)
        {
            Stopwatch oReloj = new Stopwatch();
            oReloj.Start();
            brCliente obrCliente = new brCliente();
            obeListas = obrCliente.listarLineal();
            //Configurar Grilla Clientes
            dgvCliente.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dgvCliente.CellMouseMove += new DataGridViewCellMouseEventHandler 
            (cambiarPunteroMouse);
            dgvCliente.CellContentClick += new DataGridViewCellEventHandler
            (listarOrdenesPorCliente);
            dgvCliente.KeyPress += new KeyPressEventHandler(ocultarGrillas);
            dgvCliente.DataSource = obeListas.Clientes;
            //Configurar Grilla Ordenes
            dgvOrden = new DataGridView();
            dgvOrden.Visible = false;
            dgvOrden.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dgvOrden.CellMouseMove += new DataGridViewCellMouseEventHandler 
            (cambiarPunteroMouse);
            dgvOrden.CellContentClick += new DataGridViewCellEventHandler
            (listarDetallesPorOrden);
            dgvCliente.Controls.Add(dgvOrden);
            //Configurar Grilla Detalles
            dgvDetalle = new DataGridView();
            dgvDetalle.Visible = false;
            dgvDetalle.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dgvOrden.Controls.Add(dgvDetalle);
            oReloj.Stop();
            this.Text = String.Format("Tiempo Procesamiento: {0:n0} msg",
            oReloj.Elapsed.TotalMilliseconds);
        }

        private void cambiarPunteroMouse(object sender, DataGridViewCellMouseEventArgs e)
        {
            DataGridView dgv = (DataGridView)sender;
            if (e.ColumnIndex.Equals(0)) dgv.Cursor = Cursors.Hand;
            else dgv.Cursor = Cursors.Default;
        }

        private void listarOrdenesPorCliente(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex.Equals(0))
            {
                string IdCliente = dgvCliente.CurrentCell.Value.ToString();
                dgvOrden.Visible = true;
                dgvOrden.DataSource = obeListas.Ordenes.FindAll
                (obe => obe.IdCliente.Equals(IdCliente));
                int x = dgvCliente.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Left + 
                dgvCliente.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Width;
                int y = dgvCliente.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Top + 
                dgvCliente.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Height;
                dgvOrden.SetBounds(x, y, 800, 400);
            }
        }

        private void listarDetallesPorOrden(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex.Equals(0))
            {
                int IdOrden = (int)dgvOrden.CurrentCell.Value;
                dgvDetalle.Visible = true;
                dgvDetalle.DataSource = obeListas.Detalles.FindAll
                (obe => obe.IdOrden.Equals(IdOrden));
                int x = dgvOrden.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Left + 
                dgvOrden.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Width;
                int y = dgvOrden.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Top + 
                dgvOrden.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Height;
                dgvDetalle.SetBounds(x, y, 600, 200);
            }
        }

        private void ocultarGrillas(object sender, KeyPressEventArgs e)
        {
            if (((int)e.KeyChar).Equals(27))
            {
                if (dgvDetalle.Visible) dgvDetalle.Visible = false;
                if (dgvOrden.Visible) dgvOrden.Visible = false;
            }
        }
    }
}

Formulario de Prueba de Grillas Jerárquicas llenadas con Listas Jerárquicas

Crear un formulario llamado: "frmGrillaListasJerarquicas" con una Grilla llamada "dgvCliente" acoplada a todo el formulario, asociar al evento "Load" del formulario la función: "cargarDatos", tal como se muestra en el siguiente código:

using System.Windows.Forms;
using System.Diagnostics;

namespace Grillas_Jerarquicas_3Niveles
{
    public partial class frmGrillaListasJerarquicas : Form
    {
        private List<beCliente> lbeCliente;
        private DataGridView dgvOrden;
        private DataGridView dgvDetalle;

        public frmGrillaListasJerarquicas()
        {
            InitializeComponent();
        }

        private void cargarDatos(object sender, EventArgs e)
        {
            Stopwatch oReloj = new Stopwatch();
            oReloj.Start();
            brCliente obrCliente = new brCliente();
            lbeCliente = obrCliente.listarJerarquico();
            //Configurar Grilla Clientes
            dgvCliente.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dgvCliente.CellMouseMove += new DataGridViewCellMouseEventHandler 
            (cambiarPunteroMouse);
            dgvCliente.CellContentClick += new DataGridViewCellEventHandler
            (listarOrdenesPorCliente);
            dgvCliente.KeyPress += new KeyPressEventHandler(ocultarGrillas);
            dgvCliente.DataSource = lbeCliente;
            //Configurar Grilla Ordenes
            dgvOrden = new DataGridView();
            dgvOrden.Visible = false;
            dgvOrden.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dgvOrden.CellMouseMove += new DataGridViewCellMouseEventHandler 
            (cambiarPunteroMouse);
            dgvOrden.CellContentClick += new DataGridViewCellEventHandler
            (listarDetallesPorOrden);
            dgvCliente.Controls.Add(dgvOrden);
            //Configurar Grilla Detalles
            dgvDetalle = new DataGridView();
            dgvDetalle.Visible = false;
            dgvDetalle.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dgvOrden.Controls.Add(dgvDetalle);
            oReloj.Stop();
            this.Text = String.Format("Tiempo Procesamiento: {0:n0} msg",
            oReloj.Elapsed.TotalMilliseconds);
        }

        private void cambiarPunteroMouse(object sender, DataGridViewCellMouseEventArgs e)
        {
            DataGridView dgv = (DataGridView)sender;
            if (e.ColumnIndex.Equals(0)) dgv.Cursor = Cursors.Hand;
            else dgv.Cursor = Cursors.Default;
        }

        private void listarOrdenesPorCliente(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex.Equals(0))
            {
                string IdCliente = dgvCliente.CurrentCell.Value.ToString();
                int posCliente = lbeCliente.FindIndex(obe => obe.IdCliente.Equals(IdCliente));
                dgvOrden.Visible = true;
                dgvOrden.DataSource = lbeCliente[posCliente].Ordenes;
                int x = dgvCliente.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Left + 
                dgvCliente.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Width;
                int y = dgvCliente.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Top + 
                dgvCliente.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Height;
                dgvOrden.SetBounds(x, y, 800, 400);
            }
        }

        private void listarDetallesPorOrden(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex.Equals(0))
            {
                string IdCliente = dgvCliente.CurrentCell.Value.ToString();
                int posCliente = lbeCliente.FindIndex(obe => obe.IdCliente.Equals(IdCliente));
                int IdOrden = (int)dgvOrden.CurrentCell.Value;
                int posOrden = lbeCliente[posCliente].Ordenes.FindIndex 
                (obe=>obe.IdOrden.Equals(IdOrden));
                dgvDetalle.Visible = true;
                dgvDetalle.DataSource = lbeCliente[posCliente].Ordenes[posOrden].Detalles;
                int x = dgvOrden.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Left + 
                dgvOrden.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Width;
                int y = dgvOrden.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Top + 
                dgvOrden.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, false).Height;
                dgvDetalle.SetBounds(x, y, 600, 200);
            }
        }

        private void ocultarGrillas(object sender, KeyPressEventArgs e)
        {
            if (((int)e.KeyChar).Equals(27))
            {
                if (dgvDetalle.Visible) dgvDetalle.Visible = false;
                if (dgvOrden.Visible) dgvOrden.Visible = false;
            }
        }
    }
}

Ejecución y Pruebas de Ambos Formularios

Configurar en el archivo "Program.cs" en la función "Main" lo siguiente: 
Application.Run(new frmGrillaListasLineales());
Pulsar F5 para ejecutar y probar el primer formulario con Listas Lineales y la ventana sale de inmediato (de 200 a 400 msg aprox), luego clic a cualquier celda de la primera columna con el código del cliente y se mostrará sus ordenes, luego clic a cualquier celda de la columna con el código de la orden y se mostrará sus detalles.

Para salir de las grillas pulsar la tecla Escape sobre la grilla de Clientes. Detener la aplicación.

Configurar en el archivo "Program.cs" en la función "Main" lo siguiente: 
Application.Run(new frmGrillaListaJerarquicas());
Pulsar F5 para ejecutar y probar el segundo formulario con Listas Jerárquicas y la ventana demora en mostrarse (mas de 12000 msg aprox), luego clic a cualquier celda de la primera columna con el código del cliente y se mostrará sus ordenes, luego clic a cualquier celda de la columna con el código de la orden y se mostrará sus detalles.

Comentario Final

Hemos demostrado que con un poco de ingenio podemos crear Grillas Jerárquicas en WinForms usando Listas de Objetos y que mejor es usar Listas Lineales y filtrarlas a demanda en vez de crear listas jerárquicas que demoran en cargarse (12,000 msg Versus 200 msg) y consumen mucha memoria. Espero les sea útil sobre a todo los desarrolladores que trabajan en Cliente Servidor o Windows que mucha veces extrañan la facilidad de Visual Foxpro, Power Builder, etc.

Descarga:
Demo06_Grillas_Jerarquicas_3Niveles

lunes, 23 de junio de 2014

El Demo del Día: Comparación de Rendimiento al Llenar Listas en WinForms

Comparación de Rendimiento al Llenar Listas en WinForms

En este post, compararemos el rendimiento de diferentes técnicas para llenar listas en Windows Forms desde un arreglo. En WinForms los controles ListBox, ComboBox, CheckedListBox y ListView tienen una colección de Items que representan los elementos del control. Aprenderemos varias formas de mejorar la performance o rendimiento al llenar controles de tipo listas.

Problema al Llenar Listas en WinForms

Si tenemos un arreglo con datos y queremos llenar un control de tipo lista en WinForms, muchos programadores crean un bucle (for, foreach, while) y leen del arreglo cada elemento y lo agregan al control lista. Si son muchos registros a agregar el tiempo aumenta considerablemente en proporción a la cantidad de filas y se nota un parpadeo al momento de pintar el control.

Solución al Problema de Llenar Listas en WinForms

Existen 2 formas de solucionar el problema del rendimiento al llenar un control lista desde un arreglo:

1. Usar el método AddRange de la colección de Items del control lista y agregar directamente el arreglo.

2. Usar el método BeginUpdate antes del bucle para congelar la actualización en pantalla de fila x fila y al final del bucle llamar al método EndUpdate para actualizar el pintado del control lista.

Diseño de la Aplicación WinForms

Crear una aplicación WinForms llamada: "Comparacion_Rendimiento_Listas" en C# y cambiar de nombre al formulario por "frmLista", luego agregar los siguientes controles:
- Un Control Label llamado "lblTiempoProcesamiento"
- Un Control ListBox llamado "lstNumero"
- Cinco Controles Button llamados: "btnLlenarArreglo", "btnLlenarListaForItemsAdd",
  "btnLlenarListaAddRange", "btnLlenarListaForItemsAddBU" y "btnLlenarListaAddRangeBU".


Código Fuente de la Aplicación WinForms

En el código he creado un método "ejecutarMetodo" que tiene como parámetro un método gracias al tipo "Action" lo que me permite crear una rutina que permita "medir el tiempo de procesamiento de cualquier método" para poder comparar los tiempos en cada caso, para lo cual usamos la clase "Stopwatch" de "System.Diagnostics".
Además usamos un arreglo de cadenas, el cual lo llenamos con 10000 registros para poder ser pasados al control ListBox con las diferentes técnicas.

using System.Windows.Forms;
using System.Diagnostics;

namespace Comparacion_Rendimiento_Listas
{
    public partial class frmLista : Form
    {
        private string[] lista;

        public frmLista()
        {
            InitializeComponent();
        }

        public string ejecutarMetodo(Action metodo)
        {
            Stopwatch oReloj = new Stopwatch();
            oReloj.Start();
            metodo();
            oReloj.Stop();
            return (String.Format("Tiempo de Procesamiento: {0:n0} msg",
            oReloj.Elapsed.TotalMilliseconds));
        }

        private void llenarArreglo()
        {
            lista=new string[10000];
            for (int i = 0; i < lista.Length; i++)
            {
                lista[i] = i.ToString();
            }
        }

        private void btnLlenarArreglo_Click(object sender, EventArgs e)
        {
            lblTiempoProcesamiento.Text = ejecutarMetodo(llenarArreglo);
        }

        private void llenarListaForItemsAdd()
        {
            lstNumero.Items.Clear();
            for (int i = 0; i < lista.Length; i++)
            {
                lstNumero.Items.Add(lista[i]);
            }
        }

        private void btnLlenarListaForItemsAdd_Click(object sender, EventArgs e)
        {
            lblTiempoProcesamiento.Text = ejecutarMetodo(llenarListaForItemsAdd);
        }

        private void llenarListaAddRange()
        {
            lstNumero.Items.Clear();
            lstNumero.Items.AddRange(lista);
        }

        private void btnLlenarListaAddRange_Click(object sender, EventArgs e)
        {
            lblTiempoProcesamiento.Text = ejecutarMetodo(llenarListaAddRange);
        }

        private void llenarListaForItemsAddConBeginUpdate()
        {
            lstNumero.BeginUpdate();
            lstNumero.Items.Clear();
            for (int i = 0; i < lista.Length; i++)
            {
                lstNumero.Items.Add(lista[i]);
            }
            lstNumero.EndUpdate();
        }

        private void btnLlenarListaForItemsAddBU_Click(object sender, EventArgs e)
        {
            lblTiempoProcesamiento.Text = ejecutarMetodo(llenarListaForItemsAddConBeginUpdate);
        }

        private void llenarListaAddRangeConBeginUpdate()
        {
            lstNumero.BeginUpdate();
            lstNumero.Items.Clear();
            lstNumero.Items.AddRange(lista);
            lstNumero.EndUpdate();
        }

        private void btnLlenarListaAddRangeBU_Click(object sender, EventArgs e)
        {
            lblTiempoProcesamiento.Text = ejecutarMetodo(llenarListaAddRangeConBeginUpdate);
        }
    }
}

Ejecución y Pruebas de la Aplicación WinForms

Si ejecutamos la aplicación y damos clic al botón "Llenar Arreglo" notamos que el tiempo de procesamiento es de 2 msg (mili segundos), si damos clic al botón "Llenar Lista For Items Add" el tiempo de procesamiento es de 3306 msg (este puede variar cada vez que se da clic entre 2500 y 3500 aproximadamente), además en todo este tiempo se nota la aplicación en espera y con parpadeo.


Si damos clic al botón "Llenar Lista AddRange" o los otros 2 últimos botones (es decir las 3 formas de mejor rendimiento) notamos que el tiempo de procesamiento es de 69 msg (puede variar entre 60 y 80 msg).


Comentario Final

Después de hacer las comparaciones de rendimiento nos damos cuenta que la diferencia es extremadamente abismal, para 10000 filas el método clásico del bucle demora mas de 3000 msg mientras que las otras 3 técnicas solo 70 msg, es decir una diferencia de 3000, la cual aumenta con el aumento de los registros.

Después de esto, espero que ningún programador se olvide de colocar "BeginUpdate" antes del llenado de su lista y al final "EndUpdate" o use directamente el método AddRange.

Descarga:
Demo05_Rendimiento_Listas