It doesn't work out of the box but it's not all that difficult to subclass System.Windows.Forms.DataGridViewColumn
to host any control of your choosing. On top of that, the System.Windows.Forms
namespace already contains a DateTimePicker
class but at this point it's not suited to use inside a DataGridView
. Let's glue these two together.
We'll start by creating a DataGridViewCalendarColumn
class:
public class DataGridViewCalendarColumn : DataGridViewColumn
{
public DataGridViewCalendarColumn() : base(new DataGridViewCalendarCell())
{
}
public override DataGridViewCell CellTemplate
{
get
{
return base.CellTemplate;
}
set
{
// Ensure that the cell used for the template is a CalendarCell.
if (value != null &&
!value.GetType().IsAssignableFrom(typeof(DataGridViewCalendarCell)))
{
throw new InvalidCastException("Must be a DataGridViewCalendarCell");
}
base.CellTemplate = value;
}
}
}
So what does this class do? It basically says ok, a DataGridViewColumn
expects a CellTemplate
for its cells. This is a constraint that makes sure that any column we add to a DataGridView
adheres to a certain standard.
In essence we're sort of tricking DataGridView
into accepting a new kind of column containing a new kind of cell. But at this point, DataGridViewCalendarCell
doesn't exist yet, so we'll continue by creating that as well:
We'll start off by subsclassing DataGridViewTextBoxCell
, since we have to start with a kind of cell that DataGridView
does recognize. In this case DataGridViewTextBoxCell
.
public class DataGridViewCalendarCell : DataGridViewTextBoxCell
{
public DataGridViewCalendarCell()
: base()
{
// Use the short date format.
this.Style.Format = "d";
}
public override void InitializeEditingControl(int rowIndex, object
initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
// Set the value of the editing control to the current cell value.
base.InitializeEditingControl(rowIndex, initialFormattedValue,
dataGridViewCellStyle);
DataGridViewCalendarEditingControl ctl =
DataGridView.EditingControl as DataGridViewCalendarEditingControl;
// Use the default row value when Value property is null.
if (this.Value != null || !String.IsNullOrEmpty(initialFormattedValue.ToString()))
{
DateTime parsedDate;
bool IsDate = DateTime.TryParseExact(this.Value.ToString(), "d/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDate);
if (IsDate)
{
ctl.Value = parsedDate;
}
}
}
public override Type EditType
{
get
{
// Return the type of the editing control that DataGridViewCalendarCell uses.
return typeof(DataGridViewCalendarEditingControl);
}
}
public override Type ValueType
{
get
{
// Return the type of the value that DataGridViewCalendarCell contains.
return typeof(DateTime);
}
}
public override object DefaultNewRowValue
{
get
{
// Use the current date and time as the default value.
return null;
}
}
}
So what this basically does is say to our DataGridView
: look, I know and you know that you're already accustomed to accepting DataGridViewTextBoxCell
, because that's how we create grids that can both display and edit text. So I'm going to throw a kind of text cell at you.
Furthermore, we'll constrain the input value to a text that is parseable as a DateTime
.
This creates a reference frame so our grid won't act surprised if we try to create an actual calendar control when we click the cell. So now comes the real magic: the actual calendar control!
We'll subclass the regular DateTimePicker
control but also make it implement the IDataGridViewEditingControl
interface, a contract that binds any control that wishes to edit content within a DataGridView
to a specific set of rules.
public class DataGridViewCalendarEditingControl : DateTimePicker, IDataGridViewEditingControl
{
DataGridView dataGridView;
private bool valueChanged = false;
int rowIndex;
public DataGridViewCalendarEditingControl()
{
this.Format = DateTimePickerFormat.Short;
}
// Implements the IDataGridViewEditingControl.EditingControlFormattedValue
// property.
public object EditingControlFormattedValue
{
get
{
return this.Value.ToShortDateString();
}
set
{
if (value is String)
{
try
{
// This will throw an exception if the string is
// null, empty, or not in the format of a date.
DateTime parsedDate;
bool IsDate = DateTime.TryParseExact((String)value, "dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDate);
this.Value = (IsDate) ? parsedDate : DateTime.Now;
}
catch
{
// In the case of an exception, just use the
// default value so we're not left with a null
// value.
this.Value = DateTime.Now;
}
}
}
}
// Implements the
// IDataGridViewEditingControl.GetEditingControlFormattedValue method.
public object GetEditingControlFormattedValue(
DataGridViewDataErrorContexts context)
{
return EditingControlFormattedValue;
}
// Implements the
// IDataGridViewEditingControl.ApplyCellStyleToEditingControl method.
public void ApplyCellStyleToEditingControl(
DataGridViewCellStyle dataGridViewCellStyle)
{
this.Font = dataGridViewCellStyle.Font;
this.CalendarForeColor = dataGridViewCellStyle.ForeColor;
this.CalendarMonthBackground = dataGridViewCellStyle.BackColor;
}
// Implements the IDataGridViewEditingControl.EditingControlRowIndex
// property.
public int EditingControlRowIndex
{
get
{
return rowIndex;
}
set
{
rowIndex = value;
}
}
// Implements the IDataGridViewEditingControl.EditingControlWantsInputKey
// method.
public bool EditingControlWantsInputKey(
Keys key, bool dataGridViewWantsInputKey)
{
// Let the DateTimePicker handle the keys listed.
switch (key & 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;
}
}
// Implements the IDataGridViewEditingControl.PrepareEditingControlForEdit
// method.
public void PrepareEditingControlForEdit(bool selectAll)
{
// No preparation needs to be done.
}
// Implements the IDataGridViewEditingControl
// .RepositionEditingControlOnValueChange property.
public bool RepositionEditingControlOnValueChange
{
get
{
return false;
}
}
// Implements the IDataGridViewEditingControl
// .EditingControlDataGridView property.
public DataGridView EditingControlDataGridView
{
get
{
return dataGridView;
}
set
{
dataGridView = value;
}
}
// Implements the IDataGridViewEditingControl
// .EditingControlValueChanged property.
public bool EditingControlValueChanged
{
get
{
return valueChanged;
}
set
{
valueChanged = value;
}
}
// Implements the IDataGridViewEditingControl
// .EditingPanelCursor property.
public Cursor EditingPanelCursor
{
get
{
return base.Cursor;
}
}
protected override void OnValueChanged(EventArgs eventargs)
{
// Notify the DataGridView that the contents of the cell
// have changed.
valueChanged = true;
this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
base.OnValueChanged(eventargs);
}
}
This is the meat of the code. I realize that it seems a lot, but once you get the hang of this, you can create all kinds of DataGridView
columns, cells and controls that you'd ever need.
The data you pull in from your database (i.e. the columns that are DATETIME
on the db side) can be left in string format. The control we just created will parse these strings into dates. So fill your DataTable
, add your columns programmatically and bind. Assuming you fill your DataTable
with two columns, one of which contains dates:
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("dbTextColName", typeof(string)));
dt.Columns.Add(new DataColumn("dbDateTimeColName", typeof(string))); // yes string, our code above will take care of the parsing
// logic to fill your DataTable
from db goes here
var colText = new DataGridViewTextBoxColumn();
colText.DataPropertyName = "dbTextColName";
colText.HeaderText = "MyTextColumn";
colText.Name = "dbTextColName";
var colDateTime = new DataGridViewCalendarColumn();
colDateTime.DataPropertyName = "dbDateTimeColName";
colDateTime.HeaderText = "MyDateTimeColumn";
colDateTime.Name = "dbDateTimeColName";
yourDataGridView.Columns.AddRange(new DataGridViewColumn[] {colText, colDateTime});
yourDataGridView.DataSource = dt;