Formatear y resaltar valores en el control DataGridView con el evento CellFormatting (I)

Aunque las capacidades de formato que proporciona el control DataGridView puedan resultar, en principio, sencillas de implementar, con cierta regularidad encuentro en los foros de Microsoft consultas referentes a cómo conseguir que la información de las celdas que este control muestra sea personalizable, para adaptarse a las exigencias del usuario.


DataGridView es un control potente, que nos permite visualizar una gran cantidad de datos en forma tabular, pero su aspecto predeterminado no resulta demasiado atractivo si lo que necesitamos es resaltar ciertos elementos para llamar la atención del usuario.



Sin embargo esto no quiere decir que carezca de la funcionalidad necesaria para ofrecer, si es menester, una imagen lo suficientemente atrayente.


Como ejemplo de dichas características, en esta serie de artículos abordaremos el evento CellFormatting, que combinado con las capacidades de creación y aplicación de estilos visuales, ofrece al programador un potente y flexible mecanismo que le permite formatear y resaltar los datos presentados.


CellFormatting es un evento que se desencadena para cada celda del DataGridView que es dibujada. Su funcionamiento por defecto consiste en convertir el dato a mostrar en el valor más adecuado a efectos de presentación (habitualmente texto). Pero dado que para ciertos valores, una manipulación más elaborada del dato puede ser necesaria, se expone el evento al programador para que pueda adaptarlo a sus necesidades.


Seguidamente vamos a mostrar un conjunto de los modos más habituales en que podemos formatear los valores de las celdas del control utilizando este evento.


 


El proyecto base de los ejemplos


Una  vez creado un nuevo proyecto en Visual Studio que nos sirva como banco de pruebas, y dado que para cada ejemplo a mostrar utilizaremos un formulario diferente, emplearemos el formulario por defecto que incluye el proyecto como «lanzadera» del resto de formularios, donde añadiremos un ListBox con los nombres de cada tipo de operación de formato junto a un botón para abrir el correspondiente formulario.



A continuación podemos ver el código necesario para las mencionadas operaciones.


private void btnVisualizar_Click(object sender, EventArgs e)
{
Form frmFormulario;

switch (this.listBox1.SelectedIndex)
{
case 0:
frmFormulario = new frmFormatoBasico();
frmFormulario.Show();
break;

case 1:
frmFormulario = new frmFormatoColores();
frmFormulario.Show();
break;

case 2:
frmFormulario = new frmFormatoCondicional();
frmFormulario.Show();
break;

case 3:
frmFormulario = new frmFormatoCondicionalCambiarTodo();
frmFormulario.Show();
break;

case 4:
frmFormulario = new frmFormatoIndirecto();
frmFormulario.Show();
break;

case 5:
frmFormulario = new frmFormatoIndirectoImagen();
frmFormulario.Show();
break;

case 6:
frmFormulario = new frmFormatoTipoDato();
frmFormulario.Show();
break;

case 7:
frmFormulario = new frmFormatoExpresionRegulada();
frmFormulario.Show();
break;

case 8:
frmFormulario = new frmFormatoFilaCompleta();
frmFormulario.Show();
break;
}
}


 


Preparación de los datos a visualizar


En todos los ejemplos mostrados vamos a utilizar una consulta contra la tabla DimCostumer, perteneciente a la base de datos AdventureWorksDW. Para evitar en cada formulario del proyecto la repetición del código que conecta con la fuente de datos y construye el DataSet encargado de rellenar el control DataGridView, vamos a crear una clase con un método estático, que nos devuelva la mencionada tabla, la cual asignaremos a la propiedad DataSource del control DataGridView.


class ConstruirDatos
{
public static DataTable ObtenerTabla()
{
// crear la sentencia sql para obtener las filas
StringBuilder sbSQL = new StringBuilder();
sbSQL.Append(«SELECT CustomerAlternateKey, «);
sbSQL.Append(«Title, FirstName, LastName, «);
sbSQL.Append(«BirthDate, YearlyIncome, Gender «);
sbSQL.Append(«FROM DimCustomer «);
sbSQL.Append(«WHERE Title IS NOT NULL»);

// conectar con la base de datos,
// ejecutar la consulta y devolver el resultado
SqlConnection cnConexion = new SqlConnection();
cnConexion.ConnectionString = «Data Source=localhost;» +
«Initial Catalog=AdventureWorksDW;» +
«Integrated Security=True»;

SqlCommand cmdComando = new SqlCommand(sbSQL.ToString(), cnConexion);
SqlDataAdapter daAdaptador = new SqlDataAdapter(cmdComando);
DataSet dsDatos = new DataSet();

cnConexion.Open();
daAdaptador.Fill(dsDatos, «DimCustomer»);
cnConexion.Close();

return dsDatos.Tables[0];
}
}


 


Formato básico. La primera aproximación a CellFormatting


Nuestro primer ejemplo consistirá en aplicar un formato a los datos que con mayor frecuencia se prestan a realizar este tipo de acción: fechas y números; por lo que tras crear en primer lugar el manipulador de evento para CellFormatting, procederemos a dotarle de funcionalidad.


El evento CellFormatting recibe como parámetro un tipo DataGridViewCellFormattingEventArgs, cuyas propiedades serán las que nos proporcionen la información y soporte para aplicar el formato necesario en cada ocasión.


Debemos comenzar comprobando que la columna sobre la que se está produciendo el evento corresponde a aquella que necesitamos formatear, para lo que utilizaremos la propiedad DataGridViewCellFormattingEventArgs.ColumnIndex aplicada a la colección Columns del DataGridView. Una vez realizada esta verificación, con la propiedad DataGridViewCellFormattingEventArgs.CellStyle accederemos a un tipo DataGridViewCellStyle, cuyos miembros serán los que nos ofrezcan todas las posibilidades de formato, efectos de colores, alineación, etc., disponibles para adaptar la visualización del dato existente en la celda.


En este caso concreto emplearemos la propiedad DataGridViewCellFormattingEventArgs.CellStyle.Format para asignar una cadena de formato sobre las columnas BirthDate y YearlyIncome del control. Dicha cadena puede estar basada en una de las predefinidas por la plataforma .Net Framework, o ser una personalizada, construida por el programador empleando los caracteres de formato especiales disponibles a tal efecto. En el siguiente bloque de código mostramos un ejemplo de cada tipo.


public partial class frmFormatoBasico : Form
{
public frmFormatoBasico()
{
InitializeComponent();
}

//….

private void frmFormatoBasico_Load(object sender, EventArgs e)
{
this.dataGridView1.DataSource = ConstruirDatos.ObtenerTabla();
}

private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
// formatear fecha
if (this.dataGridView1.Columns[e.ColumnIndex].Name == «BirthDate»)
{
e.CellStyle.Format = «D»;
}

// formatear valor numérico monetario
if (this.dataGridView1.Columns[e.ColumnIndex].Name == «YearlyIncome»)
{
e.CellStyle.Format = «#,#.0 €»;
}
}
}


En la siguiente imagen podemos apreciar el resultado de esta operación de formato.



 


Aplicando colores al formato


Además de los procedimientos de formato, que como acabamos de ver, modifican la manera en que el dato es mostrado, podemos cambiar otros aspectos en la visualización de la celda tales como la combinación de colores -en estado normal y seleccionado-, alineación del contenido, etc.


Las propiedades BackColor, ForeColor, SelectionBackColor, SelectionForeColor y Alignment, de la clase DataGridViewCellStyle, nos ofrecen la posibilidad de manipular estas características, tal y como vemos en el siguiente bloque de código. Nótese que para los colores en estado seleccionado de la columna LastName, aplicamos una técnica consistente en asignar la combinación de colores normales pero en sentido inverso.


private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (this.dataGridView1.Columns[e.ColumnIndex].Name == «FirstName»)
{
// aplicar colores a celda en estado normal
e.CellStyle.BackColor = Color.MediumTurquoise;
e.CellStyle.ForeColor = Color.DarkOrchid;

// aplicar colores a celda en estado seleccionado
e.CellStyle.SelectionBackColor = Color.DarkOliveGreen;
e.CellStyle.SelectionForeColor = Color.Yellow;

// establecer alineación del texto
e.CellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
}

if (this.dataGridView1.Columns[e.ColumnIndex].Name == «LastName»)
{
// aplicar colores a celda en estado normal
e.CellStyle.BackColor = Color.MediumSeaGreen;
e.CellStyle.ForeColor = Color.LightYellow;

// aplicar colores a celda en estado seleccionado
e.CellStyle.SelectionBackColor = e.CellStyle.ForeColor;
e.CellStyle.SelectionForeColor = e.CellStyle.BackColor;

// establecer alineación del texto
e.CellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
}
}


El efecto del uso de estas propiedades de estilo podemos apreciarlo en la siguiente imagen, donde vemos las celdas con los colores asignados para su estado normal y seleccionado.



El código fuente de los ejemplos puede descargarse desde los siguientes enlaces: C# y VB. Estos proyectos contienen los ejemplos al completo pertenecientes a toda esta serie de artículos.


En la siguiente entrega, más características interesantes de este evento.


Un saludo.


 

15 Comentarios

  1. anonymous

    Después de mucho batallar con el DataGridView, una de las cosas que he aprendido es que para comparar columnas en el CellFormat (en caso de que las columnas sean objetos «DataGridView…Column» es a través de la propiedad Index, es decir.
    Suponiento que la columna «FirstName» sea del objeto FirstNameColumn, la comparación seria:
    if (FirstNameColumn.Index == e.ColumnIndex)
    Asi te evitas comparar por strings(el cual puede cambiar), y por tanto es mas costoso y sobretodo en un evento como el CellFormat que se lanza muy a menudo.

  2. lmblanco

    Hola Javier

    Muchas gracias por el apunte, resulta un detalle muy interesante de cara a lograr un mayor rendimiento en el proceso de formateo de valores dentro del grid 8-).

    No obstante, si no me equivoco, este modo de comprobar la columna sobre la que vamos a operar solamente podría ser aplicado cuando hayamos definido las columnas del DataGridView en tiempo de diseño, ya que de esa forma, se crean previamente los diferentes objetos derivados de las clases DataGridViewXXXColumn mediante los que podemos aplicar esta técnica.

    Gracias de nuevo, y lo incluiré como truco en uno de estos posts sobre formateo de valores con el DataGridView que estoy publicando en el blog 8-D.

    Un saludo,
    Luismi

  3. anonymous

    Hola Luis Miguel, ante todo me disculpo por ser novato y por ende no saber explicarme bien. Yo tome una clase de columnas personalizadas publicada por ti (donde das un ejemplo con un DatetimePicker ) y la modifique para hacer mi propia clase «DataGridViewNumericColumn» a la que le he agregado unas propiedades tales como decimales (numeros despues de la coma) y simbolo (pesos, euros, cm3 etc) . vamos al problema en si: en el vb 2005 (version express) cuando miro las propiedades de de mi columna personalizada aparecen «simbolo» y «decimales» pero al asignarles un valor este valor no se me «guarda» vale decir solo puedo asignarles valor por codigo pero nunca en diseño…. no se si me he explicado , desde ya , muchisimas gracias por leer esto , tan confuso y engorroso y saludos desde Argentina.

  4. lmblanco

    Hola Pablo

    Para crear una columna con edición de celdas personalizada en un DataGridView, como habrás podido comprobar en el ejemplo que publiqué usando DatetimePicker, hace falta desarrollar tres clases: una de ellas hereda de DataGridViewColumn y contendrá las propiedades que expones en diseño de la columna; la otra clase hereda de DataGridViewTextBoxCell y se encarga de hacer persistentes los valores que has asignado en la clase de la columna; la tercera clase hereda del control que quieres utilizar dentro de la celda para editar el valor e implementa la interfaz IDataGridViewEditingControl.

    Por la descripción del error que me comentas, es posible que el problema resida en la clase derivada de DataGridViewTextBoxCell, ya que es en esta donde se «escriben» el contenido de las propiedades, dentro de un método llamado InitializeEditingControl, y desde esta clase se instancia un objeto de la tercera clase –la que implementa IDataGridViewEditingControl– que representa el control que usaremos para editar el valor, pasando también estas propiedades a este control.

    En el número de la revista dotNetMania de este mes he publicado precisamente un artículo sobre este aspecto. En la web de la revista tienes disponible un enlace para descargar el código fuente del ejemplo:

    http://www.dotnetmania.com/Articulos/047/Apoyo/SustituirEditorDGV.rar

    De dicho ejemplo te adjunto a continuación un extracto de lo que te he comentado. Espero que sea por este motivo que te dije donde se encuentra el problema.

    public class NumArribaAbajoColumn : DataGridViewColumn
    {
    // campos
    private decimal nMinimo;
    private decimal nMaximo;

    // en el constructor establecemos el tipo de celda
    // que utilizará esta columna
    public NumArribaAbajoColumn()
    : base(new NumArribaAbajoCell())
    {
    // modo de ordenación de los datos
    this.SortMode = DataGridViewColumnSortMode.Automatic;
    }

    // obtener o asignar el tipo de celda (objeto derivado de DataGridViewCell)
    // que se utilizará para editar el valor
    public override System.Windows.Forms.DataGridViewCell CellTemplate
    {
    get
    {
    return base.CellTemplate;
    }
    set
    {
    if ((value != null) && (!value.GetType().IsAssignableFrom(typeof(NumArribaAbajoCell))))
    {
    InvalidCastException oException;
    oException = new InvalidCastException(«La plantilla de celda no es de tipo NumArribaAbajoCell»);
    }

    base.CellTemplate = value;
    }
    }

    // propiedades para configurar la celda cuando
    // editemos su valor mediante el control NumericUpDown
    public decimal Minimo
    {
    get { return nMinimo; }
    set { nMinimo = value; }
    }

    public decimal Maximo
    {
    get { return nMaximo; }
    set { nMaximo = value; }
    }
    //….
    //….
    }

    //————————————————

    class NumArribaAbajoCell : DataGridViewTextBoxCell
    {
    //….
    // obtener el tipo de control utilizado para editar la celda
    public override Type EditType
    {
    get { return typeof(NumArribaAbajoEditingControl); }
    }

    // obtener el tipo de dato que es editado en la celda
    public override Type ValueType
    {
    get { return typeof(Decimal); }
    }

    // establecer el valor por defecto para una nueva celda
    public override object DefaultNewRowValue
    {
    get { return 0; }
    }

    // crear y alojar en la celda una instancia del control
    // utilizado para editar el valor de la celda
    public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
    {
    base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);

    // obtener el control que usaremos para editar el valor de la celda
    NumArribaAbajoEditingControl ctlEditorCelda = (NumArribaAbajoEditingControl)DataGridView.EditingControl;

    // obtener el objeto columna propietario de la celda que vamos a editar
    NumArribaAbajoColumn colNumArribaAbajo = (NumArribaAbajoColumn)this.OwningColumn;

    // asignar valor a las propiedades del control editor
    ctlEditorCelda.Minimum = colNumArribaAbajo.Minimo;
    ctlEditorCelda.Maximum = colNumArribaAbajo.Maximo == 0 ? 100 : colNumArribaAbajo.Maximo;

    // comprobar si el valor de la celda que debemos editar
    // está en el rango admisible por el control NumericUpDown
    if (this.Value == DBNull.Value)
    {
    ctlEditorCelda.Value = 0;
    }
    else
    {
    if (((decimal)this.Value >= ctlEditorCelda.Minimum) &&
    ((decimal)this.Value <= ctlEditorCelda.Maximum)) { ctlEditorCelda.Value = (decimal)this.Value; } else { this.RaiseDataError(new DataGridViewDataErrorEventArgs( new Exception(string.Format("Valor {0} incorrecto. Rango admisible de valores de {1} a {2}", this.Value, ctlEditorCelda.Minimum, ctlEditorCelda.Maximum)), this.ColumnIndex, this.RowIndex, DataGridViewDataErrorContexts.Display)); this.Value = ctlEditorCelda.Minimum; } } } } //------------------------------------------------ class NumArribaAbajoEditingControl : NumericUpDown, IDataGridViewEditingControl { // campos private DataGridView oDGV; private int nIndiceFila; private bool bValorCambiado = false; #region Miembros de IDataGridViewEditingControl public DataGridView EditingControlDataGridView { get { return oDGV; } set { oDGV = value; } } public object EditingControlFormattedValue { get { return this.Value.ToString(); } set { decimal nValorDecimal; if (decimal.TryParse(value.ToString(), out nValorDecimal)) { this.Value = nValorDecimal; } else { this.Value = 0; } } } //.... //.... } Espero que te sirva de ayuda. Un saludo. Luismi

  5. anonymous

    Nuevamente muchisimas gracias, he leido con detenimiento el problema y es correcto: InitializeEditingControl es mi problema, es por ello que no se me persisten los datos… ahora voy a tratar de que todo funcione y la adjuntare para que algun otro principiante como yo le pueda dar utilidad… muchas gracias Luis Miguel!!!!

  6. lmblanco

    Hola Pablo

    No hay de qué, me alegra que hayas localizado el origen del problema con las indicaciones que te comenté.

    Un saludo.
    Luismi

  7. anonymous

    amigo, esta muy interesante todo esto pero quisiera me ayudaras en mi caso, yo necesito cambiarle el color a una celda tipo fecha de acuerdo a tiempo que tenga de vencida, en el datagrid hay cuatro celdas con fechas y en cada celda necesito que el sistema de una alerta visual de distintos colores de acuerdo a tiempo que tenga de vencidas. gracias por tu ayuda de antemano

  8. lmblanco

    Hola Daniel

    Puedes probar a asociar la celda a un temporizador, que una vez cumplido el tiempo estipulado, resalte visualmente la celda al usuario. En el siguiente ejemplo esta acción se realiza en el evento CellFormatting del grid.

    //——————————–
    public partial class Form2 : Form
    {
    Timer tmrTimer1;
    int nFila;
    int nColumna;

    private void Form2_Load(object sender, EventArgs e)
    {
    SqlConnection oConexion;
    oConexion = new SqlConnection();
    oConexion.ConnectionString = «Data Source=localhost;» +
    «Initial Catalog=Northwind;» +
    «User ID=sa;Password=»;

    SqlCommand cmdCategories;
    cmdCategories = new SqlCommand(«SELECT CategoryID, CategoryName FROM Categories»,
    oConexion);

    SqlDataAdapter daCategories;
    daCategories = new SqlDataAdapter(cmdCategories);

    DataSet oDataSet;
    oDataSet = new DataSet();
    daCategories.Fill(oDataSet, «Categories»);

    this.grdDatos.DataSource = oDataSet;
    this.grdDatos.DataMember = «Categories»;
    }
    private void grdDatos_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
    {
    if (this.grdDatos.Columns[e.ColumnIndex].Name == «CategoryID»)
    {
    if (e.Value != null)
    {
    if ((int)e.Value == 5)
    {
    nFila = e.RowIndex;
    nColumna = e.ColumnIndex;
    tmrTimer1 = new Timer();
    tmrTimer1.Tick += new EventHandler(tmrTimer1_Tick);
    tmrTimer1.Interval = 3000;
    tmrTimer1.Start();
    }
    }
    }
    }
    void tmrTimer1_Tick(object sender, EventArgs e)
    {
    this.grdDatos.Rows[nFila].Cells[nColumna].Style.BackColor = Color.Aquamarine;
    tmrTimer1.Stop();
    }
    }
    //——————————–

    Espero que te resulte de utilidad.

    Un saludo.
    Luismi

  9. anonymous

    Hola, tengo una consulta, yo uso un datagridview que tiene la opcion de ir recibir datos por el usuario, pero yo deseo que en cuanto presiona la tecla enter baje a la siguiente celda pero ya con el foco puesto en la celda 4 de la columna 3 por ejemplo,
    he intentado pero no encuentro la opcion adecuada y con ello genero que el usuario siempre que presiona enter se queda en una celda adelantada, no sè si me expliquè bien, gracias.

  10. lmblanco

    Hola Óscar

    Creo que puedes conseguir el efecto que mencionas utilizando la propiedad EditMode del control DataGridView. Dicha propiedad contiene un valor de la enumeración DataGridViewEditMode, que te permite configurar el modo en cómo se va a producir la edición de los valores en las celdas del grid. Si asignas el valor EditOnEnter a dicha propiedad, al pulsar Enter, el usuario entrará directamente en modo de edición de la celda, y no como ocurre por defecto que en primer lugar nos situamos en la celda y después hay que ponerla en modo editable.

    Espero que te sea de utilidad.

    Un saludo.
    Luismi

  11. anonymous

    brother esta bien pero … se un buen aporte si te cuelgas info de la otros eventos de l datagridview…

  12. anonymous

    Consulta: De una base de datos access 2007 con c# levanto una tabla en la que la fecha la tengo guardada 201006011200 como string año mes dia hora minutos. En el datagridview quiero mostrarla como 01/06/2010 y esto para cada una de las filas.
    Alguien puede ayudarme y decirme como puedo hacer esto?
    Mi mail es florencia.sarasua@gmail.com
    Muchisimas gracias

  13. anonymous

    A raíz de un artículo que publiqué sobre diversos aspectos de la presentación

  14. anonymous

    Buen aporte amigo

  15. lmblanco

    Hola Lenisma

    Gracias por tu opinión, celebro que te gustara el post.

    Un saludo,
    Luismi

Responder a Cancelar respuesta

Tema creado por Anders Norén