Skip to content

Instantly share code, notes, and snippets.

@jrgcubano
Last active December 12, 2022 05:53
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jrgcubano/4fdd6ec776c6ddeacb4d518b0355dd77 to your computer and use it in GitHub Desktop.
Save jrgcubano/4fdd6ec776c6ddeacb4d518b0355dd77 to your computer and use it in GitHub Desktop.
ExtendedDatePicker with Nullable values for Xamarin.Forms
using UIKit;
using Xamarin.Forms;
namespace Check.iOS.Extensions
{
/// <summary>
/// Color extensions
/// </summary>
public static class CheckColorExtensions
{
internal static readonly UIColor SeventyPercentGrey = new UIColor(0.7f, 0.7f, 0.7f, 1);
public static bool IsDefault(this Color color) => Color.Default == color;
}
}
// Example usage
<controls:ExtendedDatePicker
x:Name="ContactBirthDate"
Placeholder="{i18n:Translate Contact_BirthdayLabel}"
NullableDate="{Binding Contact.BirthDate}"
Format="d MMM yyyy"
XAlign="Start"
HasBorder="false"
HorizontalOptions="FillAndExpand"
StyleId="BirthDateFieldId"
VerticalOptions="Center"
IsEnabled="{Binding IsNotBusy}" >
</controls:ExtendedDatePicker>
using System;
using Xamarin.Forms;
namespace Check.Client.Core.Controls
{
/// <summary>
/// Extended DatePicker for Nullable Values
/// Via: https://forums.xamarin.com/discussion/20028/datepicker-possible-to-bind-to-nullable-date-value
/// Via: https://github.com/XLabs/Xamarin-Forms-Labs/wiki/ExtendedEntry
/// </summary>
public class ExtendedDatePicker : DatePicker
{
/// <summary>
/// The font property
/// </summary>
public static readonly BindableProperty FontProperty =
BindableProperty.Create("Font", typeof(Font), typeof(ExtendedDatePicker), new Font());
/// <summary>
/// The NullableDate property
/// </summary>
public static readonly BindableProperty NullableDateProperty =
BindableProperty.Create("NullableDate", typeof (DateTime?), typeof (ExtendedDatePicker), null, BindingMode.TwoWay);
/// <summary>
/// The XAlign property
/// </summary>
public static readonly BindableProperty XAlignProperty =
BindableProperty.Create("XAlign", typeof(TextAlignment), typeof(ExtendedDatePicker),
TextAlignment.Start);
/// <summary>
/// The HasBorder property
/// </summary>
public static readonly BindableProperty HasBorderProperty =
BindableProperty.Create("HasBorder", typeof(bool), typeof(ExtendedDatePicker), true);
/// <summary>
/// The Placeholder property
/// </summary>
public static readonly BindableProperty PlaceholderProperty =
BindableProperty.Create("Placeholder", typeof (string), typeof (ExtendedDatePicker), string.Empty, BindingMode.OneWay);
/// <summary>
/// The PlaceholderTextColor property
/// </summary>
public static readonly BindableProperty PlaceholderTextColorProperty =
BindableProperty.Create("PlaceholderTextColor", typeof(Color), typeof(ExtendedDatePicker), Color.Default);
/// <summary>
/// Gets or sets the Font
/// </summary>
public Font Font
{
get { return (Font)GetValue(FontProperty); }
set { SetValue(FontProperty, value); }
}
/// <summary>
/// Get or sets the NullableDate
/// </summary>
public DateTime? NullableDate
{
get { return (DateTime?) GetValue(NullableDateProperty); }
set
{
if (value != NullableDate)
{
SetValue(NullableDateProperty, value);
UpdateDate();
}
}
}
/// <summary>
/// Gets or sets the X alignment of the text
/// </summary>
public TextAlignment XAlign
{
get { return (TextAlignment)GetValue(XAlignProperty); }
set { SetValue(XAlignProperty, value); }
}
/// <summary>
/// Gets or sets if the border should be shown or not
/// </summary>
public bool HasBorder
{
get { return (bool)GetValue(HasBorderProperty); }
set { SetValue(HasBorderProperty, value); }
}
/// <summary>
/// Get or sets the PlaceHolder
/// </summary>
public string Placeholder
{
get { return (string) GetValue(PlaceholderProperty); }
set { SetValue(PlaceholderProperty, value); }
}
/// <summary>
/// Sets color for placeholder text
/// </summary>
public Color PlaceholderTextColor
{
get { return (Color)GetValue(PlaceholderTextColorProperty); }
set { SetValue(PlaceholderTextColorProperty, value); }
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
UpdateDate();
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
//Device.OnPlatform(() =>
//{
if (propertyName == IsFocusedProperty.PropertyName)
{
if (IsFocused)
{
if (!NullableDate.HasValue)
{
Date = (DateTime) DateProperty.DefaultValue;
}
}
else
{
OnPropertyChanged(DateProperty.PropertyName);
}
}
//});
if (propertyName == DateProperty.PropertyName)
{
NullableDate = Date;
}
if (propertyName == NullableDateProperty.PropertyName)
{
if (NullableDate.HasValue)
{
Date = NullableDate.Value;
}
}
}
private void UpdateDate()
{
if (NullableDate.HasValue)
{
Date = NullableDate.Value;
}
else
{
Date = (DateTime)DateProperty.DefaultValue;
}
}
}
}
using Android.Views;
using Check.Client.Core.Controls;
using Check.Droid.Renderers;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(ExtendedDatePicker), typeof(ExtendedDatePickerRenderer))]
namespace Check.Droid.Renderers
{
/// <summary>
/// Extended DatePicker Renderer for Nullable Values
/// Via: https://forums.xamarin.com/discussion/20028/datepicker-possible-to-bind-to-nullable-date-value
/// Via: https://github.com/XLabs/Xamarin-Forms-Labs/wiki/ExtendedEntry
/// </summary>
public class ExtendedDatePickerRenderer : DatePickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
base.OnElementChanged(e);
var view = Element as ExtendedDatePicker;
if (view != null)
{
SetFont(view);
SetTextAlignment(view);
// SetBorder(view);
SetNullableText(view);
SetPlaceholder(view);
SetPlaceholderTextColor(view);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (ExtendedDatePicker)Element;
if (e.PropertyName == ExtendedDatePicker.FontProperty.PropertyName)
SetFont(view);
else if (e.PropertyName == ExtendedDatePicker.XAlignProperty.PropertyName)
SetTextAlignment(view);
// else if (e.PropertyName == ExtendedDatePicker.HasBorderProperty.PropertyName)
// SetBorder(view);
else if (e.PropertyName == ExtendedDatePicker.NullableDateProperty.PropertyName)
SetNullableText(view);
else if (e.PropertyName == ExtendedDatePicker.PlaceholderProperty.PropertyName)
SetPlaceholder(view);
else if (e.PropertyName == ExtendedDatePicker.PlaceholderTextColorProperty.PropertyName)
SetPlaceholderTextColor(view);
}
/// <summary>
/// Sets the text alignment.
/// </summary>
/// <param name="view">The view.</param>
private void SetTextAlignment(ExtendedDatePicker view)
{
switch (view.XAlign)
{
case Xamarin.Forms.TextAlignment.Center:
Control.Gravity = GravityFlags.CenterHorizontal;
break;
case Xamarin.Forms.TextAlignment.End:
Control.Gravity = GravityFlags.End;
break;
case Xamarin.Forms.TextAlignment.Start:
Control.Gravity = GravityFlags.Start;
break;
}
}
/// <summary>
/// Sets the font.
/// </summary>
/// <param name="view">The view.</param>
private void SetFont(ExtendedDatePicker view)
{
if (view.Font != Font.Default)
{
Control.TextSize = view.Font.ToScaledPixel();
Control.Typeface = view.Font.ToExtendedTypeface(Context);
}
}
/// <summary>
/// Set text based on nullable value
/// </summary>
/// <param name="view"></param>
private void SetNullableText(ExtendedDatePicker view)
{
if (view.NullableDate == null)
Control.Text = string.Empty;
}
/// <summary>
/// Set the placeholder
/// </summary>
/// <param name="view"></param>
private void SetPlaceholder(ExtendedDatePicker view)
{
Control.Hint = view.Placeholder;
}
/// <summary>
/// Sets the color of the placeholder text.
/// </summary>
/// <param name="view">The view.</param>
private void SetPlaceholderTextColor(ExtendedDatePicker view)
{
if (view.PlaceholderTextColor != Color.Default)
{
Control.SetHintTextColor(view.PlaceholderTextColor.ToAndroid());
}
}
}
}
using Xamarin.Forms.Platform.iOS;
using Xamarin.Forms;
using Check.Client.Core.Controls;
using System.ComponentModel;
using UIKit;
using Check.iOS.Renderers;
using Check.iOS.Extensions;
using Foundation;
using System;
using CoreGraphics;
[assembly: ExportRenderer(typeof(ExtendedDatePicker), typeof(ExtendedDatePickerRenderer))]
namespace Check.iOS.Renderers
{
/// <summary>
/// Extended DatePicker Renderer for Nullable Values
/// Via: https://forums.xamarin.com/discussion/20028/datepicker-possible-to-bind-to-nullable-date-value
/// Via: https://github.com/XLabs/Xamarin-Forms-Labs/wiki/ExtendedEntry
/// </summary>
public class ExtendedDatePickerRenderer : DatePickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
{
base.OnElementChanged(e);
var view = Element as ExtendedDatePicker;
if (view != null)
{
SetFont(view);
SetTextAlignment(view);
SetBorder(view);
SetNullableText(view);
SetPlaceholderTextColor(view);
ResizeHeight();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (ExtendedDatePicker)Element;
if (e.PropertyName == ExtendedDatePicker.FontProperty.PropertyName)
SetFont(view);
else if (e.PropertyName == ExtendedDatePicker.XAlignProperty.PropertyName)
SetTextAlignment(view);
else if (e.PropertyName == ExtendedDatePicker.HasBorderProperty.PropertyName)
SetBorder(view);
else if (e.PropertyName == ExtendedDatePicker.NullableDateProperty.PropertyName)
SetNullableText(view);
else if (e.PropertyName == ExtendedDatePicker.PlaceholderTextColorProperty.PropertyName)
SetPlaceholderTextColor(view);
ResizeHeight();
}
/// <summary>
/// Sets the text alignment.
/// </summary>
/// <param name="view">The view.</param>
private void SetTextAlignment(ExtendedDatePicker view)
{
switch (view.XAlign)
{
case TextAlignment.Center:
Control.TextAlignment = UITextAlignment.Center;
break;
case TextAlignment.End:
Control.TextAlignment = UITextAlignment.Right;
break;
case TextAlignment.Start:
Control.TextAlignment = UITextAlignment.Left;
break;
}
}
/// <summary>
/// Sets the font.
/// </summary>
/// <param name="view">The view.</param>
private void SetFont(ExtendedDatePicker view)
{
UIFont uiFont;
if (view.Font != Font.Default && (uiFont = view.Font.ToUIFont()) != null)
Control.Font = uiFont;
else if (view.Font == Font.Default)
Control.Font = UIFont.SystemFontOfSize(17f);
}
/// <summary>
/// Sets the border.
/// </summary>
/// <param name="view">The view.</param>
private void SetBorder(ExtendedDatePicker view)
{
Control.BorderStyle = view.HasBorder ? UITextBorderStyle.RoundedRect : UITextBorderStyle.None;
}
/// <summary>
/// Set text based on nullable value
/// </summary>
/// <param name="view"></param>
private void SetNullableText(ExtendedDatePicker view)
{
if (view.NullableDate == null)
Control.Text = string.Empty;
}
/// <summary>
/// Resizes the height.
/// </summary>
private void ResizeHeight()
{
if (Element.HeightRequest >= 0) return;
var height = Math.Max(Bounds.Height,
new UITextField { Font = Control.Font }.IntrinsicContentSize.Height) * 2;
Control.Frame = new CGRect(0.0f, 0.0f, (nfloat)Element.Width, (nfloat)height);
Element.HeightRequest = height;
}
/// <summary>
/// Sets the color of the placeholder text.
/// </summary>
/// <param name="view">The view.</param>
private void SetPlaceholderTextColor(ExtendedDatePicker view)
{
if (!string.IsNullOrEmpty(view.Placeholder))
{
var foregroundUIColor = view.PlaceholderTextColor.ToUIColor(CheckColorExtensions.SeventyPercentGrey);
var backgroundUIColor = view.BackgroundColor.ToUIColor();
var targetFont = Control.Font;
Control.AttributedPlaceholder = new NSAttributedString(view.Placeholder, targetFont, foregroundUIColor, backgroundUIColor);
}
}
}
}
@wegascee
Copy link

Your solution is building up on XLabs. Therefore you have ExtendedEntry in your code. If you don't use XLabs there is no XAlignProperty, HasBorderProperty for normal Entry. Also on Android, if you open the datepicker and don't change the date, the datepicker text isn't updated. If you use Device.OnPlatform(Android: () =>{} instead, the issue on Android goes away for my first test.

@jrgcubano
Copy link
Author

Hi there,

Solution updated. Replaced ExtendedEntry with ExtendedDatePicker. I will review Device.OnPlatform later and add fontsize as property to.

Thanks for your comments.

@jrgcubano
Copy link
Author

Revision 6:

  • Added font property.
  • Fixed ios control height.

@ikovKuzmin
Copy link

Thank you for creating this. I just finished creating a Bindable Entry that allows ints, doubles, and decimals, and am now onto the nullable datepicker. What you have pulled together works great! If I have a null date coming in, it won't show anything in the "picker". So let's say I select a date, and then realize I don't need it? How can I clear it out? Since it's a textbox, I can just backspace to an empty text box. BUT, when I tap out of the picker, it selects that same date I initially picked OR the latest one. Have you seen this behavior?

@jrgcubano
Copy link
Author

I have in my board for next week some fixes and improvements for this control and entry too. I will update this gist as soon as posible and then maybe create a nuget with my core controls.

The main problem with toolbars for entries and pickers is the xamarin.forms renderer it self. They have all the methods as private....We need a big update in Xamarin.Forms to get renderers that can be extended in a fashion manner. Declared methods or fields as protected virtual at least.

:)

@owlstack
Copy link

owlstack commented Nov 3, 2016

@jrgcubano, I tried your approach for setting a placeholder for DatePicker and while the property is set correctly when I was debugging, it's not renderering on the view. The text is blank for the DatePicker.

One thing to note is e.PropertyName for OnElementPropertyChanged is always "Renderer" and not the proper property name. Was there something else I had to set?

@zuckerthoben
Copy link

What about UWP? Can you provide a UWP Renderer?

@krzysztofbielecki
Copy link

@jrgcubano, Thank you for creating this but I have a problem with this control on android. When I click on that DatePicker and confirm (using OK) that the default date is today no value is set. When I change the date to something else it works great but still setting the today value is not working for me (value that was set to placeholder is still there).

@krzysztofbielecki
Copy link

@jrgcubano In ExtendedDatePicker class you miss
/// <summary> /// Gets or sets the Font of the text /// </summary> public Font Font { get { return (Font) GetValue(FontProperty); } set { SetValue(FontProperty, value); } }

@bruzkovsky
Copy link

bruzkovsky commented Mar 28, 2017

@krzysztofbielecki In OnPropertyChanged comment out the Device.OnPlatform part

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

@AhmmedTowfique
Copy link

First of all i would like to thank you for producing such program. I am using it for one of my project. but In ExtendedDatePickerDroidRenderer.cs class i am having an error. Please can you tell me why does it so? how can i solve it. Control.Typeface = view.Font.ToExtendedTypeface(Context);

@RaulMarquezInclan
Copy link

RaulMarquezInclan commented Feb 19, 2018

I have same issue as AhmmedTowfique, how do we fix this?

EDIT: Install XLabs

@RaulMarquezInclan
Copy link

RaulMarquezInclan commented Feb 19, 2018

is this the correct line in content?
xmlns:controls="clr-namespace:MyApp;assembly=MyApp"

EDIT: In my case that was correct. However the date is still rendered in the date field and not the string placed in the placeholder property, while debuggin I can see that the Control.Hint is set correctly with the passed string, but still not showing the placeholder.

Any ideas?

@tcerdaj
Copy link

tcerdaj commented Mar 15, 2018

How can I implement Cancel and OK events for both IOS and Androip. Thanks.

@stewartsims
Copy link

Anyone interested in UWP could look at this issue on a project featuring iOS and Android renderers. Here I outline an approach for a 'right-click' to clear behaviour. Not perfect but it might help someone:

CrossGeeks/ClearableDatePickerSample#10

@munkii
Copy link

munkii commented Sep 29, 2021

Does anyone ever use this Control? I have a fundamental issue with it and nobody else seems to have that problem.

In ExtendedDatePicker OnBindingContextChanged UpdateDate is called.

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

UpdateDate checks the NullableDate property and if that is Null it updates the underlying DatePicker's Date property to min date of 01/01/1900.

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

Setting of the Date property triggers an OnPropertyChnaged in ExtendedDatePicker that then sets the NullableDate to the newly updated Date of 01/01/1900 therefore removing the null Date!

if (propertyName == DateProperty.PropertyName)
{
        NullableDate = Date;
}

This means you never have a NullableDate that is Null after the first pass of the Binding code. Surely other people have seen this issue too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment