简体   繁体   中英

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:

  • user should be able to select any date from the datepicker (including todays)
  • be able to clear the current selected date
  • cancellation should behave like a cancellation (and clears the date)
  • works for all platforms (iOS, Droid, UWP)

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?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM