Divyesh
Divyesh

Reputation: 2401

How to create nullable DatePickerControl?

I trying to use .NET MAUI's date picker control in my app. I have a requirement that initially when screen get displayed need to show nothing or "-" below StartDate label. But datepicker control is showing default value like 01/01/1900.

Now, the question is how to modify a datepicker control that it will not show any default value initially?

I have did it using label control above the datepicker inside grid. But that is work around with different control. How to handle this using DatePicker control it self? Is it possible?

Updated Jan 06, 2023

With reference to XamGirl's blog post here: https://xamgirl.com/clearable-datepicker-in-xamarin-forms/

And as @Dongzhi Wang-MSFT mentioned we can use Xamarin.Forms renderers in MAUI. So, I have followed the same and created NullDatePiceker control and Custom renderers on both platforms. But it does work only on Android.

iOS NullableDatePicker Renderer

using System;
using UIKit;
using Microsoft.Maui.Controls.Handlers.Compatibility;
using Silvar.Mobile.UI.Core.Controls;
using System.ComponentModel;
using Microsoft.Maui.Controls.Compatibility;
using Microsoft.Maui.Controls.Platform;

namespace Tyler.EnerGov.Mobile.UI.Core.iOS
{
    
    public class NullableDatePickerRenderer : ViewRenderer<NullableDatePicker, UITextField>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<NullableDatePicker> e)
        {
            base.OnElementChanged(e);
           
            if (e.NewElement != null && this.Control != null) 
            {
                this.AddClearButton();
                
                var entry = (NullableDatePicker)this.Element;
                if (!entry.NullableDate.HasValue)
                {
                    this.Control.Text = entry.PlaceHolder;
                }

                if (DeviceInfo.Idiom == DeviceIdiom.Tablet)
                {
                    this.Control.Font = UIFont.SystemFontOfSize(25);
                }
            }
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // Check if the property we are updating is the format property
            if (e.PropertyName == NullableDatePicker.DateProperty.PropertyName || e.PropertyName == NullableDatePicker.FormatProperty.PropertyName)
            {
                var entry = (NullableDatePicker)this.Element;

                // If we are updating the format to the placeholder then just update the text and return
                if (this.Element.Format == entry.PlaceHolder)
                {
                    this.Control.Text = entry.PlaceHolder;
                    return;
                }
            }
            base.OnElementPropertyChanged(sender, e);
        }

        private void AddClearButton()
        {
            var originalToolbar = this.Control.InputAccessoryView as UIToolbar;

            if (originalToolbar != null && originalToolbar.Items.Length <= 2)
            {
                var clearButton = new UIBarButtonItem("Clear", UIBarButtonItemStyle.Plain, ((sender, ev) =>
                {
                    NullableDatePicker baseDatePicker = this.Element as NullableDatePicker;
                    this.Element.Unfocus();
                    this.Element.Date = DateTime.Now;
                    baseDatePicker.CleanDate();

                }));

                var newItems = new List<UIBarButtonItem>();
                foreach (var item in originalToolbar.Items)
                {
                    newItems.Add(item);
                }

                newItems.Insert(0, clearButton);

                originalToolbar.Items = newItems.ToArray();
                originalToolbar.SetNeedsDisplay();
            }
        }
    }
}

Here when OnElementChanged() got called and in that getting this.Control as null. I have registered a renderer using AddHandler.

Does anyone have an idea about this?

Result in Xamarin.Forms (in MAUI it shows Null)

enter image description here

Upvotes: 1

Views: 2083

Answers (3)

Divyesh
Divyesh

Reputation: 2401

I'm able to achieve Nullable Date with a clear button in iOS using Handlers. But now the Done button has stopped working.

  • When clicking on the Done button it's stopped working, nothing happens on the Done button click now. The Done button should close the picker.

iOS Handler:

public class NullableDatePickerRenderer : DatePickerHandler
{
    protected override MauiDatePicker CreatePlatformView()
    {
        return base.CreatePlatformView();
    }

    protected override void ConnectHandler(MauiDatePicker platformView)
    {
        var datePicker = VirtualView as NullableDatePicker;
        var platformPicker = new MauiDatePicker();

        var originalToolbar = platformPicker.InputAccessoryView as UIToolbar;

        if (originalToolbar != null && originalToolbar.Items.Length <= 2)
        {
            var clearButton = new UIBarButtonItem("Clear", UIBarButtonItemStyle.Plain, ((sender, ev) =>
            {
                datePicker.Unfocus();
                datePicker.Date = DateTime.Now;
                datePicker.CleanDate();
            }));

            var newItems = new List<UIBarButtonItem>();
            foreach (var item in originalToolbar.Items)
            {
                newItems.Add(item);
            }

            newItems.Insert(0, clearButton);

            originalToolbar.Items = newItems.ToArray();
            originalToolbar.SetNeedsDisplay();

            platformView.InputAccessoryView = originalToolbar;
            platformView.ReloadInputViews();

            base.ConnectHandler(platformView);
        }
    }
}

Upvotes: 1

Zack
Zack

Reputation: 1609

You can use Custom Renderers, like this

using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;

namespace ClearButtonDemo
{
    public class NullableDatePicker : DatePicker
    {
        public NullableDatePicker()

        {
         //   Format = "d";
        }


        public event EventHandler ClearRequested;
        public void clear()
        {
            if (ClearRequested != null)
            {
                ClearRequested(this, EventArgs.Empty);
            }
        }

        public string _originalFormat = null;

        public static readonly BindableProperty PlaceHolderProperty =
                        BindableProperty.Create(nameof(PlaceHolder), typeof(string), typeof(NullableDatePicker), "");

        public string PlaceHolder
        {
            get { return (string)GetValue(PlaceHolderProperty); }
            set
            {
                SetValue(PlaceHolderProperty, value);
            }
        }

        public static readonly BindableProperty NullableDateProperty =
BindableProperty.Create(nameof(NullableDate), typeof(DateTime?), typeof(NullableDatePicker), null, defaultBindingMode: BindingMode.TwoWay);

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

        private void UpdateDate()
        {
            if (NullableDate != null)
            {
                if (_originalFormat != null)
                {
                    Format = _originalFormat;
                }
            }
            else{
                Format = PlaceHolder;
            }
        }
        protected override void OnBindingContextChanged()
        {
            base.OnBindingContextChanged();

            if (BindingContext != null)
            {
                _originalFormat = Format;
                UpdateDate();
            }
        }

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

            if (propertyName == DateProperty.PropertyName || (propertyName == IsFocusedProperty.PropertyName && !IsFocused && (Date.ToString("d") == DateTime.Now.ToString("d"))))

            {
                AssignValue();
            }

            if (propertyName == NullableDateProperty.PropertyName && NullableDate.HasValue)
            {
                Date = NullableDate.Value;
                if (Date.ToString(_originalFormat) == DateTime.Now.ToString(_originalFormat))
                {
                    UpdateDate();
                }
            }
        }

        public void CleanDate()
        {        
            NullableDate = null;
            UpdateDate();
        }

        public void AssignValue()
        {
            NullableDate = Date;
            UpdateDate();
        }
    }
}

DataPicker Custom renderer:

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using ClearButtonDemo.Droid;
using ClearButtonDemo;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin.Forms;
using System.ComponentModel;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(NullableDatePicker), typeof(NullableDatePickerRenderer))]

namespace ClearButtonDemo.Droid
{
    public class NullableDatePickerRenderer : ViewRenderer<NullableDatePicker, EditText>
    {
        public NullableDatePickerRenderer(Context context) : base(context)
        {

        }

        DatePickerDialog _dialog;

        protected override void OnElementChanged(ElementChangedEventArgs<NullableDatePicker> e)

        {

            base.OnElementChanged(e);



            this.SetNativeControl(new Android.Widget.EditText(Forms.Context));

            if (Control == null || e.NewElement == null)

                return;



            this.Control.Click += OnPickerClick;

            this.Control.Text = Element.Date.ToString(Element.Format);

            this.Control.KeyListener = null;

            this.Control.FocusChange += OnPickerFocusChange;

            this.Control.Enabled = Element.IsEnabled;

           Element.ClearRequested += Element_ClearRequested;

        }

        private void Element_ClearRequested(object sender, EventArgs e)
        {


            this.Element.CleanDate();
            Control.Text = this.Element.Format;
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)

        {

            base.OnElementPropertyChanged(sender, e);



            if (e.PropertyName == Xamarin.Forms.DatePicker.DateProperty.PropertyName || e.PropertyName == Xamarin.Forms.DatePicker.FormatProperty.PropertyName)

                SetDate(Element.Date);

        }



        void OnPickerFocusChange(object sender, Android.Views.View.FocusChangeEventArgs e)
        {

            if (e.HasFocus)
            {
                ShowDatePicker();
            }

        }



        protected override void Dispose(bool disposing)
        {
            if (Control != null)
            {
                this.Control.Click -= OnPickerClick;

                this.Control.FocusChange -= OnPickerFocusChange;

                if (_dialog != null)

                {

                    _dialog.Hide();

                    _dialog.Dispose();

                    _dialog = null;

                }

            }



            base.Dispose(disposing);

        }



        void OnPickerClick(object sender, EventArgs e)

        {

            ShowDatePicker();

        }



        void SetDate(DateTime date)

        {
            //ToString(Element.Format)
            this.Control.Text = date.ToShortDateString();

            Element.Date = date;

        }



        private void ShowDatePicker()

        {

            CreateDatePickerDialog(this.Element.Date.Year, this.Element.Date.Month - 1, this.Element.Date.Day);

            _dialog.Show();

        }



        void CreateDatePickerDialog(int year, int month, int day)

        {

            NullableDatePicker view = Element;

            _dialog = new DatePickerDialog(Context, (o, e) =>

            {

                view.Date = e.Date;

                ((IElementController)view).SetValueFromRenderer(VisualElement.IsFocusedProperty, false);

                Control.ClearFocus();



                _dialog = null;

            }, year, month, day);



            _dialog.SetButton("OK", (sender, e) =>

            {

                SetDate(_dialog.DatePicker.DateTime);

                this.Element.Format = this.Element._originalFormat;

                this.Element.AssignValue();

            });

            _dialog.SetButton2("cancel", (sender, e) =>

            {

                //   this.Element.CleanDate();

                //   Control.Text = this.Element.Format;

                _dialog.Dismiss();

            });   
        }

    }
}

When you use it, XAML:

 <local:NullableDatePicker x:Name="NumdataPicker"/>

.cs

   NumdataPicker.clear();

For iOS, you need to continue to use DatePickerRenderer, modify your code to:

public class NullableDatePickerRenderer : DatePickerRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
    {
        base.OnElementChanged(e);
       
        if (e.NewElement != null && this.Control != null) 
        {
            this.AddClearButton();
            
            var entry = (NullableDatePicker)this.Element;
            if (!entry.NullableDate.HasValue)
            {
                this.Control.Text = entry.PlaceHolder;
            }

            if (DeviceInfo.Idiom == DeviceIdiom.Tablet)
            {
                this.Control.Font = UIFont.SystemFontOfSize(25);
            }
        }
    }

Upvotes: 0

Bas H
Bas H

Reputation: 2216

Maybe like this .

public class MyDatePicker : DatePicker
{
    private string _format = null;
    public static readonly BindableProperty NullableDateProperty = BindableProperty.Create<MyDatePicker, DateTime?>(p => p.NullableDate, null);
    public DateTime? NullableDate
    {
        get { return (DateTime?)GetValue(NullableDateProperty); }
        set { SetValue(NullableDateProperty, value); UpdateDate(); }
    }
    private void UpdateDate()
    {
        if (NullableDate.HasValue) { if (null != _format) Format = _format; Date = NullableDate.Value; }
        else { _format = Format; Format = "pick ..."; }
    }
    protected override void OnBindingContextChanged()
    {
        base.OnBindingContextChanged();
        UpdateDate();
    }
    protected override void OnPropertyChanged(string propertyName = null)
    {
        base.OnPropertyChanged(propertyName);
        if (propertyName == "Date") NullableDate = Date;
    }
}

In Xaml

<local:MyDatePicker x:Name="myDatePicker"></local:MyDatePicker>

In MainPage.cs

{
        InitializeComponent();
        myDatePicker.NullableDate = null;
    }

Upvotes: 0

Related Questions