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

En la primera entrega de este artículo, el desarrollo de nuestra columna quedó pendiente de mostrar su contenido, aspecto que abordaremos en esta segunda parte, donde además de realizar el dibujo de las celdas y los botones que estas alojan, trataremos sobre la interacción que el usuario realiza sobre las mismas mediante el cursor del ratón.

 

Paint y ButtonRenderer.DrawButton. Dibujando la celda y su contenido

El problema radica en que no hemos escrito el código necesario para que se realice el dibujo de la celda y su contenido, lo cual haremos a continuación, reemplazando el método Paint de esta clase.

class DGVCeldaBoton : DataGridViewCell
{
    //....
    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)
    {
        // llamar al método base
        base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue,
            errorText, cellStyle, advancedBorderStyle, paintParts);

        // pintar borde de la celda
        base.PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle);

        // crear el rectángulo que contendrá el botón,
        // calcular su posición y dimensiones
        int nAjustePosicionBoton = (cellBounds.Width * 10) / 100;
        int nAjusteDimensionesBoton = (cellBounds.Width * 20) / 100;
        Rectangle rctBoton = new Rectangle(cellBounds.X + nAjustePosicionBoton, cellBounds.Y + nAjustePosicionBoton,
            cellBounds.Width - nAjusteDimensionesBoton, cellBounds.Height - nAjusteDimensionesBoton);

        // crear el rectángulo que contendrá la imagen,
        // calcular su posición y dimensiones
        int nAjustePosicionImagen = (cellBounds.Width * 12) / 100;
        int nAjusteDimensionesImagen = (cellBounds.Width * 22) / 100;
        Rectangle rctImagen = new Rectangle(rctBoton.X + nAjustePosicionImagen, rctBoton.Y + nAjustePosicionImagen,
            rctBoton.Width - nAjusteDimensionesImagen, rctBoton.Height - nAjusteDimensionesImagen);

        ButtonRenderer.DrawButton(graphics,
            rctBoton,
            ((DGVColumnaBoton)this.OwningColumn).ImagenBitmap,
            rctImagen, 
            false, 
            PushButtonState.Normal);
    }
//....

Como podemos apreciar en el código anterior, el parámetro cellBounds, que recibe el método Paint, nos servirá como base para calcular las superficies rectangulares del botón y la imagen que se alojarán en cada celda de la columna. Una vez realizados todos estos cálculos, dibujaremos el botón mediante la clase ButtonRenderer y su método estático DrawButton. En la sobrecarga que actualmente utilizamos de este método, los parámetros que recibe pertenecen a los siguientes tipos:

–Graphics. Tipo que encapsula la superficie de dibujo GDI+ sobre la que se va a realizar la operación de dibujo.

–Rectangle. Superficie que contendrá el botón.

–Image. Imagen que se dibujará sobre el botón.

–Rectangle. Superficie para dibujar la imagen que se situará encima del botón.

–Boolean. Permite indicar si el botón mostrará el rectángulo indicativo de haber obtenido el foco de entrada del usuario.

–PushButtonState. Tipo enumerado que permite establecer el estado visual del botón. 

En la siguiente imagen apreciamos el resultado en tiempo de ejecución.

 

 

OnMouseEnter y OnMouseLeave. Respondiendo a las acciones del usuario con el ratón

Nuestro siguiente paso consistirá en reemplazar ciertos métodos de la clase base DataGridViewCell, para poder dotar al botón del comportamiento visual que necesitemos, como respuesta a las acciones del usuario.

Comenzaremos por OnMouseEnter y OnMouseLeave, los cuales son llamados al entrar y salir, respectivamente, el cursor del ratón en la celda.

La mecánica básica que debemos implementar en estos métodos es simple: llamar al método base e invalidar la celda para provocar una llamada al método Paint, que obligue a pintarla de nuevo.

protected override void OnMouseXXXr(int rowIndex)
{
    base.OnMouseXXX(rowIndex);
    this.DataGridView.InvalidateCell(this.ColumnIndex, rowIndex);
}

Sin embargo nos encontramos ante un problema: si el cursor está situado sobre una celda de nuestra columna personalizada y nos movemos a una celda adyacente de la misma columna, los métodos OnMouseEnter y OnMouseLeave se ejecutarán seguidos en primer lugar, y a continuación se ejecutarán los dos métodos Paint, como respuesta a haber invalidado visualmente las celdas en OnMouseEnter y OnMouseLeave. En el siguiente esquema podemos apreciar el flujo de ejecución que acabamos de describir.

 

Si el desplazamiento del cursor se realiza desde abajo hacia arriba, el orden de ejecución del método Paint sigue siendo el mismo, es decir, si movemos el cursor desde la fila 3 a la 2, Paint se ejecutará en primer lugar para la celda de la fila 2 y después para la fila 3.

Puesto que en este escenario se producen dos ejecuciones seguidas de Paint, necesitamos saber cuál ha sido debida a OnMouseEnter y cuál a OnMouseLeave. La técnica que vamos a emplear para solucionar el problema consistirá en pasar a Paint, desde cada uno de los métodos OnMouseXXX, información suficiente para saber cómo dibujar el botón.

El medio de transporte consistirá en una nueva clase que llamaremos Operación, cuyo código, que vemos a continuación, añadiremos a nuestro proyecto.

class Operacion
{
    public string Metodo;
    public int Fila;
    public PushButtonState EstadoBoton;
    public int AjustePosicionImagen;
    public int AjusteDimensionesImagen;
}

En cada uno de los métodos OnMouseEnter y OnMouseLeave, crearemos una instancia de esta clase, añadiéndola a una colección genérica basada en dicho tipo, que habremos declarado con ámbito a nivel de la clase DGVCeldaBoton. Por otra parte, para poder manipular con más comodidad el estado visual del botón a dibujar, también declararemos, con el mismo ámbito, una variable de tipo PushButtonState, que inicializaremos en el constructor de la clase DGVCeldaBoton.

class DGVCeldaBoton : DataGridViewCell
{
    private List<Operacion> lstOperacion = new List<Operacion>();
    private PushButtonState xEstadoBoton;
    
    public DGVCeldaBoton()
        : base()
    {
        // establecer el estado visual inicial para el botón en el constructor
        xEstadoBoton = PushButtonState.Normal;
    }
    //....
    protected override void OnMouseEnter(int rowIndex)
    {
        base.OnMouseEnter(rowIndex);

        lstOperacion.Add(new Operacion()
        {
            Metodo = "OnMouseEnter",
            Fila = rowIndex,
            EstadoBoton = PushButtonState.Hot,
            AjustePosicionImagen = 16,
            AjusteDimensionesImagen = 28
        });

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

    protected override void OnMouseLeave(int rowIndex)
    {
        base.OnMouseLeave(rowIndex);

        lstOperacion.Add(new Operacion()
        {
            Metodo = "OnMouseLeave",
            Fila = rowIndex,
            EstadoBoton = PushButtonState.Normal,
            AjustePosicionImagen = 12,
            AjusteDimensionesImagen = 22
        });

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

Pasando al código del método Paint, ahora comprobaremos si la colección lstOperacion contiene elementos (indicativo de que se ha ejecutado un método OnMouseXXX), y en caso afirmativo, obtendremos de la colección (mediante una expresión LINQ) el objeto Operacion correspondiente a la fila sobre la que se está ejecutando el método Paint, para realizar los cálculos de posición y dimensiones de la imagen a dibujar, en función de las propiedades de dicho objeto.

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)
{
    // llamar al método base
    base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue,
        errorText, cellStyle, advancedBorderStyle, paintParts);

    // pintar borde de la celda
    base.PaintBorder(graphics, clipBounds, cellBounds, cellStyle, advancedBorderStyle);

    // crear el rectángulo que contendrá el botón,
    // calcular su posición y dimensiones
    int nAjustePosicionBoton = (cellBounds.Width * 10) / 100;
    int nAjusteDimensionesBoton = (cellBounds.Width * 20) / 100;
    Rectangle rctBoton = new Rectangle(cellBounds.X + nAjustePosicionBoton, cellBounds.Y + nAjustePosicionBoton,
        cellBounds.Width - nAjusteDimensionesBoton, cellBounds.Height - nAjusteDimensionesBoton);

    // variables para calcular posición y tamaño de la imagen 
    // a dibujar dentro del botón
    int nAjustePosicionImagen = 0;
    int nAjusteDimensionesImagen = 0;

    // si se ha ejecutado el método OnMouseEnter u OnMouseLeave,
    // la colección lstOperacion contendrá elementos
    if (lstOperacion.Count > 0)
    {
        // obtener de la colección lstOperacion
        // la información relacionada con el método OnMouseXXX
        // para dibujar el botón
        Operacion oOperacion = lstOperacion.Find(oOp => oOp.Fila == rowIndex);

        if (oOperacion != null)
        {
            nAjustePosicionImagen = (cellBounds.Width * oOperacion.AjustePosicionImagen) / 100;
            nAjusteDimensionesImagen = (cellBounds.Width * oOperacion.AjusteDimensionesImagen) / 100;
            xEstadoBoton = oOperacion.EstadoBoton;
        }

        // eliminar el objeto Operacion obtenido de la colección
        lstOperacion.Remove(oOperacion);
    }
    else
    {
        // calcular posición y tamaño de la imagen a dibujar dentro del botón para el resto de métodos
        nAjustePosicionImagen = (cellBounds.Width * 12) / 100;
        nAjusteDimensionesImagen = (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);

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

La siguiente imagen muestra un momento de la ejecución, donde podemos apreciar que una de las celdas se ha dibujado de modo diferente, debido a que hemos situado el cursor sobre la misma.

 

Y terminamos aquí la segunda entrega de este artículo, en la tercera parte, que concluye esta serie, abordaremos la navegación y pulsación mediante el teclado de las celdas de nuestra columna. En los enlaces C# y VB el lector tiene disponible el proyecto con el código fuente completo del ejemplo.

Un saludo.

2 Comentarios

  1. anonymous

    Después de explicar el modo de dibujo y estados de presentación visual de los botones en

  2. anonymous

    GRACIAS COMPA ME SIRVE

Leave a Reply

Tema creado por Anders Norén