Reputation: 457
I have a datetimepicker that I am binding with nullable Date/Time column in dataset. I applied Format event successfully for null and not null object value. But, when I uncheck dtp control it does not get set to null in the dataset. This is my code:
dtpBirthdate.DataBindings.Add(new Binding("Value", bsStaff, "birthDate", true));
dtpBirthdate.DataBindings["Value"].Format += new ConvertEventHandler(dtpFormat);
dtpBirthdate.DataBindings["Value"].Parse += new ConvertEventHandler(dtpParse);
Format and Parse events:
private void dtpFormat(object sender, ConvertEventArgs e)
{
Binding b = sender as Binding;
if(b != null)
{
DateTimePicker dtp = (b.Control as DateTimePicker);
if(dtp != null)
{
if (e.Value == null || e.Value == DBNull.Value)
{
dtp.Checked = false;
dtp.CustomFormat = " ";
e.Value = false;
}
else
{
dtp.Checked = true;
dtp.CustomFormat = "dd-MMM-yyyy";
dtp.Value = (DateTime) e.Value;
}
}
}
}
private void dtpParse(object sender, ConvertEventArgs e)
{
Binding b = sender as Binding;
if (b != null)
{
DateTimePicker dtp = (b.Control as DateTimePicker);
if (dtp != null)
{
if (dtp.Checked == false)
{
e.Value = DBNull.Value;
}
else
{
e.Value = dtp.Value;
}
}
}
}
After debugging, I found that it goes to infinite loop between parse and format events. What is wrong with my code?
Edit: There is also a datagridview binded to bsStaff bindingsource.
Upvotes: 9
Views: 2187
Reputation: 205939
The following should fix the issue (see the comments in code):
private void dtpFormat(object sender, ConvertEventArgs e)
{
Binding b = sender as Binding;
if(b != null)
{
DateTimePicker dtp = (b.Control as DateTimePicker);
if (dtp != null)
{
if (e.Value == null || e.Value == DBNull.Value)
{
dtp.Checked = false;
dtp.CustomFormat = " ";
// e.Value = false;
// To prevent dtp.Value property setter setting Checked back to true
e.Value = dtp.Value;
}
else
{
dtp.Checked = true;
dtp.CustomFormat = "dd-MMM-yyyy";
//dtp.Value = (DateTime) e.Value;
// dtp.Value will be set to e.Value from databinding anyway
}
}
}
}
private void dtpParse(object sender, ConvertEventArgs e)
{
Binding b = sender as Binding;
if (b != null)
{
DateTimePicker dtp = (b.Control as DateTimePicker);
if (dtp != null)
{
if (dtp.Checked == false)
{
e.Value = DBNull.Value;
}
else
{
//e.Value = dtp.Value;
// Do nothing, e.Value is already populated with dtp.Value
}
}
}
}
But the whole idea is wrong from the beginning because it's based on data binding infrastructure hacks (the typical XY problem - to overcome the lack of DateTime?
value property in DTP). Convert
and Parse
events are supposed to perform value conversion from data source value to control value and vice versa. They are not supposed to read or write control properties (it breaks the whole encapsulation), the information is provided through e.Value
and e.DesiredType
and the handlers are supposed to change the e.Value
based on that information.
The right way is to create custom control inheriting DateTimePicker
and implementing a (shadow) DateTime? Value
property. The property getter and setter can apply the necessary logic (they are allowed to read/modify other properties). Then replace DTP controls with that custom control and simply bind to "Value" property w/o any binding event handlers.
Update: Here is a quick and dirty implementation of non binding approach mentioned:
public class CustomDateTimePicker : DateTimePicker
{
public CustomDateTimePicker()
{
Format = DateTimePickerFormat.Custom;
SetValueCore(null);
}
new public DateTime? Value
{
get { return Checked ? base.Value : (DateTime?)null; }
set
{
if (Value != value)
SetValueCore(value);
}
}
private void SetValueCore(DateTime? value)
{
if (value == null)
Checked = false;
else
base.Value = value.Value;
UpdateCustomFormat();
}
protected override void OnValueChanged(EventArgs eventargs)
{
UpdateCustomFormat();
base.OnValueChanged(eventargs);
}
private void UpdateCustomFormat()
{
CustomFormat = Value != null ? "dd-MMM-yyyy" : " ";
}
}
Upvotes: 4
Reputation: 2199
The issue is you need to set e.Value to something; but if you change it, it will fire parse again. Try setting it to it's original value.
e.Value = dtp.Value;
Here is a link to someone who has ran into this before. They were not using your DbNull.Value, but other than that it is nearly identical to what you are doing.
http://blogs.interknowlogy.com/2007/01/21/winforms-databinding-datetimepicker-to-a-nullable-type/
Upvotes: 1
Reputation: 51
I noticed that you are using a Databinding event capture for both controls but on your first dtpFormat event handler you don't check for databinding values first.
Imho this line of code:
if (e.Value == null || e.Value == DBNull.Value)
needs to be changed to
if (e.Value == DBNull.Value || e.Value == null)
Upvotes: 1
Reputation: 656
You are casting "Binding b = sender as Binding" before the null check. check if the sender is null before casting and you should be fine.
Upvotes: 1
Reputation: 1141
dtpParse is setting e.Value = dbNull.Value which will fire the dtpFormat as the value has changed which in turn sets e.Value = false which is different to dbNull.Value which will again fire dtpParse. Try removing the e.Value = false from dtpFormat
Upvotes: 0