Date and Time picker

Hi I'm building a simple diary app with Xamarin.Forms and have been really enjoying it so far, however I've hit a bit of a brick wall trying to get Date and Time pickers working using a single DateTime property for binding. I have been using a solution posted by ren78 posted here https://forums.xamarin.com/discussion/comment/85512/#Comment_85512

The only change I've made to this is where the bindable property is defined as I was getting an error with ren78's example:

public static readonly BindableProperty ValueProperty = BindableProperty.Create<DateTimePicker, DateTime>(p => p.Value, default(DateTime));

My start_date property is defined in my viewmodel like this:
public DateTime start_date { get { return evnt.start_date; } set { evnt.start_date = value; NotifyPropertyChanged(); } }
which as far as I can tell should bind a single datetime value to a datepicker and a timepicker, but it is always returning the default value (current date) instead of the correct datetime value which has me confused. If anyone knows another way of accomplishing this or can see where I'm making a mistake then please let me know!
Thanks.

Best Answer

Answers

  • PaulDistonPaulDiston USUniversity ✭✭✭✭
    edited April 2015

    Hi,

    I have made a slight change to the code posted in the other thread, making the Value a traditional bindable property and using event handlers on the DatePicker and TimePicker, as follows :-

    using System; using System.ComponentModel; using System.Globalization; using Xamarin.Forms; using Xamarin.Forms.Labs; using Xamarin.Forms.Labs.Services; namespace DateTimePickerSample { public class DateTimeControl : StackLayout { private readonly DatePicker _date; private readonly TimePicker _time; private readonly IDisplay _display; public DateTimeControl() { _display = Resolver.Resolve<IDisplay>(); this.Orientation = StackOrientation.Horizontal; this.Padding = 2; _date = new DatePicker(); _date.Format = CultureInfo.DefaultThreadCurrentUICulture.DateTimeFormat.ShortDatePattern; _date.WidthRequest = ((_display.Width / 2) / 3) * 2 - 50; _time = new TimePicker(); _time.Format = CultureInfo.DefaultThreadCurrentUICulture.DateTimeFormat.ShortTimePattern; _time.WidthRequest = ((_display.Width / 2) / 3) - 10; this.Children.Add(_date); this.Children.Add(_time); _date.PropertyChanged += DateOnPropertyChanged; _time.PropertyChanged += TimeOnPropertyChanged; } private void TimeOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) { if (propertyChangedEventArgs.PropertyName == "Time") { Value = _date.Date.Add(_time.Time); } } private void DateOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) { if (propertyChangedEventArgs.PropertyName == "Date") { Value = _date.Date.Add(_time.Time); } } #region Value public static readonly BindableProperty ValueProperty = BindableProperty.Create<DateTimeControl, DateTime>(p => p.Value, default(DateTime)); public DateTime Value { get { return (DateTime)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } #endregion } }

    A side note, the calculation used to determine the WidthRequest of the two controls did not work on my OnePlus One with a 1024 wide screen.

    Hope this helps.

    Paul Diston

  • Thanks for the reply! Unfortunately i'm still getting the default date instead of the correct value but if your code works for you it must be something to do with how my app works.
    It could be because I initialize the view before passing any data to it as I use the messaging center to send data to the viewmodel, but surely the values would update.

    I have no idea why its not working for me :(

    PS: I commented out the width calculations as I was also getting an error but I can always come back to that later once the control is working

  • PaulDistonPaulDiston USUniversity ✭✭✭✭

    Hi,

    If you can package up a sample, I would be happy to take a look.

    Thanks

    Paul Diston

  • Hi here is a sample of what im trying to do. I'm pretty new to Xamarin so I'm not sure if its the correct way but its been working so far until now. Thanks for the help! :)

  • PaulDistonPaulDiston USUniversity ✭✭✭✭

    Hi,

    Would you be able to explain how you want the functionality to work?

    For example, I don't see the Save button click event being handled, so not sure what you want to happen.

    Thanks

    Paul Diston

  • FindlayBaxter.7825FindlayBaxter.7825 USMember
    edited April 2015

    I'm using azure mobile services to sync with an existing database so when the user clicks save the event is sent to a script which adds it to the local database and then syncs it with the online one via the service(this works fine), I removed all this functionality for the sample for readability. In my main app all the button click events are handled, the only issue I'm having is with binding to the custom datepicker as regardless of the datetime on the start_date and end_date properties it shows the default datetime instead of the correct value for the event.

    I should have maybe made the start and end dates displayed on the list page but you can see the list of them in the codbehind on the list view:
    public List<Events> eventList = new List<Events> { new Events{ text="Breakfast", start_date = DateTime.Now.AddDays(1), end_date = DateTime.Now.AddDays(1).AddMinutes(15) }, new Events{ text="Lunch", start_date = DateTime.Now.AddDays(2), end_date = DateTime.Now.AddDays(2).AddMinutes(59) }, new Events{ text="Dinner", start_date = DateTime.Now.AddDays(3), end_date = DateTime.Now.AddDays(3).AddMinutes(22) } };

    Thanks again

  • YES! that's the control displaying the correct date now! Thanks so much for all your help Paul, much appreciated :smile:

  • NestorLedonNestorLedon USMember ✭✭
    edited July 2015

    @PaulDiston I ran into some issues trying to bind with your code. Everything seems to work great; except the second I set a binding I get exceptions:

    The string was not recognized as a valid DateTime. There is an unknown word starting at index 0.
    

    Code is nearly the same outside of CultureInfo.CurrentCulture.

        public partial class DateTimeControl : StackLayout
        {
            public DatePicker _date;
            public TimePicker _time;
            public IDisplay _display;
    
            public DateTimeControl()
            {
    
                IDevice device = Resolver.Resolve<IDevice>();
    
                _display = device.Display;
                this.Orientation = StackOrientation.Vertical;
                this.Padding = 2;
                _date = new DatePicker();
                _date.Format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
                _date.WidthRequest = ((_display.Width / 2) / 3) * 2 - 50;
                _time = new TimePicker();
                _time.Format = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
                _time.WidthRequest = ((_display.Width / 2) / 3) - 10;
                this.Children.Add(_date);
                this.Children.Add(_time);
                _date.PropertyChanged += DateOnPropertyChanged;
                _time.PropertyChanged += TimeOnPropertyChanged;
    
                _date.Date = new DateTime ();
                _time.Time = new DateTime ().TimeOfDay;
            }
            private void TimeOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
            {
                if (propertyChangedEventArgs.PropertyName == "Time")
                {
                    Value = _date.Date.Add(_time.Time);
                }
            }
            private void DateOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
            {
                if (propertyChangedEventArgs.PropertyName == "Date")
                {
                    Value = _date.Date.Add(_time.Time);
                }
            }
    
            #region Value
            public static readonly BindableProperty ValueProperty = 
                BindableProperty.Create<DateTimeControl, DateTime>(p => p.Value, 
                    defaultValue: default(DateTime),
                    defaultBindingMode: BindingMode.OneWay,
                    propertyChanging: (bindable, oldValue, newValue) => {
                        ffffff(bindable, oldValue, newValue);
                        //DateTimeControl ctrl = (DateTimeControl)bindable;
                        //ctrl._date.Date = newValue;
                    });
    
            public static void ffffff (BindableObject bindable, DateTime oldValue, DateTime newValue)
            {
                DateTimeControl ctrl = (DateTimeControl)bindable;
                ctrl._date.Date = newValue;
            }
    
            public DateTime Value
            {
                get
                {
                    return (DateTime)GetValue(ValueProperty);
                }
                set
                {
                    SetValue(ValueProperty, value);
                }
            }
            #endregion
        }
    
    <controls:DateTimeControl x:Name="startTime" Value="Event.StartTime"/>
    
    <controls:DateTimeControl x:Name="endTime" Value="Event.EndTime"/>
    
  • NestorLedonNestorLedon USMember ✭✭

    Was able to resolve this. Just needed to pass the values through a converter.

    <controls:DateTimeControl x:Name="startTime" Value="{Binding Event.StartTime, Converter={StaticResource dtc}"/>
    
    <controls:DateTimeControl x:Name="endTime" Value="{Binding Event.EndTime, Converter={StaticResource dtc}"/>
    
    
  • siva55siva55 USMember

    hi

    I am using a Datapicker `dpExpdate.Focus();

                    dpExpdate.PropertyChanged +=dpExpdate_PropertyChanged;                  
                    txtExpdate.Text = string.Empty;`
    

    in delegate i wrote the simple code like
    ` private void dpExpdate_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {

            txtExpdate.Text = dpExpdate.Date.ToString();
        }`
    

    In Winphone this delete(event) is not fired with out changing the date value.

    means when picker opend i am not chageing the date and simply click on "ok".
    then the current value of date picker is stored in txtExpdate.Text.

    so Please help out........

  • Nick2016Nick2016 USMember

    @PaulDiston

    Hi Paul,

    I am trying to use your code. However, BindableProperty.Create<DateTimeControl, DateTime> is obsolete and no longer supported. It is beyond my skill to rewrite using the new method. Could you please help revising your code for me?

    Similar to Nestor, I will be using it for a startdatetime and enddatetime. What would a converter "dtc" be as mentioned in his post?

    Thanks.

  • cristhianargcristhianarg ARMember

    @Nick2016 This is my dtc:

    public class DateTimeConverter : IValueConverter
        {
    
            #region IValueConverter implementation
    
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                if (value == null)
                {
                    value = DateTime.Now;
                }
                if (value is DateTime)
                {
                    return ((DateTime)value).ToString("M/d/yyyy");
                }
                return value.ToString();
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
                return System.Convert.ToDateTime(value);
            }
    
            #endregion
        }
    
    • DateTimePicker.cs
    #region Value
            //public static readonly BindableProperty ValueProperty =
            //            BindableProperty.Create<DateTimePicker, DateTime>(p => p.Value, default(DateTime));
            public static readonly BindableProperty ValueProperty =
                BindableProperty.Create(nameof(Value), typeof(DateTime), typeof(DateTimePicker), default(DateTime));
    
            public DateTime Value
            {
                get
                {
                    return (DateTime)GetValue(ValueProperty);
                }
                set
                {
                    SetValue(ValueProperty, value);
                }
            }
    #endregion
    

    It is not perfect, but it works for me.
    Cheers from Salta, Argentina

  • MelbourneDeveloperMelbourneDeveloper AUMember ✭✭✭
    edited June 2017

    Hey people. I've added this code to a repo I've been working on here:
    https://github.com/MelbourneDeveloper/Adapt.Presentation

    This is my implementation of the code:
    https://github.com/MelbourneDeveloper/Adapt.Presentation/blob/master/Adapt.Presentation.Standard/Adapt/Presentation/Controls/DateTimePicker.cs

    I've cleaned a few things up and included some extra features that might be necessary for some. Firstly, it uses a WrapLayout instead of a StackPanel so that horizontal space is used before dropping back to vertical space. Secondly, I've added Clear, and Now buttons because users often need to be able to clear out dates, or select the current Date/Time.

    Here's the code as at now, but it will probably change so keep and eye on the repo. The WrapLayout control I picked up from here https://github.com/conceptdev/xamarin-forms-samples/blob/master/Evolve13/Evolve13/Controls/WrapLayout.cs and is included in my repo.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Globalization;
    using Xamarin.Forms;
    
    namespace Adapt.Presentation.Controls
    {
    public class DateTimePicker : WrapLayout
    {
        #region Fields
        private readonly DatePicker _Date;
        private readonly TimePicker _Time;
        private readonly Button _ClearButton;
        private readonly Button _NowButton;
        private bool _IsChanging;
        #endregion
    
        #region Bindable Properties
        public static readonly BindableProperty ValueProperty =
        BindableProperty.Create<DateTimePicker, DateTime>
        (
            p => p.Value,
            defaultValue: default(DateTime),
            defaultBindingMode: BindingMode.TwoWay,
            propertyChanging: (bindable, oldValue, newValue) =>
            {
                ValueChanging(bindable, oldValue, newValue);
            }
        );
    
        private static void ValueChanging(BindableObject bindable, DateTime oldValue, DateTime newValue)
        {
            var ctrl = (DateTimePicker)bindable;
            ctrl._IsChanging = true;
            ctrl._Date.Date = newValue;
            ctrl._Time.Time = newValue.TimeOfDay;
            ctrl._IsChanging = false;
        }
        #endregion
    
        #region Public Properties
        public DateTime Value
        {
            get => (DateTime)GetValue(ValueProperty);
            set => SetValue(ValueProperty, value);
        }
        #endregion
    
        #region Constructor
        public DateTimePicker()
        {
            Padding = 2;
            _Date = new DatePicker { Format = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern };
            _Time = new TimePicker { Format = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern };
            _ClearButton = new Button { Text = "Clear" };
            _NowButton = new Button { Text = "Now" };
            Children.Add(_ClearButton);
            Children.Add(_NowButton);
            Children.Add(_Date);
            Children.Add(_Time);
            _Date.PropertyChanged += PropertyChanged;
            _Time.PropertyChanged += PropertyChanged;
            _ClearButton.Clicked += ClearButton_Clicked;
            _NowButton.Clicked += NowButton_Clicked;
            _Date.Date = new DateTime();
            _Time.Time = new DateTime().TimeOfDay;
        }
        #endregion
    
        #region Event Handlers
    
        private void NowButton_Clicked(object sender, EventArgs e)
        {
            Value = DateTime.Now;
        }
    
        private void ClearButton_Clicked(object sender, EventArgs e)
        {
            Value = DateTime.MinValue;
        }
    
        private void PropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
        {
            if (_IsChanging)
            {
                return;
            }
    
            if (!new List<string> { nameof(TimePicker.Time), nameof(DatePicker.Date) }.Contains(propertyChangedEventArgs.PropertyName))
            {
                return;
            }
    
            Value = _Date.Date.Add(_Time.Time);
        }
        #endregion
    }
    }
    
  • MelbourneDeveloperMelbourneDeveloper AUMember ✭✭✭
    edited June 2017

    Thanks to @PaulDiston for the original code.

  • MelbourneDeveloperMelbourneDeveloper AUMember ✭✭✭

    I've now got the samples running on all the platforms. If you run the sample, there is a DateTimePicker with a sample on the second tab.

    https://github.com/MelbourneDeveloper/Adapt.Presentation.git

  • I apologize for resurrecting an old post, but I ran across a problem with the code posted as solutions to this question which I am unable to get past at the moment.

    I have no issues with getting the binding to initially set correctly, but the problem I ran into is with the changing of the value.

    I am using 2 of these DateTimePickers and so far they have been working fantastically, however I am having some trouble attaching some behaviors to the Value Property of the pickers.

    I have a calculation that is supposed to take place when the Value changes, but it is never being evaluated with this custom control. The Property changes in the View and I am able to save the Form entry into the database successfully, but the calculation doesn't happen.

    Am I missing something about the implementation of the Custom controls that needs to be added in order to notify the view/viewmodel of the value changing?

    I am using the "Behaviors.Forms" library by David Britch to attach behaviors.

    Here is my XAML for the DateTimePickers:

    <local:DateTimePicker x:Name="pickStartDate" Value="{Binding VTrip.StartDate, Mode=TwoWay}" Text="Start Date/Time" Grid.Row="0" Grid.ColumnSpan="3"> <local:DateTimePicker.Behaviors> <behaviors:DataChangedBehavior Binding="{Binding Path=Value, Source={x:Reference pickStartDate}}"> <behaviors:InvokeCommandAction Command="{Binding TimeChangedCommand}" /> </behaviors:DataChangedBehavior> </local:DateTimePicker.Behaviors> </local:DateTimePicker>

    The DateTimePicker code is nearly identical to what was posted by @PaulDiston, with minor changes for formatting and a second bindable property for a label (Text).

    I have even tried the code that @MelbourneDeveloper posted (without the WrapLayout), but it gave me the same result.
    When trying to pass the PropertyChanged event through a converter my app crashes on start with an unhandled exception (no extra info). ( I have verified the converter is functioning correctly with other controls).

    Any help or pointers would be greatly appreciated.

Sign In or Register to comment.