DataGridViewColumn y DataGridViewCell. Creación de columnas personalizadas para el control DataGridView (y 3)

Después de explicar el modo de dibujo y estados de presentación visual de los botones en la segunda parte, en esta tercera entrega, que concluye el presente artículo, abordaremos la interacción con las celdas de nuestra columna, pero esta vez mediante el teclado.

 

Ganando y perdiendo el foco de la celda mediante teclado… y clic de ratón

El siguiente comportamiento a programar para la celda será aquel que se produzca al dar o quitar el foco de entrada a la misma, cuando naveguemos con el teclado por las celdas del DataGridView, o bien, al hacer clic directamente sobre una celda.

Los métodos que debemos reemplazar en este caso son OnEnter y OnLeave, y el estado visual que necesitamos aplicar al botón cuando se ejecute OnEnter, consiste en el borde que revela que el botón tiene el foco de entrada. La siguiente imagen muestra ambos estados en los botones.

 

Dicho estado se aplicará en realidad dentro del código del método Paint, cuando hagamos la llamada al método ButtonRenderer.DrawButton, más concretamente, en el parámetro focused de dicho método. Utilizaremos el valor true para establecer el foco y false para quitarlo. Sin embargo, el valor del parámetro focused lo estableceremos en los métodos OnEnter y OnLeave a través de la variable bEnfocado, con ámbito a nivel de clase.

Por otro lado, también podemos encontrarnos con un efecto indeseado por una situación similar a la siguiente: supongamos que tenemos el cursor situado en una celda de nuestra columna personalizada, y mediante el teclado navegamos por otras celdas distintas de la misma columna. Es posible que la celda sobre la que se encuentra el cursor pierda su estado visual, PushButtonState.Hot, y pase a estado PushButtonState.Normal, debido a que no estamos manteniendo el índice de fila sobre la que está el cursor.

Para remediar este problema, declararemos otra nueva variable con ámbito de clase llamada nFilaCeldaHot, que será la encargada de almacenar en todo momento la fila sobre la que se encuentra el cursor. La asignación a esta variable la realizaremos en el método OnMouseEnter, mientras que en los métodos OnEnter y OnLeave consultaremos su valor, para así determinar el estado visual con que deberá dibujarse el botón cuando sea llamado el método Paint.

class DGVCeldaBoton : DataGridViewCell
{
    //....
    private bool bEnfocado = false;
    static int nFilaCeldaHot = -1;

    //....
    protected override void OnMouseEnter(int rowIndex)
    {
        base.OnMouseEnter(rowIndex);

        lstOperacion.Add(new Operacion()
        {
            Metodo = "OnMouseEnter",
            Fila = rowIndex,
            EstadoBoton = PushButtonState.Hot,
            AjustePosicionImagen = 16,
            AjusteDimensionesImagen = 28
        });
        
        // establecer el índice de fila para el estado visual del botón
        nFilaCeldaHot = rowIndex;

        this.DataGridView.InvalidateCell(this.ColumnIndex, rowIndex);
    }

    protected override void OnEnter(int rowIndex, bool throughMouseClick)
    {
        base.OnEnter(rowIndex, throughMouseClick);

        if (throughMouseClick)
        {
            xEstadoBoton = PushButtonState.Pressed;
        }
        else
        {
            if (rowIndex == nFilaCeldaHot)
            {
                xEstadoBoton = PushButtonState.Hot;
            }
        }

        bEnfocado = true;
    }

    protected override void OnLeave(int rowIndex, bool throughMouseClick)
    {
        base.OnLeave(rowIndex, throughMouseClick);

        if (rowIndex == nFilaCeldaHot)
        {
            xEstadoBoton = PushButtonState.Hot;
        }
        else
        {
            xEstadoBoton = PushButtonState.Normal;
        }

        bEnfocado = false;
    }
//....
}

Para que el nuevo comportamiento que deseamos conseguir surta efecto, nuevamente deberemos retocar el código del método Paint, de forma que el dibujo del botón refleje adecuadamente el estado que necesitamos en cada momento. En el siguiente bloque de código se muestra parte de dicho método, indicando los puntos en los que se han realizado las modificaciones.

protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds,
    int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue,
    string errorText, DataGridViewCellStyle cellStyle,
    DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
{

    //....
    // si se ha ejecutado el método OnMouseEnter u OnMouseLeave,
    // la colección lstOperacion contendrá elementos
    if (lstOperacion.Count > 0)
    {
        //....
        //....
        //....
    }
    else
    {
        // ¡¡¡modificado!!!
        // calcular posición y tamaño de la imagen a dibujar dentro del botón para el resto de métodos
        nAjustePosicionImagen = nFilaCeldaHot == rowIndex ? (cellBounds.Width * 16) / 100 : (cellBounds.Width * 12) / 100;
        nAjusteDimensionesImagen = nFilaCeldaHot == rowIndex ? (cellBounds.Width * 28) / 100 : (cellBounds.Width * 22) / 100;
    }

    // crear el rectángulo para la imagen que se situará dentro del botón
    Rectangle rctImagen = new Rectangle(rctBoton.X + nAjustePosicionImagen, rctBoton.Y + nAjustePosicionImagen,
            rctBoton.Width - nAjusteDimensionesImagen, rctBoton.Height - nAjusteDimensionesImagen);

    // ¡¡¡modificado!!!
    // dibujar el botón
    ButtonRenderer.DrawButton(graphics,
        rctBoton,
        ((DGVColumnaBoton)this.OwningColumn).ImagenBitmap,
        rctImagen,
        bEnfocado,
        xEstadoBoton);
}

En la siguiente imagen podemos apreciar los diferentes estados visuales del botón en función de que este tenga o no el foco, y el cursor del ratón esté o no situado encima de la celda.

 

  

OnKeyDown y OnKeyUp. Simulando clics mediante teclado

Además de realizar la pulsación de los botones de las celdas mediante el ratón, también puede resultar de utilidad el uso del teclado, empleando las teclas Enter y la barra espaciadora. Esta funcionalidad podemos conseguirla reemplazando los métodos OnKeyDown y OnKeyUp, donde mediante el parámetro KeyEventArgs que reciben, comprobaremos la tecla pulsada, y en caso de coincidir con alguna de las anteriormente mencionadas, estableceremos los valores correspondientes en las variables utilizadas para controlar el estado visual del botón que se dibujará en Paint. Entre las propiedades de KeyEventArgs que debemos manipular se encuentra Handled, a la que asignaremos true cuando el usuario haya pulsado alguna de las teclas mencionadas, para indicar que manejaremos de forma personalizada el comportamiento en estos casos.

Adicionalmente, para poder capturar el evento de pulsación del botón desde el código del DataGridView, llamaremos, desde el método OnKeyUp de nuestra clase DGVCeldaBoton, al método RaiseCellClick, que heredamos de DataGridViewCell.

protected override void OnKeyDown(KeyEventArgs e, int rowIndex)
{
    base.OnKeyDown(e, rowIndex);

    if (e.KeyCode == Keys.Space || e.KeyCode == Keys.Enter)
    {
        xEstadoBoton = PushButtonState.Pressed;
        this.DataGridView.InvalidateCell(this.ColumnIndex, rowIndex);
        e.Handled = true;
    }
}

protected override void OnKeyUp(KeyEventArgs e, int rowIndex)
{
    base.OnKeyUp(e, rowIndex);

    if (e.KeyCode == Keys.Space || e.KeyCode == Keys.Enter)
    {
        if (rowIndex == nFilaCeldaHot)
        {
            xEstadoBoton = PushButtonState.Hot;
        }
        else
        {
            xEstadoBoton = PushButtonState.Normal;
        }

        this.RaiseCellClick(new DataGridViewCellEventArgs(this.OwningColumn.Index, rowIndex));
        this.DataGridView.InvalidateCell(this.ColumnIndex, rowIndex);
        e.Handled = true;
    }
}

 

Escribiendo el evento CellClick para el DataGridView

Llegados a este punto hemos finalizado la escritura del código para la clase DGVCeldaBoton. Volviendo al código del formulario del proyecto, escribiremos el manipulador para el evento CellClick del control DataGridView, donde al pulsar uno de los botones de nuestra columna personalizada, realizaremos un cálculo basado en la celda de la columna ListPrice, perteneciente a la fila sobre la que hemos pulsado, y en el TextBox del formulario.

private void dataGridView1_CellClick(object sender, DataGridViewCellEventArgs e)
{
    if (this.txtPorcentaje.Text == string.Empty)
    {
        MessageBox.Show("Introducir valor para el porcentaje");
        this.txtPorcentaje.Focus();
    }
    else
    {
        if (this.dataGridView1.Columns[e.ColumnIndex].Name == "colBotones")
        {
            decimal nListPrice = (decimal)this.dataGridView1.Rows[e.RowIndex].Cells["ListPrice"].Value;
            int nPorcentaje = int.Parse(this.txtPorcentaje.Text);
            decimal nDescuento = (nListPrice * nPorcentaje) / 100;

            MessageBox.Show(nDescuento.ToString("#,#.##"), "Descuento aplicable");
        }
    }
}

El resultado de la operación lo visualizaremos en una caja de mensaje. 

 

Y llegados a este punto, damos por concluido esta tercera entrega y el presente artículo, en el que paso a paso, hemos expuesto una técnica que nos permite, partiendo de cero, crear una columna personalizada para el control DataGridView, adaptando su comportamiento a nuestros requerimientos. En los enlaces C# y VB encontraremos el proyecto con el código fuente de este ejemplo. Espero que os resulte de ayuda.

Un saludo.

3 Comentarios

  1. lmblanco

    Hola Enrique

    Gracias a tí por leerlo y un abrazo 8-D

    Luismi

  2. anonymous

    muy útil, lo estoy usando para controles que he creado ya que no uso infragistics.

  3. lmblanco

    Hola Jorge

    Celebro que te esté resultando útil.

    Un saludo,
    Luismi

Responder a Cancelar respuesta

Tema creado por Anders Norén