Lilypie Primer PicLilypie Primer Ticker

lunes, 26 de marzo de 2007

Un ComboBox con imágenes

Update: Actualizado en www.MiCodigo.info

En este post les mostraré una forma de incluir imágenes al lado de cada elemento de un ComboBox , asi:





Para ello necesitaremos un Windows Form en el que incluiremos un control ComboBox y un Componente ImageList.



En el ImageList cargamos las imágenes que queremos mostrar. En lo posible, el tamaño de estas imágenes debe ser de 16x16.





Ahora necesitamos crear una clase muy simple que nos permita almacenar la etiqueta de los elementos del Combo y el índice de la imagen en el ImageList.



class ComboItem

{
private int _imageIndex;
private string _etiqueta;

public string Etiqueta
{
get { return _etiqueta; }
set { _etiqueta = value; }
}

public int ImageIndex
{
get { return _imageIndex; }
set { _imageIndex = value; }
}

public ComboItem(string etiqueta, int imageIndex)
{
this.Etiqueta = etiqueta;
this.ImageIndex = imageIndex;
}

public override string ToString()
{
return Etiqueta;
}
}

Nada más, ahora lo único que tenemos que hacer es añadir instancias de la clase ComboItem en nuestro comboBox, podemos hacerlo en el evento Load asi:


private void Form1_Load(object sender, EventArgs e)
{
comboBox1.Items.Add(new ComboItem("Aumentar", 0));
comboBox1.Items.Add(new ComboItem("Disminuir", 1));
comboBox1.Items.Add(new ComboItem("---------", -1));
comboBox1.Items.Add(new ComboItem("Editar", 2));
}

Nótese que el tercer item tiene el índice -1. Usaremos -1 para indicar que un elemento no tiene una imagen asociada.


Ahora si, la verdadera acción:


Primero debemos modificar la propiedad DrawMode del ComboBox, y fijarla en OwnerDrawFixed.


Y finalmente añadimos un manejador para el evento DrawItem del ComboBox, asi:



private void comboBox1_DrawItem(object sender, DrawItemEventArgs e)
{
ComboItem item = comboBox1.Items[e.Index] as ComboItem;
e.DrawBackground();
if (item.ImageIndex >= 0 && item.ImageIndex < imageList1.Images.Count)
e.Graphics.DrawImage(imageList1.Images[item.ImageIndex], new PointF(e.Bounds.Left, e.Bounds.Top));
e.Graphics.DrawString(item.Etiqueta, e.Font, new SolidBrush(e.ForeColor), new PointF(e.Bounds.Left + imageList1.ImageSize.Width + 1, e.Bounds.Top));
}




Eso es todo, nuestro ComboBox ahora muestra su imagen asociada a su izquierda.

En un siguiente post, mostraré cómo se puede crear un control derivado de ComboBox que incluya todo este procedimiento de modo que sea reutilizable.

jueves, 22 de marzo de 2007

Patrones de Diseño: Singleton

Este post marca el inicio de una serie que cubrirá los 23 patrones de diseño definidos en el libro "Design Patterns: Elements of Reusable Object Oriented Software" de Gamma, Helm, Johnson y Vlissides, más conocidos como  La Banda de los Cuatro (Gang of Four o GoF).

Empezaré con uno de los patrones más fáciles de implementar: el Patrón Singleton.

Según el libro de Dessign Patterns, el propósito del patrón Singleton es:

Asegurarse que una clase tiene solamente una instancia, y provee un punto de acceso global a ella.

Conozco dos formas de implementar este patrón:

  • Hacer privados los constructores de la clase e implementar un método estático que cree las instancias.
  • Implementar los mecanismos de control en los constructores de la clase.

Se recomienda usar el primer enfoque, porque es preferible tener un método especializado que implemente el patrón Singleton y dejar que el constructor haga las cosas que se supone que debe hacer: Inicializar las propiedades del objeto.

A manera de ejemplo, imaginemos que estamos creando un juego de Pacman, es lógico pensar que en nuestro juego debe existir UN y solo UN Pacman, entonces es una buena oportunidad para implementar un patrón Singleton, asi:

 

class Pacman
{
#region Patron Singleton
private static Pacman instancia = null;
public static Pacman Create()
{
if (instancia == null)
instancia = new Pacman();
return instancia;
}
#endregion

private Pacman()
{
// Inicializar Objeto
}
}

Podemos ver que hemos declarado el constructor como private de modo que no sea accesible desde el exterior de la Clase.

La implementación del patrón Singleton es bastante directa. Toda la acción ocurre en el método Create que inspecciona el valor de la variable instancia para determinar si debe crear o no una instancia de la Clase Pacman. Si no existe (si instancia == null), entonces se crea una nueva instancia y la almacena en el campo instancia. Si ya existe una instancia, entonces esa instancia será devuelta

Entonces, si intentamos crear una instancia de la clase Pacman usando el constructor, de esta manera:

Pacman p = new Pacman(); 

Obtenemos un error pues el constructor solo es accesible desde el interior de la Clase.

La única forma de obtener nuestro Pacman, es mediante el método Create asi:

Pacman p = Pacman.Create(); 

Una posible variante del método Create, es hacer que se lance una excepción cundo se intente crear más de una instancia.

 

 


martes, 20 de marzo de 2007

ALBI abrió sus puertas

Como se habia anunciado, ayer lunes 19 la Academia Latinoamericana de Business Intelligence (ALBI) abrió sus puertas. Se trata de otra propuesta de capacitación gratuita por parte de Microsoft.

Los objetivos de la Academia son:

Enseñar a optimizar la toma de decisiones en un entorno altamente competitivo.


Ayudar a comprender la necesidad de analizar la información en tiempo y forma para tomar las decisiones adecuadas, que aseguren a la compañía llegar a los objetivos planificados.

La ALBI sigue los mismos lineamientos de los otros programas de capacitación de MS, como el Desarrollador 5 estrellas, el Profesional 5 Estrellas o la Academia NetProtector. Es decir que es un programa en que el estudiante consigue recompensas a medida que avanza en el curso. En el caso de la ALBI, el estudiante obtiene una medalla por cada nivel que supera.

El programa completo consta de 4 niveles, que van del Nivel 0 al Nivel 3. El Nivel 0 se alcanza al inscribirse al programa, obteniendo asi la primera medalla (mas facil imposible)

El primer nivel (que es el único disponible actualmente) se centra en crear soluciones basadas en OLAP, utilizando la herramienta Analysis Services de SQL Server.

El contenido del primer nivel es el siguiente:

  • Unidad 1. Qué es Business Intelligence?
  • Unidad 2. Definiendo Soluciones OLAP
  • Unidad 3. Diseñando una Solución OLAP
  • Unidad 4. Construyendo una Solución OLAP
  • Unidad 5. Implementando cubos OLAP

Entonces los animo a inscribirse, aqui les dejo el link.

http://www.mslatam.com/latam/technet/albi

viernes, 16 de marzo de 2007

Capítulo II: XBindingList se vuelve ordenada

En el anterior post, vimos como crear la clase XBindingList e implementamos la característica de búsqueda.

Ahora continuaremos con la implementacion de XBindingList añadiendo la opción de ordenación.

Como vimos anteriormente, la forma de añadir prestaciones a una clase derivada de BindingList es solapando métodos y propiedades. Para el caso específico de la ordenación, trabajaremos con las propiedades SupportsSortingCore, SortPopertyCore, SortDirectionCore, IsSortedCore y el método ApplySortCore

Primero debemos informar al mundo que nuestra colección soporta ordenación, para ello solapamos la propiedad SupportSortingCore, asi:


protected override bool SupportsSortingCore
{
get
{
return true;
}
}


Ahora debemos solapar las tres propiedades en las que almacenamos el estado actual de la ordenación: IsSortedCore, SortDirectionCore y SortPropertyCore.


IsSortedCore nos sirve para determinar si la colección está actualmente ordenada o no.


SortDirectionCore determina el sentido de la ordenación. Los posibles valores son Ascending y Descending.


SortPropertyCore determina la propiedad que sirve de criterio para la ordenación.


Entonces solapamos esas tres propiedades asi:



private bool _isSorted;
private ListSortDirection _sortDirection;
private PropertyDescriptor _sortProperty;
 
protected override bool IsSortedCore
{
get
{
return _isSorted;
}
}

protected override ListSortDirection SortDirectionCore
{
get
{
return _sortDirection;
}
}

protected override PropertyDescriptor SortPropertyCore
{
get
{
return _sortProperty;
}
}

Simplemente hemos definido tres campos privados que almacenarán el estado, y hemos solapado las propiedades para accesarlos.


Ahora implementamos el mecanismo de búsqueda. Para ello debemos solapar el método ApplySortCore, asi:


protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
{
_sortProperty = prop;
_sortDirection = direction;

if (this.Count == 0)
return;


SortComparer <T> comparer = new SortComparer(prop, direction);

(this.Items as List<T>).Sort(comparer);


_isSorted = true;
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));

}


Este método recibe dos parámetros: prop que es la propiedad que actuará como el criterio de ordenación, y direction que es el sentido de la orientación, que puede ser acendente o descendente.


A continuación creamos una instancia de la clase SortComparer que será usado por el mecanismo de ordenación para hacer las comparaciones. la clase SortComparer está definida asi:


public class SortComparer<T> : IComparer<T>
{
private ListSortDirection _sortDirection = ListSortDirection.Ascending;
private PropertyDescriptor _propDescriptor = null;

public SortComparer(PropertyDescriptor propDescriptor, ListSortDirection sortDirection)
{
this._propDescriptor = propDescriptor;
this._sortDirection = sortDirection;
}

int IComparer<T>.Compare(T x, T y)
{
object xValue = _propDescriptor.GetValue(x);
object yValue = _propDescriptor.GetValue(y);
return CompareValues(xValue, yValue);
}

private int CompareValues(object xValue, object yValue)
{
int retValue = 0;
if (xValue is IComparable)
retValue = (xValue as IComparable).CompareTo(yValue);
else if (yValue is IComparable)
retValue = (yValue as IComparable).CompareTo(xValue);
else if (!xValue.Equals(yValue))
retValue = xValue.ToString().CompareTo(yValue.ToString());

if (_sortDirection == ListSortDirection.Descending)
retValue *= -1;
return retValue;
}
}

La clase SortComparer implementa la interfaz IComparer, siendo el método CompareValues el encargado de realizar todo el trabajo pesado: las comparaciones.

La línea (this.Items as List<T>).Sort(comparer) es donde realmente la ordenación tiene lugar. Simplemente invocamos al método Sort de la clase List<T> pasando como parámetro la instancia de la clase SortComparer que acabamos de crear.

Eso es todo, estamos listo para probar nuestra implementación. La misma aplicación que creamos anteriormente para probar lá búsqueda nos servirá para probar la ordenación. Con un simple click en la cabecera de las columnas podremos ver la ordenación en funcionamiento.


Con la implementación actual, XBindingList se comporta bastante bien cuando de ordenar se trata, pero para completar el ejercicio, debemos implementar también el método RemoveSortCore que es invocado cuando queremos dejar de aplicar la ordenación. Ahi se complica el panorama porque debemos guardar el orden (o desorden) original para que pueda ser restaurado luego. ...pero ese será tema de otro post.


Siguiente Capítulo: XBindingList se vuelve selectiva.

martes, 13 de marzo de 2007

El prefijo sp_: Usar o no usar... esa es la cuestión

Dando un vistazo a los procedimientos almacenados que incorpora SQL Server, nos damos cuenta que sus nombres siguen una misma convención: Todos empiezan con 'sp_', asi tenemos sp_configure, sp_tables sp_columns, sp_help, sp_who, etc.

Nuestro primer instinto nos sugiere que 'sp' significa 'Stored Procedure', por tanto pensamos que lo mejor que podemos hacer es seguir la misma convención y comenzamos a crear procedimientos almacenados con nombres como sp_factura_insert, sp_empleado_delete, sp_salario_select, etc.

A primera vista parece que todo funciona adecuadamente, creamos los procedimientos, los ejecutamos sin problemas y obtenemos los resultados esperados.

Ahora.. la cruda realidad: Usar el prefijo sp_ para nombrar nuestros procedimientos almacenados NO es la mejor idea.

La razón es muy simple: SQL Server reconoce el prefijo sp_ como 'System Stored Procedure', es decir, un procedimiento almacenado de Sistema.

Esa caracter'istica influye en la estrategia que SQL Server utiliza para localizar el SP cuando intentamos ejecutarlo. Por ejemplo, si lanzamos una instrucción exec sp_persona_delete, SQL Server asume que se trata de un procedimiento almacenado de sistema, y por tanto debe encontrarse en la Base de Datos MASTER, donde residen todos los demás procedimientos almacenados de este tipo. Entonces primeramente intenta localizar el procedimiento en la BD Master. Al no encontrarlo, recién intenta encontrarlo en la BD activa.

Y cuales son las consecuencias de este comportamiento?: Una caída en el rendimiento. No parece influir mucho cuando la carga de transacciones es pequeña, pero cuando nuestro servidor debe atender miles de transacciones por minuto, entonces se notará una gran caída en el desempeño.

Otro punto a tomar en cuenta es que si creamos un procedimiento almacenado con el mismo nombre que un procedimiento almacenado de sistema, entonces nuestro procedimiento almacenado no se ejecutará nunca. Siempre será invocado el SP de sistema.

Por ejemplo, si creamos un SP llamado sp_who en la BD tempdb

USE tempdb
GO
CREATE PROCEDURE SP_WHO
AS
SELECT 'HOLA'
GO


ahora intentamos ejecutarlo

USE tempdb
GO
EXEC sp_who
EXEC dbo.sp_who
EXEC tempdb.dbo.sp_who
GO


Vemos que en todos los casos se ejecuta el SP de sistema. Aún cuando intentamos ejecutar nuestro SP con su nombre totalmente cualificado.


La única forma de ejecutar nuestro SP en lugar del SP de sistema es creándolo bajo un schema diferente a dbo, así:

USE tempdb
GO
CREATE SCHEMA mySchema AUTHORIZATION dbo
GO
CREATE PROCEDURE mySchema.SP_WHO
AS
SELECT 'HOLA'
GO
EXEC mySchema.sp_who
GO


Demasiadas complicaciones para obtener un simple Hola.


Moraleja: Jamás usar el prefijo 'sp_' en los nombres de nuestros procedimientos almacenados.

jueves, 8 de marzo de 2007

XBindingList: Un BindingList con esteroides

Una de las clases del .NET Framework que utilizo con más frecuencia es BindingList.

Como saben, BindingList es una colección genérica optimizada para servir como origen de datos para controles como el DataGridView y otros.

Es de gran utilidad, pero no están implementadas 3 características deseables: Búsqueda, Ordenación y Filtrado.

Mostraré la forma de crear una clase derivada de BindingList que implemente estas prestaciones. Como es un tema mas o menos extenso, irá desglosado en varios posts.

A falta de un mejor nombre, llamaré a esta clase XBindingList.

Definamos primero una clase Persona (ID, Nombre, Fecha de Nacimiento) que usaremos para nuestras pruebas.


public class Persona

{

private int _id;

private string _nombre;

private DateTime _fechaNacimiento;



public int Id

{

get { return _id; }

set { _id = value; }

}



public string Nombre

{

get { return _nombre; }

set { _nombre = value; }

}



public DateTime FechaNacimiento

{

get { return _fechaNacimiento; }

set { _fechaNacimiento = value; }

}



public Persona()

{

}



public Persona(int id, string nombre, DateTime fechaNacimiento)

:
this()

{

this.Id = id;

this.Nombre = nombre;

this.FechaNacimiento = fechaNacimiento;

}

}





Ahora si estamos listos para entrar en materia. Empecemos definiendo nuestra clase XBindingList:

public class XBindingList<T> : BindingList<T>

{

//...

}

Simplemente hemos declarado la clase XBindingList como una nueva colección genérica que se deriva de BindingList.

Ahora suministraremos la primera dosis de esteroides a nuestra nueva clase XBindingList: La capacidad de realizar búsquedas.

Se desea buscar algún valor en cualquiera de las propiedades de nuestra clase. Es decir, si usamos la clase Persona, debemos ser capaces de buscar un elemento ya sea por el Id, el Nombre o la Fecha de Nacimiento.

En general, para implementar nuevas características en una clase derivada de BindingList, debemos solapar (override) ciertas propiedades y métodos. Para las operaciones de búsqueda debemos solapar la propiedad SupportSearchingCore y el método FindCore. SuportsSearchingCore se encarga de comunicar al mundo si la colección es capaz o no de realizar búsquedas, mientras que FindCore se encarga de realizar la búsqueda propiamente dicha.

Primero vamos a enseñarle a XBindingList que responda afirmativamente cuando alguien le pregunte si es capaz de realizar búsquedas. Para ello solapamos la propiedad SupportsSearchingCore, asi:


protected override bool SupportsSearchingCore

{

get

{

return true;

}

}



Ahora viene el trabajo real: implementar el mecanismo de búsqueda.

El método FindCore recibe dos parámetros: prop que le indica en cuál propiedad debe buscar el valor, y key que es el valor que se debe buscar. Como punto de partida implementaremos un mecanismo de búsqueda secuencial. Es muy sencillo de codificar, pero es ineficiente cuando la colección es muy grande.


protected override int FindCore(PropertyDescriptor prop, object key)

{

foreach (T item in this)

{

if (prop.GetValue(item).ToString().Equals(key.ToString()))

{

return this.IndexOf(item);

}

}

return -1;

}



Esta es la idea básica, tal cual está implementado el algoritmo de búsqueda, solamente encuentra el primer elemento que coincida exactamente con la el valor de key. Sin duda se pueden añadir muchas mejoras como comparaciones parciales, buscar siguiente, etc. En algún momento postearé un algoritmo mejorado, por mientras los animo a que en algún comentario puedan compartir conmigo (y los otros lectores del blog) sus propios algoritmos.

Nada más, XBindingList ahora soporta búsquedas, veamos un ejemplo de su utilización.




Incluimos un BindingSource, un BindingNavigator y un DataGridView. En la barra de herramientas del BindingNavigator incluimos un Combobox (cbPropiedad), un TextBox (tbValor) y un Button (btnBuscar). Agregamos en cbPropiedad los nombres de las propiedades es que queremos poder buscar. En tbValor el usuario escribirá el valor que quiere buscar y la búsqueda se realizará al hacer click en el botón btnBuscar.

En el constructor del Form declaramos una variable llamada lista que es una instancia de XBindingList que almacenará elementos del tipo Persona, la poblamos con unos pocos datos y finalmente hacemos que nuestro BindingSource use lista como su origen de datos.



public FormTest2()

{

InitializeComponent();

XBindingList
<Persona> lista = new XBindingList<Persona>();

lista.Add(
new Persona(1, "Juan Perez", new DateTime(1980, 12, 10)));

lista.Add(
new Persona(2, "Maria Fernandez", new DateTime(1975, 12, 7)));

lista.Add(
new Persona(3, "Oscar Luna", new DateTime(1989, 4, 1)));

personaBindingSource.DataSource
= lista;

}




Asi que el único código que nos queda por escribir es precisamente el manejador del evento Click del botón. Es tan simple como esto:


private void btnBuscar_Click(object sender, EventArgs e)

{

int idx =personaBindingSource.Find(cbPropiedad.Text, tbValor.Text);

if (idx >= 0)

personaBindingSource.Position
= idx;

}




Eso es todo, ahora nuestra recién creada XBindingList soporta búsquedas.


Siguiente episodio: XBindingList se vuelve ordenada.