testing
testing

Reputation: 20289

Xamarin.Forms (Nullable) DatePicker: Workaround for missing OK and cancel events

I'm using a nullable DatePicker, which is implemented by subclassing from DatePicker and using custom renderers.

public class ExtendedDatePicker : DatePicker
{
    public static readonly BindableProperty NullableDateProperty =
        BindableProperty.Create(
            "NullableDate", 
            typeof(DateTime?), 
            typeof(ExtendedDatePicker), 
            null, 
            BindingMode.TwoWay);

    public static readonly BindableProperty PlaceholderProperty =
        BindableProperty.Create(
            "Placeholder", 
            typeof(string), 
            typeof(ExtendedDatePicker), 
            string.Empty, 
            BindingMode.OneWay);

    public DateTime? NullableDate
    {
        get { return (DateTime?)GetValue(NullableDateProperty); }
        set
        {
            if (value != NullableDate)
            {
                SetValue(NullableDateProperty, value);
            }
        }
    }

    public string Placeholder
    {
        get { return (string)GetValue(PlaceholderProperty); }
        set { SetValue(PlaceholderProperty, value); }
    }

    public ExtendedDatePicker()
    {
        //this.Unfocused += ExtendedDatePicker_Unfocused;
        //this.DateSelected += ExtendedDatePicker_DateSelected;
    }

    //private void ExtendedDatePicker_DateSelected(object sender, DateChangedEventArgs e)
    //{
    //    NullableDate = Date;
    //}

    //private void ExtendedDatePicker_Unfocused(object sender, FocusEventArgs e)
    //{
    //    if (Device.RuntimePlatform == Device.Android && !e.IsFocused)
    //    {
    //        NullableDate = Date;
    //    }
    //}

    protected override void OnBindingContextChanged()
    {
        base.OnBindingContextChanged();
        UpdateDate();
    }

    protected override void OnPropertyChanged(string propertyName = null)
    {
        base.OnPropertyChanged(propertyName);

        if (propertyName == IsFocusedProperty.PropertyName)
        {
            if (IsFocused)
            {
                if (!NullableDate.HasValue)
                {
                    Date = (DateTime)DateProperty.DefaultValue;
                }
            }
            else
            {
                OnPropertyChanged(DateProperty.PropertyName);
            }
        }

        if (propertyName == DateProperty.PropertyName)
        {
            if (Date != default(DateTime))
            {
                if (Date != NullableDate.GetValueOrDefault())
                    NullableDate = Date;
            }
            else
            {
                if (NullableDate != null)
                    NullableDate = null;
            }
        }

        if (propertyName == NullableDateProperty.PropertyName)
        {
            if (NullableDate.HasValue)
            {
                if (Date != NullableDate.GetValueOrDefault())
                    Date = NullableDate.Value;
            }
        }
    }

    private void UpdateDate()
    {
        if (NullableDate.HasValue)
        {
            Date = NullableDate.Value;
        }
        else
        {
            Date = (DateTime)DateProperty.DefaultValue;
        }
    }
}

I don't provide the custom renderers here, because they are mostly setter. My current approach works, but the clearing of the selected date has to be done via an extra button and pressing "cancel" still sets the date in the datepicker. Now I want to improve this behavior and I'm looking for a better solution.

Issues:

There is already a feature request, but this hasn't been implemented yet. Also there are some tries to workaround this problem, but I haven't found a working solution, which solves all my issues.

Either I can use the DateSelected event, which does not get fired if I select todays date (or the last selected date to be exactly and on initialization this is todays date). Or I set the NullableDate everytime and on cancellation the date is also shown. As result, the user can't clear the date if he press on cancel. I tried to experiment with the Unfocused event, but I haven't found something useful. Perhaps one could use the ViewRenderer and exchanges the underlying native view to have full control over it? But the renderers have wrong base classes ...

Any ideas?

Upvotes: 3

Views: 4974

Answers (2)

Andrea
Andrea

Reputation: 11

I had the same problem and I resolved in this simple way: I added a converter that returns a color.transparent if date is null and color.black if date is not null; then I used this converter in XAML on the TextColor of datepicker. The result is that, if the date is null, datepicker seems to be empty; but, if you choose a date, the color become black.

Upvotes: 1

Arun Gupta
Arun Gupta

Reputation: 2636

I had found the solution for this. Struggled a lot for this.

No need to listen for DateSelected or UnFocused Events.

Basically we create a event in ExtendedDatePicker and then call this event upon Done/Cancel clicked probably UpdateDate method or based on your flow where date is set.

Now in view where ExtendedDatePicker is used, in .cs file listen for the OnDateSelected event.

Example:

.xaml file:

<xyz:BorderlessDatePicker x:Name="drawCalender" Format="dd-MMM-yyyy" />

.xaml.cs file:

drawCalender.OnDateSelected += (object sender, DateChangedEventArgs e) =>
                {
                    ExtendedDatePicker calendarPicker = (ExtendedDatePicker)sender;
                    if (calendarPicker.NullableDate != null)
                    {
                        CalendarText = (DateTime)calendarPicker.NullableDate;
                    }
                    else
                    {
                        CalendarText = null;
                    }
                };

ExtendedDatePicker Class:

public class ExtendedDatePicker : DatePicker
{
    public delegate void DateSelectedHandler(object sender, DateChangedEventArgs e);
    public event DateSelectedHandler OnDateSelected;

    public static readonly BindableProperty NullableDateProperty =
        BindableProperty.Create(
            "NullableDate", 
            typeof(DateTime?), 
            typeof(ExtendedDatePicker), 
            null, 
            BindingMode.TwoWay);


    protected override void OnBindingContextChanged()
    {
        base.OnBindingContextChanged();
        UpdateDate();
    }

    protected override void OnPropertyChanged(string propertyName = null)
    {
        base.OnPropertyChanged(propertyName);

        if (propertyName == IsFocusedProperty.PropertyName)
        {
            if (IsFocused)
            {
                if (!NullableDate.HasValue)
                {
                    Date = (DateTime)DateProperty.DefaultValue;
                }
            }
            else
            {
                OnPropertyChanged(DateProperty.PropertyName);
            }
        }

        if (propertyName == DateProperty.PropertyName)
        {
            if (Date != default(DateTime))
            {
                if (Date != NullableDate.GetValueOrDefault())
                    NullableDate = Date;
            }
            else
            {
                if (NullableDate != null)
                    NullableDate = null;
            }
        }

        if (propertyName == NullableDateProperty.PropertyName)
        {
            if (NullableDate.HasValue)
            {
                if (Date != NullableDate.GetValueOrDefault())
                    Date = NullableDate.Value;
            }
        }
    }

    private void UpdateDate()
    {
        if (NullableDate.HasValue)
        {
            Date = NullableDate.Value;
        }
        else
        {
            Date = (DateTime)DateProperty.DefaultValue;
        }
        OnDateSelected(this, null);
    }
}

Hope this helps.

Upvotes: 1

Related Questions