Событие C# DateTimePicker DataBinding Parse не работает

У меня есть средство выбора даты и времени, которое я связываю с нулевым столбцом даты/времени в наборе данных. Я успешно применил событие Format для нулевого, а не нулевого значения объекта. Но когда я снимаю флажок с управления dtp, в наборе данных не устанавливается значение null. Это мой код:

dtpBirthdate.DataBindings.Add(new Binding("Value", bsStaff, "birthDate", true));
dtpBirthdate.DataBindings["Value"].Format += new ConvertEventHandler(dtpFormat);
dtpBirthdate.DataBindings["Value"].Parse += new ConvertEventHandler(dtpParse);

События формата и разбора:

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; 
                }
            }
        }
   }

После отладки я обнаружил, что он переходит в бесконечный цикл между событиями синтаксического анализа и форматирования. Что не так с моим кодом?

Изменить: существует также представление данных, привязанное к источнику привязки bsStaff.


person Hilal Al-Rajhi    schedule 23.03.2017    source источник


Ответы (5)


Следующее должно решить проблему (см. комментарии в коде):

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
            }
        }
    }
}

Но вся идея с самого начала неверна, потому что она основана на взломе инфраструктуры привязки данных (типичная проблема XY - преодолеть отсутствие свойства DateTime? value в DTP). Предполагается, что события Convert и Parse выполняют преобразование значения из значения источника данных в значение управления и наоборот. Они не должны читать или записывать свойства управления (это нарушает всю инкапсуляцию), информация предоставляется через e.Value и e.DesiredType, и предполагается, что обработчики изменяют e.Value на основе этой информации.

Правильный способ — создать собственный элемент управления, наследующий DateTimePicker и реализующий (теневое) свойство DateTime? Value. Получатель и установщик свойств могут применять необходимую логику (им разрешено читать/изменять другие свойства). Затем замените элементы управления DTP этим пользовательским элементом управления и просто привяжите к свойству «Value» без каких-либо обработчиков событий привязки.

Обновление: вот быстрая и грязная реализация упомянутого необязательного подхода:

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" : " ";
    }
}
person Ivan Stoev    schedule 05.04.2017
comment
Я знаю. Вот почему я сказал, что это решает только конкретную проблему. И в конце упомянул правильное решение, у которого нет таких недостатков. - person Ivan Stoev; 06.04.2017
comment
Как именно я могу реализовать этот класс? Это пользовательский контроль? - person Hilal Al-Rajhi; 24.02.2021
comment
@HilalAl-Rajhi Это не пользовательский элемент управления, а пользовательский элемент управления, или, другими словами, элемент управления, как и любой другой. Просто поместите его в файл кода в своем проекте (внутри пространства имен по вашему выбору или даже внутри System.Windows.Forms), и тогда вы сможете использовать его внутри своего кода и даже во время разработки — он появится в верхней части панели инструментов. - person Ivan Stoev; 25.02.2021

Вы используете «Binding b = sender as Binding» перед нулевой проверкой. проверьте, является ли отправитель нулевым, перед кастингом, и все будет в порядке.

person Johnny DropTables    schedule 30.03.2017
comment
Я добавил эту строку вверху: if (sender == null) return; Но та же проблема. - person Hilal Al-Rajhi; 30.03.2017

Я заметил, что вы используете захват события привязки данных для обоих элементов управления, но в своем первом обработчике событий dtpFormat вы сначала не проверяете значения привязки данных.

Имхо эта строка кода:

if (e.Value == null || e.Value == DBNull.Value)

необходимо изменить на

if (e.Value == DBNull.Value || e.Value == null)

person Hskdopi    schedule 31.03.2017
comment
Я менял, все та же проблема. - person Hilal Al-Rajhi; 31.03.2017
comment
Я уже протестировал ваш образец, кажется, он работает нормально, без каких-либо проблем. Вы хотите установить для dtp значение по умолчанию, когда оно не отмечено? - person Hskdopi; 04.04.2017

Проблема в том, что вам нужно установить e.Value на что-то; но если вы измените его, он снова запустит синтаксический анализ. Попробуйте установить исходное значение.

e.Value = dtp.Value;

Вот ссылка на человека, который уже сталкивался с этим. Они не использовали ваш DbNull.Value, но в остальном он почти идентичен тому, что делаете вы.

http://blogs.interknowlogy.com/2007/01/21/winforms-databinding-datetimepicker-to-a-nullable-type/

person Larry Dukek    schedule 03.04.2017
comment
Я не предлагал вам изменить свой код, чтобы он имитировал код в ссылке. Продолжайте использовать DBNull.Value, если это работает. Скорее я предлагал изменить в методе dtpFromat значение 'e.Value = false;' на 'e.Value = dtp.Value;'. - person Larry Dukek; 05.04.2017

dtpParse устанавливает e.Value = dbNull.Value, который запускает dtpFormat по мере изменения значения, которое, в свою очередь, устанавливает e.Value = false, что отличается от dbNull.Value, которое снова запускает dtpParse. Попробуйте удалить e.Value = false из dtpFormat

person Kelly Barnard    schedule 29.03.2017
comment
Я удалил e.Value = false, но он все еще болтается. Я даже не могу перейти к другому элементу управления, например txtStaffId. Это все еще продолжается - person Hilal Al-Rajhi; 30.03.2017