sábado, 18 de abril de 2015

Windows Forms. DataGridView. Columna que acepta sólo números.

En este artículo voy a mostrar cómo crear un nuevo tipo de columna para el DataGridView para almacenar valores numéricos enteros, de forma que las celdas únicamente admitan la introducción de caracteres numéricos.



Creando la Clase IntegerGridColumn


Voy a crear un nuevo proyecto GridExtension del tipo Biblioteca de Clases.

Creando el proyecto Biblioteca de Clases

Al proyecto le agregaré una referencia al ensamblado System.Windows.Forms de forma que tenga acceso a las clases del control DataGridView.

Agregar referencia System.WindowsForms

Dentro del proyecto voy a crear un nuevo archivo IntegerGridColumn.cs/IntegerGridColumn.vb en el que voy a definir dos clases. La clase IntegerGridColumn que hereda de DataGridViewColumn y la clase IntegerGridCell que hereda de DataGridViewTextBoxCell.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace GridExtension
{
    public class IntegerGridColumn : DataGridViewColumn
    {
    }

    public class IntegerGridCell : DataGridViewTextBoxCell
    {
    }
}
Imports System.Windows.Forms

Public Class IntegerGridColumn
    Inherits DataGridViewColumn

End Class

Public Class IntegerGridCell
    Inherits DataGridViewTextBoxCell

End Class

En la clase IntegerGridCell voy a sobreescribir la propiedad ValueType para especificar que el tipo de los valores de la celda es Int32, y el método InitializeEditingControl para añadir un controlador de evento al evento KeyPress del control de edición de la celda.

El controlador de este evento (el método IntegerGridCell_KeyPress) va a ser el responsable de controlar que los caracteres se correspondan con caracteres numéricos. En caso contrario establece la propiedad Handled del objeto KeyPressEventArgs a true para anular el tratamiento de la tecla pulsada.

    public class IntegerGridCell : DataGridViewTextBoxCell
    {

        public IntegerGridCell() : base() { }

        public override Type ValueType
        {
            get { return typeof(Int32); }
        }

        public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
        {
            base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
            Control ctl = DataGridView.EditingControl;
            ctl.KeyPress += new KeyPressEventHandler(IntegerGridCell_KeyPress);
        }

        private void IntegerGridCell_KeyPress(object sender, KeyPressEventArgs e)
        {
            DataGridViewCell currentCell = ((IDataGridViewEditingControl)sender).EditingControlDataGridView.CurrentCell;
            if (currentCell is IntegerGridCell)
                e.Handled = !char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar);
        }
    }
Public Class IntegerGridCell
    Inherits DataGridViewTextBoxCell

    Public Sub New()
        MyBase.New()
    End Sub

    Public Overrides ReadOnly Property ValueType As Type
        Get
            Return GetType(Int32)
        End Get
    End Property

    Public Overrides Sub InitializeEditingControl(rowIndex As Integer, initialFormattedValue As Object, dataGridViewCellStyle As DataGridViewCellStyle)
        MyBase.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle)
        Dim ctl As Control = DataGridView.EditingControl
        AddHandler ctl.KeyPress, AddressOf IntegerGridCell_KeyPress
    End Sub

    Private Sub IntegerGridCell_KeyPress(sender As Object, e As KeyPressEventArgs)
                Dim currentCell As DataGridViewCell = CType(sender, IDataGridViewEditingControl).EditingControlDataGridView.CurrentCell
        If TypeOf currentCell Is IntegerGridCell Then
            e.Handled = Not Char.IsControl(e.KeyChar) AndAlso Not Char.IsDigit(e.KeyChar)
        End If
    End Sub

End Class



En el constructor de la clase IntegerGridColumn voy a llamar al constructor de DataGridViewColumn pasándole una instancia de IntegerGridCell como plantilla de celda. Voy a sobreescribir también la propiedad CellTemplate para comprobar que la celda que se establece como plantilla es del tipo IntegerGridCell.

    public class IntegerGridColumn : DataGridViewColumn
    {
        public IntegerGridColumn() : base(new IntegerGridCell()) { }

        public override DataGridViewCell CellTemplate
        {
            get
            {
                return base.CellTemplate;
            }
            set
            {
                if (value != null &&
                    !value.GetType().IsAssignableFrom(typeof(IntegerGridCell)))
                    throw new InvalidCastException("Debe ser del tipo IntegerGridCell");
                base.CellTemplate = value;
            }
        }
    }
Public Class IntegerGridColumn
    Inherits DataGridViewColumn

    Public Sub New()
        MyBase.New(New IntegerGridCell())
    End Sub

    Public Overrides Property CellTemplate As DataGridViewCell
        Get
            Return MyBase.CellTemplate
        End Get
        Set(value As DataGridViewCell)
            If value IsNot Nothing AndAlso _
                Not value.GetType().IsAssignableFrom(GetType(IntegerGridCell)) Then
                Throw New InvalidCastException("Debe ser del tipo IntegerGridCell")
            End If
            MyBase.CellTemplate = value
        End Set
    End Property

End Class

Con esto ya estaría creado el nuevo tipo de columna. Para probarlo voy a añadir a la solución un nuevo proyecto GridTest del tipo Aplicación de Windows Forms.

Crear Proyecto de prueba

Al proyecto GridTest le he añadido una referencia al proyecto GridExtension.

Referencia a GridExtension

En el formulario que Visual Studio crea por defecto le he añadido un control DataGridView al que le he agregado dos columnas: una columna Texto del tipo DataGridViewTextBoxColumn y una columna Entero del tipo IntegerGridColumn.

Crear columna IntegerGridColumn

Ahora ya podemos arrancar la aplicación para comprobar el funcionamiento del nuevo tipo de columna.

Formulario de prueba


El código completo de este artículo, tanto en C# como en Visual Basic, está disponible en:

Códigos de muestra - Ejemplos MSDN

11 comentarios:

  1. Esto esta muy bueno, y si se pudiera hacer que la entrada de datos sea decimal, es decir como en un facturador que se entra el número 15000 y automáticamente acomoda el número a 15.000,00 ?

    ResponderEliminar
    Respuestas
    1. Hola Rodolfo,

      Para aceptar y formatear números decimales lo que haría es añadir un control de edición (seguramente con un MaskedTextBox) al estilo del ejemplo que tengo en el blog para crear columnas para editar fechas:
      DataGridView. Columna con editor de fechas

      A ver si saco un rato y monto un ejemplo.

      Un saludo

      Eliminar
    2. He tardado un poco pero ya hay disponible un ejemplo de columna numérica que permite especificar el número de dígitos decimales:
      Columna para números con decimales

      Espero que sea de utilidad.

      Eliminar
  2. Gracias Asier, Voy a estar esperando tu ejemplo. Ojala sea con Net 2

    ResponderEliminar
  3. Hola Asier:
    Lo primero es darte las gracias por los ejemplos que tienes, ya que son realmente muy buenos.
    También estaría interesado en ver un ejemplo donde una columna del datagridview solamente aceptase números con dos decimales.

    Un Saludo.

    ResponderEliminar
    Respuestas
    1. No eres el primero que me lo pide. A ver si este fin de semana puedo preparar un ejemplo.

      Eliminar
    2. He tardado un poco pero ya hay disponible un ejemplo de columna numérica que permite especificar el número de dígitos decimales:
      Columna para números con decimales

      Espero que sea de utilidad.

      Eliminar
  4. Es muy bueno tu ejemplo, yo tengo un problema, lo que necesito que aparesca un boton en la celda activa, para hacer una busqueda!

    ResponderEliminar
  5. Hoja Jorge.

    Últimamente ando con poco tiempo para preparar ejemplos, pero me lo apunto. A ver si puedo sacar un rato un día de estos.

    Para hacerlo puedes fijarte en otros dos artículos del blog:
    En Control TextBox con Botón muestro como crear un control TextBox que tenga un botón incorporado.
    Una vez que tienes el control se trataría de crear un DataGridViewColumn que utilice como editor este control. Algo parecido a lo que muestro en el artículo DataGridView. Columna con editor de fechas, en el que creo una columna que utiliza el control DateTimePicker como editor.

    Espero que te ayude.

    ResponderEliminar
  6. Hola Doc, muy bueno blog, tengo una consulta después de crear la clase y agregarlo en la referencia no me sale el tipo de dato IntegerGridCell en la lista de tipos de datos al agregar o modificar un campo del DataGridView, yo estoy usando el VisualStudio Comunity 2015 crees que haya algún problema con esto? ojo que todo ha terminado ok con la clase, gracias

    ResponderEliminar
    Respuestas
    1. Lo que te debería aparecer es el tipo IntegerGridColumn entre los tipos de columna a seleccionar cuando añades una nueva columna en el DataGridView. Si no te aparece prueba a recompilar la solución o a cerrar y volver a abrir el formulario para que se refresquen las referencias.

      Eliminar