viernes, 24 de abril de 2015

Windows Forms. DataGridView. Columna con editor de fechas.

El DataGridView es un control con funcionalidad básica que permite una gran extensibilidad. En este artículo mostraré como crear una columna para datos de tipo fecha que utilice un DateTimePicker como editor.


Para crear cualquier columna personalizada en un DataGridView básicamente deberemos trabajar con 3 clases.

Una clase que implemente la interfaz IDataGridViewEditingControl que define el control que utilizaremos para editar los datos.

Una clase que herede de DataGridViewCell que define la plantilla de las celdas de la columna y que se encargará de inicializar el control de edición y de obtener, devolver y formatear los valores.

Una clase que herede de DataGridViewColumn que representará la columna y utilizará la clase de celda como plantilla.

Creando el proyecto de ejemplo

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

Crear proyecto de Biblioteca de Clases

Al proyecto le agregaré una referencia al ensamblado System.Windows.Forms y System.Drawing.

Referencia a System.Windows.Forms

Al proyecto le voy a añadir un único archivo DateTimeGridColumn.cs/DateTimeGridColumn.vb en la que incluiré las tres clases necesarias para definir la columna.

Creando el editor de fechas


Para generar el control de edición he creado una clase DateTimeEditingControl que hereda de la clase DateTimePicker (control que utilizaré para editar los valores de las celdas) y que implementa la interfaz IDataGridViewEditingControl.

    class DateTimeEditingControl : DateTimePicker, IDataGridViewEditingControl
    {

        private DataGridView grid;
        private bool valueChanged;

        public DateTimeEditingControl()
        {
            this.Format = DateTimePickerFormat.Custom;
        }

        protected override void OnValueChanged(EventArgs eventargs)
        {
            base.OnValueChanged(eventargs);
            SendToGridValueChanged();
        }

        private void SendToGridValueChanged()
        {
            valueChanged = true;
            if (grid != null)
                grid.NotifyCurrentCellDirty(true);
        }

        #region Miembros de IDataGridViewEditingControl

        public void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCellStyle)
        {
            this.Font = dataGridViewCellStyle.Font;
            this.CalendarForeColor = dataGridViewCellStyle.ForeColor;
            this.CalendarMonthBackground = dataGridViewCellStyle.BackColor;
        }

        public DataGridView EditingControlDataGridView
        {
            get { return grid; }
            set { grid = value; }
        }

        public object EditingControlFormattedValue
        {
            get
            {
                return this.Value.ToString(this.CustomFormat);
            }
            set
            {
                try { this.Value = DateTime.Parse((string)value); }
                catch { this.Value = DateTime.Now; }
                SendToGridValueChanged();
            }
        }

        public int EditingControlRowIndex { get; set; }

        public bool EditingControlValueChanged
        {
            get { return valueChanged; }
            set { valueChanged = value; }
        }

        public bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey)
        {
            switch (keyData & Keys.KeyCode)
            {
                case Keys.Left:
                case Keys.Up:
                case Keys.Down:
                case Keys.Right:
                case Keys.Home:
                case Keys.End:
                case Keys.PageDown:
                case Keys.PageUp:
                    return true;
                default:
                    return !dataGridViewWantsInputKey;
            }
        }

        public Cursor EditingPanelCursor
        {
            get { return base.Cursor; }
        }

        public object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context)
        {
            return EditingControlFormattedValue;
        }

        public void PrepareEditingControlForEdit(bool selectAll)
        {
        }

        public bool RepositionEditingControlOnValueChange
        {
            get { return false; }
        }

        #endregion

    }



Esta clase contiene la mayor parte del código del ejemplo ya que implementa la lógica de edición. Así que, como diría Jack el Destripador: vamos por partes.

En el constructor establecemos la propiedad Format del DateTimePicker a DateTimePickerFormat.Custom ya que estableceremos el formato de la fecha a partir del formato definido en la columna.

Sobrescribimos el método OnValueChanged para notificar al DataGridView que se ha modificado el contenido de la celda a través del método NotifyCurrentCellDirty.

 A continuación implementamos la interfaz IDataGridViewEditingControl.

En el método ApplyCellStyleToEditingControl trasladamos el estilo definido para la celda a las propiedades del DateTimePicker.

En la propiedad EditingControlFormattedValue se realiza la conversión entre el valor tipo DateTime de la celda y el string a visualizar.

En el método EditingControlWantsInputKey se establecen las teclas que necesita gestionar el control DateTimePicker durante la edición del valor.

Definiendo el tipo de celda


A continuación he definido la clase DateTimeCell que hereda de DataGridViewTextBoxCell para utilizar como plantilla de celda en la columna:

    public class DateTimeCell: DataGridViewTextBoxCell
    {

        public DateTimeCell() : base() { }

        public override Type EditType {
            get { return typeof(DateTimeEditingControl); } 
        }

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

        public override object DefaultNewRowValue
        {
            get
            {
                object defaultValue = base.DefaultNewRowValue;
                if (defaultValue is DateTime)
                    return defaultValue;
                else
                    return DateTime.Now;
            }
        }

        public override void InitializeEditingControl(int rowIndex, object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
        {
            base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);
            DateTimeEditingControl ctl = DataGridView.EditingControl as DateTimeEditingControl;
            try
            {
                if (this.Value == null)
                    ctl.Value = (DateTime)this.DefaultNewRowValue;
                else
                    ctl.Value = (DateTime)this.Value;

            }
            catch (Exception)
            {
                ctl.Value = (DateTime)this.DefaultNewRowValue;
            }

            if (dataGridViewCellStyle.Format.Length == 1)
            {
                string[] patterns = DateTimeFormatInfo.CurrentInfo.GetAllDateTimePatterns(dataGridViewCellStyle.Format.ToCharArray()[0]);
                if (patterns.Length > 0)
                    ctl.CustomFormat = patterns[0].ToString();
                else
                    ctl.CustomFormat = dataGridViewCellStyle.Format;
            }
            else
                ctl.CustomFormat = dataGridViewCellStyle.Format;
        }
    }

Las propiedades EditType y ValueType definen el tipo del control a utilizar para la edición de la celda y el tipo del valor de la celda respectivamente.

He sobrescrito el método InitializeEditingControl para establecer el valor y el formato del control DateTimePicker.

Definiendo el tipo de columna


Finalmente defino la clase DateTimeGridColumn que hereda de DataGridViewColumn y que define el tipo de columna que asignaremos a las columnas de tipo fecha del DataGridView.

    public class DateTimeGridColumn: DataGridViewColumn
    {

        public DateTimeGridColumn() : base(new DateTimeCell()) { }

        public override DataGridViewCell CellTemplate
        {
            get
            {
                return base.CellTemplate;
            }
            set
            {
                if (value != null &&
                        !value.GetType().IsAssignableFrom(typeof(DateTimeCell)))
                    throw new InvalidCastException("Debe especificar una instancia de DateTimeCell");
                base.CellTemplate = value;
            }
        }
    }

En el constructor de la columna llamo al constructor de la clase base DataGridViewColumn pasándole una nueva instancia de DateTimeCell para usar como plantilla de la celda.

Por otra lado simplemente he sobrescrito la propiedad CellTemplate para asegurarme de que la plantilla de celda asignada a la columna sea del tipo DateTimeCell.

Probando la columna de fechas

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 Fecha del tipo DateTimeGridColumn.

Crear columna DateTimeGridColumn

Para seleccionar el formato de fecha a utilizar en la columna he editado la columna Fecha y, en la ventana de edición de la propiedad DefaultCellStyle he establecido el valor de Format a g para establecer el formato de fecha general: fecha corta y hora corta.

Establecer formato de fecha de la columna


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

9 comentarios:

  1. Muchas gracias por tu aporte, me ha servido bastante :)

    ResponderEliminar
  2. hola que tal he estado viendo tus articulos y la verdad es que son muy buenos solo queria preguntarte si se le pude poner como un simbolo predeterminado y cualquier dato que se ponga salgacon ese simolo
    agradeceria tu ayuda

    ResponderEliminar
  3. Buenas noches.
    Gracias Ingeniero , acabo de acomplar las librerias de classes a mi datagridview de mi programa
    y esta funcionando de manera espectacular.
    me despliega el calendario de fechas de forma automatica y selecciono la fecha que deseo sin ningun problema .

    gracias y quedamos a la orden
    Ing. Silvestre Govea Rdz. (Sistemas Monterrey NL)

    ResponderEliminar
  4. Excelente aporte muchas gracias por su compañerismo.
    Espero seguir aprendiendo de usted.

    Jorge Aguilar

    ResponderEliminar
  5. Buenas esta bien tu clase para la fecha en la grilla, pero una consulta como hago cuando seleccione una fecha ejecute un evento, para completar se le agradecería
    Saludos,

    ResponderEliminar
  6. Error 2 'GridExtension2.DateTimeEditingControl' is not accessible in this context because it is 'Friend'. C:\Users\usuario\Desktop\Sistemas\EVSAMOD\EVSAMOD\SUBS\frmcargaextra.Designer.vb 207 23 EVSAMOD como lo resuelvo?

    ResponderEliminar
  7. Respuestas
    1. Basta con que elabores las tres clases y las guardes dentro de tu proyecto.
      Una vez guardadas agregas el datagridview y al agregar una columna de manera automatica encontraras el tipo CalendarColum.

      Saludos!

      Eliminar
  8. Excelente aportación me sirvió mucho.

    Saludos!

    ResponderEliminar