Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 416
Hilal Al-Rajhi Avatar asked Mar 23 '17 12:03

Hilal Al-Rajhi


2 Answers

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.

like image 40
Johnny DropTables Avatar answered Sep 27 '22 17:09

Johnny DropTables


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" : " ";
    }
}
like image 59
Ivan Stoev Avatar answered Sep 27 '22 17:09

Ivan Stoev