Formatear y resaltar valores en el control DataGridView con el evento CellFormatting (II)
Siguiendo con los aspectos relacionados con el formateo de valores utilizando este evento, en la presente entrega abordaremos la posibilidad de aplicar formatos basados en una condición y sobre una columna a partir de los valores existentes en otras.
Formato condicional
Las operaciones tratadas hasta el momento podríamos haberlas realizado prescindiendo del evento CellFormatting, ya que al aplicar el formato a la totalidad de la columna, habría bastado con seleccionar de la colección DataGridView.Columns aquella columna a formatear, y mediante la propiedad DefaultCellStyle -que contiene un tipo DataGridViewCellStyle-asignar a la propiedad Format la cadena de formato adecuada. Esto podemos hacerlo, por ejemplo, en el evento Load.
private void frmFormatoCondicional_Load(object sender, EventArgs e)
{
this.dataGridView1.DataSource = ConstruirDatos.ObtenerTabla();
this.dataGridView1.Columns["BirthDate"].DefaultCellStyle.Format = "MMMM (yyyy)";
}

Es por ello que un uso más conveniente del evento CellFormatting consistiría en utilizarlo para formatear las celdas de una columna que cumplan una determinada condición, como podemos ver en el siguiente bloque de código, donde tras verificar que para la columna de tipo fecha, la celda a formatear contiene valor, si este corresponde al mes de febrero, se le aplica una cadena de formato para resaltar dicha celda.
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (this.dataGridView1.Columns[e.ColumnIndex].Name == "BirthDate")
{
try
{
// comprobar si la celda tiene contenido válido
if (e.Value.GetType() != typeof(System.DBNull))
{
// si la celda tiene valor y el mes es febrero
// formatear el contenido
if (((DateTime)e.Value).Month == 2)
{
e.CellStyle.Format = "D";
e.CellStyle.BackColor = Color.Aqua;
}
}
}
catch (NullReferenceException oException)
{
}
}
}
A continuación apreciamos el resultado en la siguiente imagen.

Formato condicional aplicado a la totalidad de la columna
En ciertas ocasiones puede resultar interesante aplicar una operación de formato de modo global a todos los valores de una misma columna, consistente por ejemplo en modificar el texto original por otro más extenso, como podemos ver en el siguiente código, donde sustituimos los valores que inicialmente provienen de la columna Title de la tabla de datos.
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
// cambiar el valor del campo Title
if ((this.dataGridView1.Columns[e.ColumnIndex].Name == "Title") && (e.Value != null))
{
string TituloCortesia = string.Empty;
switch (e.Value.ToString())
{
case "Sr.":
case "Mr.":
TituloCortesia = "Señor";
break;
case "Mrs.":
TituloCortesia = "Señora";
break;
case "Ms.":
case "Ms":
TituloCortesia = "Señorita";
break;
}
e.Value = TituloCortesia;
}
}
Debido a la naturaleza del formato a realizar, este no puede acometerse asignando valores a los miembros de la propiedad DefaultCellStyle, perteneciente a la colección DataGridView.Columns -como veíamos en un caso anterior. Dado que los cambios necesarios no siguen un patrón prefijado, resulta necesario resolver el problema desde el flujo del código, dentro del evento CellFormatting.

Formato indirecto
Como acabamos de comprobar, el formato condicional resulta de utilidad cuando deseamos aplicar un formato a las celdas de una columna en base a una expresión evaluada contra los valores de dicha columna. Sin embargo también podemos aplicar una variante de esta técnica que consistiría en realizar el formato sobre una celda, pero esta vez en función de la información proveniente de celdas pertenecientes a otras columnas.
Pongamos como ejemplo, que en los datos con los que estamos operando, necesitamos destacar el nombre de los clientes cuyos ingresos anuales superen una determinada cantidad. Para ello, dentro del evento CellFormatting, cuando corresponda aplicar formato a las columnas FirstName y LastName, comprobaremos el valor de la celda correspondiente a la columna YearlyIncome, y según sea su valor, resaltaremos o no las celdas del nombre. Todo esto podemos comprobarlo en el siguiente código.
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
// si la celda a formatear corresponde al nombre del cliente...
if ((this.dataGridView1.Columns[e.ColumnIndex].Name == "FirstName") ||
(this.dataGridView1.Columns[e.ColumnIndex].Name == "LastName"))
{
//...y sus ingresos superan una cierta cantidad,
// formatear las celdas del nombre
if ((decimal)this.dataGridView1.Rows[e.RowIndex].Cells["YearlyIncome"].Value >= 100000)
{
e.CellStyle.BackColor = Color.PaleGreen;
e.Value = e.Value.ToString().ToUpper();
}
}
}
La siguiente imagen muestra el resultado de este tipo de formato.

Formato indirecto utilizando una columna con imágenes
Una variante de la técnica ofrecida en el apartado anterior consistiría en utilizar una columna de tipo imagen -DataGridViewImageColumn- para mostrar una figura que represente el valor de una columna. Dentro de la tabla que estamos usando para nuestros ejemplos, un claro candidato lo representaría la columna Gender, que nos informa acerca del género -masculino o femenino-correspondiente al cliente.
A efectos visuales, dicha columna podemos sustituirla por una de imagen, que muestre una figura identificativa, pero sin eliminar la columna Gender -que quedaría oculta-, ya que necesitaremos recurrir a ella para consultar su valor y saber qué imagen situar en la celda en curso.
En primer lugar agregaremos al proyecto sendos archivos de tipo imagen, que configuraremos en su propiedad Build Action para que se comporten como recursos incrustados -Embedded Resource.

A continuación pasaremos al código del formulario, donde en el evento Load ocultaremos la columna Gender y crearemos una nueva para contener las imágenes. Por otro lado, en el evento CellFormatting detectaremos cuando debe ser dibujada la celda de la nueva columna de imagen, y en dicho momento, comprobaremos el valor de la columna oculta Gender, asignando la imagen correcta. Todas estas operaciones las podemos ver en el siguiente código fuente.
private void frmFormatoIndirectoImagen_Load(object sender, EventArgs e)
{
this.dataGridView1.DataSource = ConstruirDatos.ObtenerTabla();
// ocultar la columna que indica el género del cliente
this.dataGridView1.Columns["Gender"].Visible = false;
// crear una nueva columna de tipo imagen
// para mostrar un icono indicativo del género del cliente
// y agregarla a la colección de columnas del control
DataGridViewImageColumn colGenero = new DataGridViewImageColumn();
colGenero.Name = "colGenero";
this.dataGridView1.Columns.Add(colGenero);
}
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
// cuando se vaya a pintar la celda de la columna de imagen
if (this.dataGridView1.Columns[e.ColumnIndex].Name == "colGenero")
{
// establecer la altura de la fila para que se pueda visualizar correctamente
this.dataGridView1.Rows[e.RowIndex].Height = 40;
// asignar la imagen adecuada
switch ((string)this.dataGridView1.Rows[e.RowIndex].Cells["Gender"].Value)
{
case "M":
e.Value = new Bitmap(this.GetType(), "SimboloMasculino.jpg");
break;
case "F":
e.Value = new Bitmap(this.GetType(), "SimboloFemenino.jpg");
break;
}
}
}
En la siguiente imagen podemos observar el efecto de esta nueva columna en tiempo de ejecución. Como dato curioso, el valor del campo Gender en la tabla de la base de datos no se corresponde correctamente en todas las ocasiones.

La próxima entrega, que finaliza esta serie de artículos, abordará algunos aspectos adicionales de este tipo de operaciones. Como ya comentamos en la primera parte, los ejemplos están disponibles en estos enlaces: C# y VB.
Un saludo.