A while back, I managed to fix a couple bugs with the sample DataGridView CalendarColumn control that made it much more usable. Today, I came across one more issue. It's pretty well known that the DateTimePicker, despite having support for a checkbox that lets you turn a date on or off, does not directly support "null" as a valid value. There are a bunch of ways to get around this, but what I came across was a need to support this inside of a DataGridView.
I started with the task of making the CalendarColumn configurable in such a way as to be able to turn the checkbox on or off at will (well, at least, at the moment of construction). That part's easy; I added a new constructor to CalendarColumn to take an "isNullable" flag:
public CalendarColumn() : base(new CalendarCell()) { }
public CalendarColumn(bool isNullable) : base(new CalendarCell(bool isNullable)) { }
Then, I added a class-level variable to my CalendarCell class, and initialized it in the constructor:
public class CalendarCell {
private bool isNullable = false;
public CalendarCell() : base() {
this.Style.Format = "d";
}
public CalendarCell(bool isNullable) : this() {
this.isNullable = isNullable;
}
[...]
The next modification comes in InitializeEditingControl. Immediately after getting the reference to CalendarEditingControl ctl is set:
ctl.ShowCheckBox = this.isNullable;
if (this.Value != null && this.Value != DBNull.Value) {
if (this.Value is DateTime) {
ctl.Value = (DateTime)this.Value;
} else {
DateTime dtVal;
if (DateTime.TryParse(Convert.ToString(this.Value), out dtVal)) ctl.Value = dtVal;
}
if (this.isNullable) ctl.Checked = true;
} else if (this.isNullable) {
ctl.Checked = false;
}
(Note that I added a little checking around the value setting area, because I'm paranoid like that.)
Next, DefaultNewRowValue:
public override object DefaultNewRowValue { get { if (this.isNullable) return null; else return DateTime.Now; } }
Here's what I found out you don't change. The ValueType property always returns typeof(DateTime). If you change this to typeof(DateTime?), what ends up happening is, if you bind to a DataTable, it attempts to insert an actual null into the table. Because (for reasons I have yet to believe necessary) null and DBNull are two completely separate and incompatible things, this fails. Apparently, by leaving the value type as non-nullable, a null value will get translated appropriately in the binding.
We're not quite done yet. In the CalendarEditingControl class, the EditingControlFormattedValue property needs to be updated:
public object EditingControlFormattedValue {
get {
if (this.ShowCheckBox && !this.Checked) {
return String.Empty;
} else {
if (this.Format == DateTimePickerFormat.Custom) {
return this.Value.ToString();
} else {
return this.Value.ToShortDateString();
}
}
}
set {
string newValue = value as string;
if (!String.IsNullOrEmpty(newValue)) {
this.Value = DateTime.Parse(newValue);
} else if (this.ShowCheckBox) {
this.Checked = false;
}
}
}
And presto, a checkable, nullable DateTimePicker column that works in a DataGridView bound to a DataTable.
3 comments:
Yakko,
Thanks for the code - it helped TREMENDOUSLY! I'm using the grid non-bound.
I made a minor change to mine:
public class CalendarColumnNullable : DataGridViewColumn
{
public CalendarColumnNullable()
: base(new CalendarCellNullable())
...
}
public class CalendarCellNullable : CalendarCell
{
public CalendarCellNullable()
{
this.isNullable = true;
}
}
I had to also override CalenderCellNullable.Clone to get this to work (on .NET 2.0).
public override object Clone() {
CalenderCellNullable clone = (CalenderCellNullable)base.Clone();
clone.isNullable = this.isNullable;
return clone;
}
What am I missing in the original that made it work for you without the code above?
Good call, I didn't think about Clone.
I'm not sure why it would work for me without and it wouldn't work for you. All I could guess was that something in your implementation ended up using Clone, but mine did not. What that "something" is, though, I couldn't begin to guess.
Post a Comment