Hilal Al-Rajhi
Hilal Al-Rajhi

Reputation: 457

C# DateTimePicker DataBinding Parse event not working

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

Answers (5)

Ivan Stoev
Ivan Stoev

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

Larry Dukek
Larry Dukek

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

Hskdopi
Hskdopi

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

Johnny DropTables
Johnny DropTables

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

Kelly Barnard
Kelly Barnard

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

Related Questions