DreJoh
DreJoh

Reputation: 35

xamarin forms DatePicker Cancel/OK event

I found and tried one example of a custom rendered DatePicker, for Android, for Xamarin Forms and does not show which button was clicked in UnFocus. At least not for me. Its from stackoverflow. Xamarin.Forms Android DatePicker/TimePicker button listener

Has the example in this article helped anyone else? I really need to know when the OK button is clicked.

Upvotes: 3

Views: 1703

Answers (1)

ToolmakerSteve
ToolmakerSteve

Reputation: 21243

This extends Nick Kovalsky's answer. I also fixed a bug in that answer, that meant the renderer was never used.

Subclass DatePicker, so that you can add a new BindableProperty and a new method. Place this in your cross-platform project.

OKCancelDatePicker.cs:

using Xamarin.Forms;

// Replace with YOUR namespace.
namespace TestBugs
{
    /// <summary>
    /// NOTE: Requires custom renderer on each platform.
    /// </summary>
    public class OKCancelDatePicker : DatePicker
    {

        public static readonly BindableProperty UserCancelledProperty = BindableProperty.Create(nameof(UserCancelled), typeof(bool), typeof(OKCancelDatePicker), false);
        /// <summary>
        /// Bind to "UserCancelled", to propagate this change elsewhere (e.g. to a VM, or to trigger some logic).
        /// </summary>
        public bool UserCancelled {
            get => (bool)GetValue(UserCancelledProperty);
            set => SetValue(UserCancelledProperty, value);
        }

        /// <summary>
        /// Optionally add code here. Though usually you'll detect the change by binding to UserCancelled.
        /// </summary>
        public void OnPickerClosed()
        {
            if (UserCancelled) {
                // User cancelled.
                _ = 0;   // Dummy code, to set a breakpoint on. You can remove this.
            } else {
                // User selected OK.
                _ = 0;   // Dummy code, to set a breakpoint on. You can remove this.
            }
        }
    }
}

Create a renderer for Android. Place this in your .Android project.

OKCancelDatePickerRenderer.cs:

using Android.App;
using Android.Content;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
// Replace these with YOUR namespaces.
using TestBugs;   // Contains OKCancelDatePicker.
using TestBugs.Droid;   // Contains OKCancelDatePickerRenderer.

[assembly: ExportRenderer(typeof(OKCancelDatePicker), typeof(OKCancelDatePickerRenderer))]
// Replace this with YOUR Android namespace.
namespace TestBugs.Droid
{
    /// <summary>
    /// Based on Nick Kovalsky's https://stackoverflow.com/a/60786875/199364.
    /// </summary>
    public class OKCancelDatePickerRenderer : DatePickerRenderer
    {
        public OKCancelDatePickerRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.DatePicker> e)
        {
            base.OnElementChanged(e);

            //Disposing
            if (e.OldElement != null) {
                _element = null;
            }

            //Creating
            if (e.NewElement != null) {
                _element = e.NewElement as OKCancelDatePicker;
            }
        }

        protected OKCancelDatePicker _element;

        protected override DatePickerDialog CreateDatePickerDialog(int year, int month, int day)
        {
            // This mimics what the original renderer did.
            var dialog = new DatePickerDialog(Context, (o, e) =>
            {
                _element.Date = e.Date;
                ((IElementController)_element).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, false);
            }, year, month, day);

            // These use our custom actions when buttons pressed.
            dialog.SetButton((int)DialogButtonType.Positive, Context.Resources.GetString(global::Android.Resource.String.Ok), OnOk);
            dialog.SetButton((int)DialogButtonType.Negative, Context.Resources.GetString(global::Android.Resource.String.Cancel), OnCancel);

            return dialog;
        }

        private void OnCancel(object sender, DialogClickEventArgs e)
        {
            // This is what the original renderer did when Cancel pressed.
            _element.Unfocus();

            // This is our custom logic.
            _element.UserCancelled = true;
            _element?.OnPickerClosed();
        }
        private void OnOk(object sender, DialogClickEventArgs e)
        {
            // This is what the original renderer did when OK pressed.
            _element.Date = ((DatePickerDialog)sender).DatePicker.DateTime;
            _element.Unfocus();

            // This is our custom logic.
            _element.UserCancelled = false;
            _element?.OnPickerClosed();
        }
    }
}

Usage:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:TestBugs"
             x:Class="TestBugs.DatePickerTestPage">
    <ContentPage.Content>
        <StackLayout>
            <Label Text="Date Picker Test Page" HorizontalOptions="CenterAndExpand" />
            <local:OKCancelDatePicker />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

IMPORTANT: If your app runs on other platforms you'll need to do similar on each platform. Per platform, try to find an example to modify. Nick's answer has a link to one for iOS.


TBD: I should add here an example of how to bind to that UserCancelled property, so you can connect it to logic in your page.

For now, read about Bindable Properties, and google for examples of binding to a BindableProperty of a control or view.

Upvotes: 2

Related Questions