September 2009 - Artículos

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.

Publicado por Luis Miguel Blanco | 3 comment(s)
Archivado en:

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.

Publicado por Luis Miguel Blanco | 2 comment(s)
Archivado en:

La creación para el control DataGridView, de una columna cuyas celdas contengan botones, los cuales realicen una determinada acción al ser pulsados, es una tarea muy sencilla, tal y como ya explicamos en un artículo anterior de este blog, donde la clase DataGridViewButtonColumn nos proveía de los botones con toda la funcionalidad típica ya implementada.

Pero supongamos que nuestro DataGridView necesita una columna de botones con un mayor grado de personalización, donde por ejemplo, dibujemos una imagen dentro del botón, la cual cambie su tamaño cuando el cursor del ratón entre en su superficie.

Este comportamiento personalizado y otros muchos pueden ser logrados partiendo de cero si heredamos de las clases DataGridViewColumn y DataGridViewCell, como veremos a lo largo de las diferentes entregas que componen este artículo.

 

El proyecto base del ejemplo

En esta primera parte comenzaremos creando, desde Visual Studio 2008, un nuevo proyecto de tipo Windows Forms Application con el nombre DGVColumnaCeldasBotonPersonalizado, agregando al diseñador del formulario un DataGridView y varios controles adicionales, hasta conseguir el aspecto de la siguiente imagen.

 

En el manipulador del evento Load de este formulario añadiremos el siguiente bloque de código, que rellenará el DataGridView con un conjunto de registros de la tabla DimProduct, perteneciente a la base de datos AdventureWorksDW.

//....
using System.Data.SqlClient;
//....
private void Form1_Load(object sender, EventArgs e)
{
    SqlConnection cnConexion = new SqlConnection();
    cnConexion.ConnectionString = "Data Source=localhost;" +
        "Initial Catalog=AdventureWorksDW;" +
        "Integrated Security=True";

    string sSQL = "SELECT ProductKey, SpanishProductName, ListPrice " +
        "FROM DimProduct WHERE ListPrice IS NOT NULL";

    SqlCommand cmdComando = new SqlCommand(sSQL, cnConexion);
    SqlDataAdapter daAdaptador = new SqlDataAdapter(cmdComando);
    DataSet dsDatos = new DataSet();
    daAdaptador.Fill(dsDatos, "DimProduct");

    this.dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
    this.dataGridView1.DataSource = dsDatos;
    this.dataGridView1.DataMember = "DimProduct";
}

De esta forma, el control presentará el siguiente aspecto en tiempo de ejecución. 

 

 

Comenzando la creación de la columna personalizada

A continuación añadiremos al proyecto dos nuevas clases: la primera, DGVColumnaBoton, heredará de DataGridViewColumn, y constituirá la base para las celdas que conformarán su contenido; la segunda, DGVCeldaBoton, heredará de DataGridViewCell, y en ella crearemos los botones que constituirán el contenido de la columna, así como la implementación de la lógica para su comportamiento visual.

using System.Windows.Forms;
using System.Drawing;
using System.Windows.Forms.VisualStyles;
//....
//----------------------------------------
class DGVColumnaBoton : DataGridViewColumn
{
    // inicializar una nueva instancia de esta clase
    // pasando como parámetro una plantilla de celda, es decir, 
    // una instancia de la clase DataGridViewCell o derivada
    public DGVColumnaBoton()
        : base(new DGVCeldaBoton())
    {
    }
    //....
}

//------------------------------------
class DGVCeldaBoton : DataGridViewCell
{
    //....
}

 

DGVColumnaBoton. La clase para la construcción de columnas

Comenzando por la clase DGVColumnaBoton, el código que tenemos que escribir para la misma es muy sencillo, ya que consistirá en dos propiedades, que contendrán la información de la ruta en la que está el archivo de imagen para el botón, y una instancia de la propia imagen.

class DGVColumnaBoton : DataGridViewColumn
{
    private string sImagen;
    private Bitmap bmpImagen;

    // inicializar una nueva instancia de esta clase
    // pasando como parámetro una plantilla de celda, es decir, 
    // una instancia de la clase DataGridViewCell o derivada
    public DGVColumnaBoton()
        : base(new DGVCeldaBoton())
    {
    }

    public string Imagen
    {
        get
        {
            return sImagen;
        }

        set
        {
            sImagen = value;
            bmpImagen = new Bitmap(value);
            this.Width = bmpImagen.Size.Width;
        }
    }

    internal Bitmap ImagenBitmap
    {
        get
        {
            return bmpImagen;
        }
    }
}

 

DGVCeldaBoton. La clase para la construcción de celdas

La clase DGVCeldaBoton sin embargo va a darnos más trabajo, lo cual resulta totalmente comprensible, puesto que, como ya hemos apuntado anteriormente, será donde tendremos que codificar todo el comportamiento visual que queramos proporcionar a los botones alojados en las celdas. Empezaremos reemplazando la propiedad FormattedValueType, que se emplea para establecer el tipo de los datos formateados que se visualizan en la celda. En nuestro caso devolveremos un tipo object.

class DGVCeldaBoton : DataGridViewCell
{
    public override Type FormattedValueType
    {
        get
        {
            return typeof(object);
        }
    }
    //....
}

En este momento ya podríamos crear una instancia de nuestra nueva clase personalizada de columna, y añadirla al DataGridView del formulario de ejemplo. Observe el lector, cómo establecemos el tamaño de la columna personalizada mediante la asignación de las propiedades DataGridView.RowTemplate.Height y DGVColumnaBoton.Width.

private void Form1_Load(object sender, EventArgs e)
{
    //....
    this.dataGridView1.RowTemplate.Height = 70;

    this.dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
    this.dataGridView1.DataSource = dsDatos;
    this.dataGridView1.DataMember = "DimProduct";


    DGVColumnaBoton colBotones = new DGVColumnaBoton();
    colBotones.Name = "colBotones";
    colBotones.HeaderText = "Descuento";
    colBotones.Imagen = System.Environment.CurrentDirectory + @"\BotonIr.jpg";
    colBotones.Width = 70;

    this.dataGridView1.Columns.Add(colBotones);
}

No obstante, el resultado obtenido no será el esperado, ya que como vemos en la siguiente imagen, el área del DataGridView donde debería aparecer nuestra nueva columna se encuentra vacía.

 

Concluye aquí la primera parte de este artículo, en donde hemos sentado las bases para la construcción de una columna personalizada para el control DataGrid. En la siguiente entrega explicaremos los pasos relacionados con el dibujo de las celdas contenidas en la columna, así como los diferentes estados visuales que pueden tomar los botones contenidos en las celdas al interaccionar el usuario. En los enlaces C# y VB el lector tiene disponible el proyecto con el código fuente completo del ejemplo.

Un saludo.

Publicado por Luis Miguel Blanco | 6 comment(s)
Archivado en:

Cuando presentamos un conjunto de datos en forma tabular mediante el control DataGridView, hay ocasiones en las que se necesita proporcionar al usuario la posibilidad de realizar ciertas operaciones, en base a uno o varios valores situados en las celdas de una determinada fila del control.

Por ejemplo, en el formulario que vemos en la siguiente imagen se muestra un DataGridView con algunos campos de la tabla DimProduct, perteneciente a la base de datos AdventureWorksDW. El objetivo consiste en que el usuario pueda introducir en el TextBox un valor, que representa un porcentaje de descuento a aplicar sobre el campo ListPrice, de la fila del DataGridView que seleccione.

 

Entre las diversas técnicas disponibles para realizar la operación que acabamos de mencionar, en este artículo vamos a hacer uso de las clases DataGridViewButtonColumn y DataGridViewButtonCell, que combinaremos para crear, dentro del control DataGridView, una nueva columna calculada, cuyas celdas contengan botones, que al ser pulsados, nos permitan realizar una operación con los valores de otras celdas de la fila seleccionada. Como particularidad añadida, los botones de nuestra nueva columna contendrán una imagen.

En primer lugar crearemos, en Visual Studio 2008, un nuevo proyecto de tipo Windows Forms Application, al que daremos el nombre DGVColumnaBotonesImagen, añadiendo al diseñador del formulario los controles mostrados en la anterior imagen.

Seguidamente pasaremos al editor de código del formulario, y en su evento Load escribiremos el siguiente bloque de código, que usaremos para conectarnos a una base de datos y cargar el DataGridView con un subconjunto de los registros de la tabla.

using System.Data.SqlClient;
//....
namespace DGVColumnaBotonesImagen
{
public partial class Form1 : Form
{
    //....

    private void Form1_Load(object sender, EventArgs e)
    {
        SqlConnection cnConexion = new SqlConnection();
        cnConexion.ConnectionString = "Data Source=localhost;" +
            "Initial Catalog=AdventureWorksDW;" +
            "Integrated Security=True";

        string sSQL = "SELECT ProductKey, SpanishProductName, ListPrice " +
            "FROM DimProduct WHERE ListPrice IS NOT NULL";

        SqlCommand cmdComando = new SqlCommand(sSQL, cnConexion);
        SqlDataAdapter daAdaptador = new SqlDataAdapter(cmdComando);
        DataTable tblDimProduct = new DataTable();
        daAdaptador.Fill(tblDimProduct);
        this.dataGridView1.DataSource = tblDimProduct;
        //....
    }
    //....

A continuación definiremos nuestra columna personalizada creando una instancia de la clase DataGridViewButtonColumn, que añadiremos a la colección de columnas del DataGridView.

private void Form1_Load(object sender, EventArgs e)
{
    //....
    DataGridViewButtonColumn colBotones = new DataGridViewButtonColumn();
    colBotones.Name = "colBotones";
    colBotones.HeaderText = "Valor Stock";

    this.dataGridView1.Columns.Add(colBotones);
}

La siguiente imagen muestra el resultado.

El anterior código crea una nueva columna basada en botones, pero estos carecen de contenido. Para incluir una imagen en su interior crearemos un manipulador para el evento CellPainting del control, en el que realizaremos las siguientes operaciones:

En primer lugar comprobaremos si la celda a pintar corresponde a nuestra columna de botones y se encuentra en una fila válida; en caso afirmativo, pintamos la celda ejecutando el método DataGridViewCellPaintingEventArgs.Paint. Seguidamente obtenemos una instancia del botón situado en la celda y otra de la imagen (un icono en este ejemplo) a situar en su interior, dibujando esta mediante el método Graphics.DrawIcon, con un tamaño ligeramente inferior al del botón.

Finalmente, para poder visualizar de forma más adecuada el botón, ajustamos las dimensiones de la fila y columna que lo alojan en función de las dimensiones de la imagen. También debemos asignar el valor true a la propiedad DataGridViewCellPaintingEventArgs.Handled, que nos servirá para que el control tenga en cuenta la implementación que hemos realizado sobre este evento.

private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    if (e.ColumnIndex >= 0 && this.dataGridView1.Columns[e.ColumnIndex].Name == "colBotones" && e.RowIndex >= 0)
    {
        e.Paint(e.CellBounds, DataGridViewPaintParts.All);

        DataGridViewButtonCell celBoton = this.dataGridView1.Rows[e.RowIndex].Cells["colBotones"] as DataGridViewButtonCell;
        Icon icoAtomico = new Icon(Environment.CurrentDirectory + @"\Atomico.ico");
        e.Graphics.DrawIcon(icoAtomico, e.CellBounds.Left + 3, e.CellBounds.Top + 3);

        this.dataGridView1.Rows[e.RowIndex].Height = icoAtomico.Height + 10;
        this.dataGridView1.Columns[e.ColumnIndex].Width = icoAtomico.Width + 10;

        e.Handled = true;
    }
}

En la siguiente imagen podemos ver el control con la columna resultante, incluyendo los botones con el icono.

Tan sólo resta añadir al botón la lógica que efectúe el cálculo del descuento comentado al comienzo del artículo, aspecto este que resolveremos codificando el evento CellClick del DataGridView. Al producirse este evento comprobaremos si existe valor en el TextBox del formulario, y en caso afirmativo, nos cercioraremos de que la celda sobre la que se ha realizado la pulsación corresponde a nuestra columna calculada, si esto se cumple, procederemos con el cálculo, mostrándolo en una caja de mensaje.

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");
        }
    }
}

La siguiente imagen muestra el resultado de una operación.

 

Utilizando el control DataGridView, en este artículo hemos explicado una técnica, que de manera sencilla nos permite crear una columna conteniendo botones que muestran un icono en su interior, lo cual nos puede servir para dar una mejor apariencia a nuestros controles de cuadrícula. En los siguientes enlaces:  C# y VB, disponemos del proyecto de ejemplo. Espero que os resulte de utilidad.

Un saludo. 

Publicado por Luis Miguel Blanco | 5 comment(s)
Archivado en: