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);
}
}
}
}
@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