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.
Al proyecto le agregaré una referencia al ensamblado System.Windows.Forms y System.Drawing.
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
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 }
Class DateTimeEditingControl Inherits DateTimePicker Implements IDataGridViewEditingControl Private grid As DataGridView Private valChanged As Boolean Public Sub New() Me.Format = DateTimePickerFormat.Custom End Sub Protected Overrides Sub OnValueChanged(eventargs As EventArgs) MyBase.OnValueChanged(eventargs) SendToGridValueChanged() End Sub Private Sub SendToGridValueChanged() valChanged = True If grid IsNot Nothing Then grid.NotifyCurrentCellDirty(True) End If End Sub #Region "Miembros de IDataGridViewEditingControl" Public Sub ApplyCellStyleToEditingControl(dataGridViewCellStyle As DataGridViewCellStyle) _ Implements IDataGridViewEditingControl.ApplyCellStyleToEditingControl Me.Font = dataGridViewCellStyle.Font Me.CalendarForeColor = dataGridViewCellStyle.ForeColor Me.CalendarMonthBackground = dataGridViewCellStyle.BackColor End Sub Public Property EditingControlDataGridView As DataGridView _ Implements IDataGridViewEditingControl.EditingControlDataGridView Get Return grid End Get Set(value As DataGridView) grid = value End Set End Property Public Property EditingControlFormattedValue As Object _ Implements IDataGridViewEditingControl.EditingControlFormattedValue Get Return Me.Value.ToString(Me.CustomFormat) End Get Set(value As Object) Try Me.Value = DateTime.Parse(CType(value, String)) Catch ex As Exception Me.Value = DateTime.Now End Try SendToGridValueChanged() End Set End Property Public Property EditingControlRowIndex As Integer _ Implements IDataGridViewEditingControl.EditingControlRowIndex Public Property EditingControlValueChanged As Boolean _ Implements IDataGridViewEditingControl.EditingControlValueChanged Get Return valChanged End Get Set(value As Boolean) valChanged = value End Set End Property Public Function EditingControlWantsInputKey(keyData As Keys, dataGridViewWantsInputKey As Boolean) As Boolean _ Implements IDataGridViewEditingControl.EditingControlWantsInputKey Select Case keyData And Keys.KeyCode Case Keys.Left, Keys.Up, Keys.Down, Keys.Right, _ Keys.Home, Keys.End, Keys.PageDown, Keys.PageUp Return True Case Else Return Not dataGridViewWantsInputKey End Select End Function Public ReadOnly Property EditingPanelCursor As Cursor _ Implements IDataGridViewEditingControl.EditingPanelCursor Get Return MyBase.Cursor End Get End Property Public Function GetEditingControlFormattedValue(context As DataGridViewDataErrorContexts) As Object _ Implements IDataGridViewEditingControl.GetEditingControlFormattedValue Return EditingControlFormattedValue End Function Public Sub PrepareEditingControlForEdit(selectAll As Boolean) _ Implements IDataGridViewEditingControl.PrepareEditingControlForEdit End Sub Public ReadOnly Property RepositionEditingControlOnValueChange As Boolean _ Implements IDataGridViewEditingControl.RepositionEditingControlOnValueChange Get Return False End Get End Property #End Region End Class
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; } }
Public Class DateTimeCell Inherits DataGridViewTextBoxCell Public Sub New() MyBase.New() End Sub Public Overrides ReadOnly Property EditType As Type Get Return GetType(DateTimeEditingControl) End Get End Property Public Overrides ReadOnly Property ValueType As Type Get Return GetType(DateTime) End Get End Property Public Overrides ReadOnly Property DefaultNewRowValue As Object Get Dim defaultValue As Object = MyBase.DefaultNewRowValue If TypeOf defaultValue Is DateTime Then Return defaultValue Else Return DateTime.Now End If 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 DateTimeEditingControl = CType(DataGridView.EditingControl, DateTimeEditingControl) Try If Me.Value = Nothing Then ctl.Value = CType(Me.DefaultNewRowValue, DateTime) Else ctl.Value = CType(Me.Value, DateTime) End If Catch ex As Exception ctl.Value = CType(Me.DefaultNewRowValue, DateTime) End Try If dataGridViewCellStyle.Format.Length = 1 Then Dim patterns As String() = DateTimeFormatInfo.CurrentInfo.GetAllDateTimePatterns( _ dataGridViewCellStyle.Format.ToCharArray()(0)) If patterns.Length > 0 Then ctl.CustomFormat = patterns(0).ToString() Else ctl.CustomFormat = dataGridViewCellStyle.Format End If Else ctl.CustomFormat = dataGridViewCellStyle.Format End If End Sub End Class
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; } } }
Public Class DateTimeGridColumn Inherits DataGridViewColumn Public Sub New() MyBase.New(New DateTimeCell()) 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(DateTimeCell)) Then Throw New InvalidCastException("Debe especificar una instancia de DateTimeCell") End If MyBase.CellTemplate = value End Set End Property End Class
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.
Al proyecto GridTest le he añadido una referencia al proyecto 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.
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.
Ahora ya podemos arrancar la aplicación para comprobar el funcionamiento del nuevo tipo de columna.
El código completo de este artículo, tanto en C# como en Visual Basic, está disponible en:
Códigos de muestra - Ejemplos MSDN
Códigos de muestra - Ejemplos MSDN
Muchas gracias por tu aporte, me ha servido bastante :)
ResponderEliminarhola 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
ResponderEliminaragradeceria tu ayuda
Buenas noches.
ResponderEliminarGracias 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)
Excelente aporte muchas gracias por su compañerismo.
ResponderEliminarEspero seguir aprendiendo de usted.
Jorge Aguilar
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
ResponderEliminarSaludos,
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?
ResponderEliminary donde encuntro la referencia
ResponderEliminarBasta con que elabores las tres clases y las guardes dentro de tu proyecto.
EliminarUna vez guardadas agregas el datagridview y al agregar una columna de manera automatica encontraras el tipo CalendarColum.
Saludos!
Excelente aportación me sirvió mucho.
ResponderEliminarSaludos!