Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enhancement] DatePicker/TimePicker should support DateOnly/TimeOnly and be able to clear the value #1100

Open
KPixel opened this issue May 27, 2021 · 56 comments
Labels
Milestone

Comments

@KPixel
Copy link

KPixel commented May 27, 2021

Merging #1847

Summary

  • DatePicker and TimePicker should use the new DateOnly and TimeOnly structs instead of DateTime
  • The new fields should be nullable
  • The new fields should be added to the Core implementation of MAUI
  • The old properties should continue to exist in Controls, be marked as Obsolete, and wrap the new properties from Core

API Proposed:

Core:

public partial interface IDatePicker
{ 
  DateOnly? Value { get; set; }
  
  DateOnly? MinimumValue { get; }
  
  DateOnly? MaximumValue { get; }
}

public partial interface ITimePicker
{ 
  TimeOnly? Value { get; }
}

Controls:

public partial class DatePicker
{ 
  DateOnly? Value { get; set; }
  
  DateOnly? MinimumValue { get; set; }
  
  DateOnly? MaximumValue { get; set; }

  [Obsolete($"Use {nameof(Value)} instead.")]
  DateTime Date
  {
    get => Value?.ToDateTime(TimeOnly.MinValue) ?? DateTime.MinValue;
    set => Value = DateOnly.FromDateTime(value);
  }

  [Obsolete($"Use {nameof(MinimumValue)} instead.")]
  DateTime MinimumDate
  {
    get => MinimumValue?.ToDateTime(TimeOnly.MinValue) ?? DateTime.MinValue;
    set => MinimumValue = DateOnly.FromDateTime(value);
  }

  [Obsolete($"Use {nameof(MaximumValue)} instead.")]
  DateTime MaximumDate
  {
    get => MaximumValue?.ToDateTime(TimeOnly.MinValue) ?? DateTime.MinValue;
    set => MaximumValue = DateOnly.FromDateTime(value);
  }
}

public partial class TimePicker
{ 
  TimeOnly? Value { get; set; }

  [Obsolete($"Use {nameof(Value)} instead.")]
  TimeSpan Time
  {
    get => Value?.ToTimeSpan() ?? TimeSpan.Zero;
    set => Value = TimeOnly.FromTimeSpan(value);
  }
}

ORIGINAL POST:

Summary

I wish DatePicker had a Date property that was nullable, so that we could use it for optional dates.

API Changes

My suggestion is to follow how UWP/WinUI resolved this issue in a backward-compatible way: They added another property.

public System.DateOnly? SelectedDate { get; set; }

It would go in both DatePicker and IDatePicker (which doesn't need to be back-compat).

By the way, I used the new DateOnly instead of DateTime. But I'm fine if that's not feasible.

Intended Use Case

Currently, it is a pain to build an app that allows users to provide an optional date. One workaround is to add a checkbox to "activate" entering that date, but that is really not user-friendly.

@KPixel KPixel added t/enhancement ☀️ New feature or request proposal/open labels May 27, 2021
@davidbuckleyni
Copy link

This is actually carried over from a request i had in Xamrian.Forms @PureWeen should be familiar with it to have a nullable or a place holder text on the date picker in total agreement it should have null date abilities even the winforms date pickers had that ability @KPixel

@davidbuckleyni
Copy link

Any objections for me to pick this one up @Redth

@Redth
Copy link
Member

Redth commented May 29, 2021

Thanks! assigned you!

@davidbuckleyni
Copy link

@KPixel Can you describe your issue you say you have to have a button, may I ask why are you not just testing for min value unless xamrain forms didn't have that property I think maybe looking at the code the new Maui controls do.

What I propose is is can give u the SelectedDate as requested but I don't understand why you cant use above method?

@KPixel
Copy link
Author

KPixel commented May 30, 2021

Hi @davidbuckleyni
The key is user-friendliness.

Let me illustrate with some screenshots: (I will use Xamarin.Forms on UWP because it supports nullable Date by default)

The scenario is that the user is filling a form, and there is a DatePicker asking for his birthday. However, that information is optional.

  1. The default UI will look like this:

image

This is weird for the user because they are obviously not born today. And even if we change it to some arbitrary date in the past (like 1900-01-01), that is still weird. The user will think that, if they don't change the value, the system will assume that it is their correct birthday.

  1. So, the quick "hack" is to add a CheckBox before the DatePicker. By default, the DatePicker would be disabled or even hidden. Then, the user has to first check that box to say that they want to fill their birthday.
    The People app in Windows 10 has a variation of this option: You explicitly add the Birthday field first, and you have a button to delete it:

image

It is somewhat ok for the People app because there is a huge number of fields that you could fill, so having a design where all of them are hidden by default works.

  1. By the day, the UWP DatePicker is capable of being empty at first:

image

But, once you pick a date, you have no way of deleting it, by default. So, you can add a Delete button next to the control to achieve this. Something like:

image

This 3rd option should be feasible once we have the nullable SelectedDate.

@davidbuckleyni
Copy link

I will review the Uwp code and take it from their shouldn't take to long prob just be the delay till fix. Till its in the pipeline @KPixel

@davidbuckleyni
Copy link

Looking at the way the date picker is created in Maui we only have access to a Text property not the individual Month day year columns like wpf would a Null Text field of type string be sufficient for your needs that way you could put your own formatting in or would you like it to default to the format mask @KPixel

@KPixel
Copy link
Author

KPixel commented May 31, 2021

Where did you see that Text property? (Which class?)

(By the way, you don't need to @mention me in every message; I'm already subscribed to this issue, so I receive notifications for all its comments.)

@davidbuckleyni
Copy link

The text property is in the main handler class that causes the render to happen its not accessible unless you make a property to expose it from the Maui source code.

@davidbuckleyni
Copy link

It is here in DatePickerExtensions.
DatePickerExtensions
image

The above for for andriod here is ios.
Same File in the platform specific folders

image

@hartez
Copy link
Contributor

hartez commented Jun 1, 2021

The newer CalendarDatePicker control supports de-selecting the date; if we were to replace the current DatePicker on Windows entirely with the CalendarDatePicker, this whole thing gets much easier.

Anyway, there's an issue open to consider supporting the newer control: #1092

@KPixel
Copy link
Author

KPixel commented Jun 1, 2021

For Android, you are referring to:

nativeDatePicker.Text = datePicker.Date.ToString(datePicker.Format);

For iOS:

nativeDatePicker.Text = datePicker.Date.ToString(datePicker.Format);

In both cases, this line would be replaced with something like:

nativeDatePicker.Text = datePicker.SelectedDate?.ToString(datePicker.Format) ?? string.Empty;

Placeholder

As an extra feature, DatePicker can be configured to show a place holder when there is no date:

@davidbuckleyni
Copy link

davidbuckleyni commented Jun 1, 2021

Hi Yes that would be my idea of tackling this situation in this case I will make the adjustment then, Place holder was not in the early versions of Xamarin forms for the DatePickers I had come across the situation you had myself in a production app where I had to use a checkbox, But they are using new Semantic Properties these are properties that can be derived from new controls.

Query how did you do that in highlighting from the Maui Source repo I must learn how to do that rather excellent feature.

So you would rather control just be fully empty rather than any end user message.

@davidbuckleyni
Copy link

Im finding its not quite as simple as this as the Set Text seems to get called every time the control is drawn to the screen @Redth Any idea on another method that we could use we only want to set the SelectedDate property if someone has actually tapped on the date at present its setting it everytime on me cause Set Text is always called.

@KPixel
Copy link
Author

KPixel commented Jun 1, 2021

Query how did you do that in highlighting from the Maui Source repo I must learn how to do that rather excellent feature.

Find the URL to that file here on GitHub, then click on the line number that you are interested in (this will add #Ln to the URL). Then, copy that URL and past it here as-is. GitHub will auto-detect and transform this url.

So you would rather control just be fully empty rather than any end user message.

"End user message" would go in the Placeholder.

the Set Text seems to get called every time the control is drawn to the screen

I'm not very familiar with this specific situation, but the new implementation should work fine even if "Set Text" is called very often. Normally, if the date hasn't change, the text will also not change, so there should be no side effect.
Is something not working correctly because of these calls?

@davidbuckleyni
Copy link

If U use

 dtPicker.SelectedDate = DateTime.Now;

Thats fine it will work as we epxected it to

But Lets say a user also has set a place holder text Plus a selected date.

We are obv checking that the Selected Date is not null and the PlaceHolder but the problem is set text will remove the Place Holder cause its called every time its not like the user tapped the control but the ui set the parameter if u get me.

@KPixel
Copy link
Author

KPixel commented Jun 2, 2021

The Text and the Placeholder are completely independent.

So, for the text, you would have:

nativeDatePicker.Text = datePicker.SelectedDate?.ToString(datePicker.Format) ?? string.Empty;

Then, in a different method, like SetPlaceholder(), you would have:

nativeDatePicker.Placeholder = datePicker.Placeholder;

The nativeDatePicker will take care of showing the placeholder when the text is empty.

@davidbuckleyni
Copy link

@Redth wanting to still do this leave this stil assigned to me

@davidbuckleyni
Copy link

@KPixel #2306 check it out

@KPixel
Copy link
Author

KPixel commented Aug 29, 2021

Hi, I can't run the code, but I will read it and make some comments.

@KPixel
Copy link
Author

KPixel commented Aug 29, 2021

Actually, your pull request only adds the SelectedDate property. It then needs to be wired up to be sync'ed with the Date property and used by the handlers to bind it to the native controls...

Edit: Can you use System.DateOnly?
Unless there is a reason to not do it, I think it is the better type.

@davidbuckleyni
Copy link

@KPixel Yeah am doing that now just getting used to the whole pull request thing etc. Will be doing that tonight the Date Only was not accessible for some reason not sure why. The only issue we might have here, is still the graphical element as explained to u before it seems to require a value of some kind of valid date.

@davidbuckleyni
Copy link

Hi, I can't run the code, but I will read it and make some comments.

Their actually against commenting allot in the code base.

@KPixel
Copy link
Author

KPixel commented Aug 30, 2021

You must be referring to this handler and this renderer.

My understanding of this code is that it creates an alert dialog when you choose to pick a date. But, that's a temporary UI. The main view is the MauiDatePicker which is a simple text box.

So, that text box can display a placeholder when the date is null.

@davidbuckleyni
Copy link

@charlesroddie as I said it was braking other issues the team has a whole agreed to leave it @rmarinho @Redth as was braking to much stuff it was not the issue of a simple or very easy. The others can commnet.

@mattjohnsonpint
Copy link
Contributor

mattjohnsonpint commented Jul 29, 2022

Here's one workaround if you want to use the new types directly on controls.

public class TimeOnlyPicker : TimePicker
{
    public new TimeOnly Time
    {
        get => TimeOnly.FromTimeSpan(base.Time);
        set => base.Time = value.ToTimeSpan();
    }
}

public class DateOnlyPicker : DatePicker
{
    public new DateOnly Date
    {
        get => DateOnly.FromDateTime(base.Date);
        set => base.Date = value.ToDateTime(TimeOnly.MinValue);
    }

    public new DateOnly MaximumDate
    {
        get => DateOnly.FromDateTime(base.MaximumDate);
        set => base.MaximumDate = value.ToDateTime(TimeOnly.MinValue);
    }

    public new DateOnly MinimumDate
    {
        get => DateOnly.FromDateTime(base.MinimumDate);
        set => base.MinimumDate = value.ToDateTime(TimeOnly.MinValue);
    }
}

Or you could do those conversions in your own code against the current controls. Or you could make extension methods, etc.

Yeah, I wish this had been done on the built-in controls sooner but it would certainly be a breaking change at this point.

@davidbuckleyni
Copy link

Here's one workaround if you want to use the new types directly on controls.

public class TimeOnlyPicker : TimePicker
{
    public new TimeOnly Time
    {
        get => TimeOnly.FromTimeSpan(base.Time);
        set => base.Time = value.ToTimeSpan();
    }
}

public class DateOnlyPicker : DatePicker
{
    public new DateOnly Date
    {
        get => DateOnly.FromDateTime(base.Date);
        set => base.Date = value.ToDateTime(TimeOnly.MinValue);
    }

    public new DateOnly MaximumDate
    {
        get => DateOnly.FromDateTime(base.MaximumDate);
        set => base.MaximumDate = value.ToDateTime(TimeOnly.MinValue);
    }

    public new DateOnly MinimumDate
    {
        get => DateOnly.FromDateTime(base.MinimumDate);
        set => base.MinimumDate = value.ToDateTime(TimeOnly.MinValue);
    }
}

Or you could do those conversions in your own code against the current controls. Or you could make extension methods, etc.

Yeah, I wish this had been done on the built-in controls sooner but it would certainly be a breaking change at this point.

Thanks @mattjohnsonpint hopefully it will bring more traction now that windows developers have joined the game and we might be able to get this done.

@jfversluis jfversluis added this to the Backlog milestone Aug 12, 2022
@ghost
Copy link

ghost commented Aug 12, 2022

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@KPixel
Copy link
Author

KPixel commented Aug 12, 2022

Looks like this proposal is pretty much dead.

See you in a few years (hopefully).

@Madde88
Copy link

Madde88 commented Sep 28, 2022

I use a converter as a workaround

        <VerticalStackLayout.Resources>
            <ResourceDictionary>
                <converters:DateTimeToDateOnlyConverter x:Key="DateTimeDateOnly"/>
            </ResourceDictionary>
        </VerticalStackLayout.Resources>

<DatePicker Date="{Binding Date, Converter={StaticResource Key=DateTimeDateOnly}, Mode=TwoWay}" />

    public class DateTimeToDateOnlyConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is DateOnly)
            {
                DateOnly date = (DateOnly)value;
                return date.ToDateTime(TimeOnly.MinValue);
            }
            return DateTime.MinValue;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is DateTime)
            {
                DateTime date = (DateTime)value;
                return DateOnly.FromDateTime(date);
            }
            return DateOnly.MinValue;
        }
    }

@davidbuckleyni
Copy link

Thanks @Madde88 that will be useful to people

@Xyotic
Copy link

Xyotic commented Nov 10, 2022

Any news on supporting null values for Datepickers?
At my company we were hoping to see this feature in MAUI since it has been a big issue for us with Xamarin.
We had to implement our own datepickers.

Something like this should be possible out of the box in my opinion.

@davidbuckleyni
Copy link

@Xyotic We had attempted it before the release but as it would brake quite another number of areas it was left on hold for now am not an MS Dev but @jfversluis or @PureWeen could maybe update this issue now that there seem to be more intrest in it. Ideally it should be nullable.

@jfversluis
Copy link
Member

jfversluis commented Mar 14, 2023

@KPixel in the backlog means far from dead. If anyone is willing to pick this up, happy to help out and review things!

Unassigning David for now as there doesn't seem to be much movement here. If anyone else is willing to give it a shot, feel free to reach out!

@KPixel
Copy link
Author

KPixel commented Mar 14, 2023

@jfversluis I appreciate the intention, however it is now impossible (without major breaking changes) to implement this proposal as planned. Namely:

DateTime Date { get; set; }

IDatePicker.Date was proposed to be nullable (and using DateOnly would have been a nice plus but was not absolutely required).

Can we still make that change?

@jfversluis
Copy link
Member

jfversluis commented Mar 14, 2023

@KPixel it was going to be a breaking change in either case so that should not be an issue.

We do want to make sure, as in the original proposal, that we deprecate the current properties first and add new ones instead of just changing the types on the existing properties. This to make the transition path easier.

@KPixel
Copy link
Author

KPixel commented Mar 14, 2023

The interface IDatePicker was brand new. So, at that level, it wasn't a breaking change.

I am still interested in this enhancement, sadly, I don't have time to work on it at the moment (in my app, I implemented a custom date picker as a workaround).

Edit: Now that I think about the current situation, maybe an approach would be to introduce an interface like INullableDatePicker which follows this proposal. Then the control would implement both interfaces, and the IDatePicker would eventually be marked as deprecated.

@TimKras
Copy link

TimKras commented Apr 19, 2023

WPF DateOnlyPicker

public class DateOnlyPicker : DatePicker
{
    public new DateOnly? SelectedDate
    {
        get { return (DateOnly?)GetValue(SelectedDateProperty); }
        set { SetValue(SelectedDateProperty, value); }
    }

    public new static readonly DependencyProperty SelectedDateProperty =
        DependencyProperty.Register(nameof(SelectedDate), typeof(DateOnly?), typeof(DateOnlyPicker), new FrameworkPropertyMetadata(null,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedDateOnlyChanged));

    private static void SelectedDateOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is not DateOnlyPicker dop)
            return;

        var dateTime = dop.SelectedDate?.ToDateTime(TimeOnly.MinValue);
        if (((DatePicker)dop).SelectedDate != dateTime)
            ((DatePicker)dop).SelectedDate = dateTime;
    }

    protected override void OnSelectedDateChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectedDateChanged(e);
        var dateOnly = base.SelectedDate.HasValue ? DateOnly.FromDateTime(base.SelectedDate.Value) : (DateOnly?)null;
        if (SelectedDate != dateOnly)
            SetValue(SelectedDateProperty, dateOnly);
    }
}

@davidbuckleyni
Copy link

WPF DateOnlyPicker

public class DateOnlyPicker : DatePicker
{
    public new DateOnly? SelectedDate
    {
        get { return (DateOnly?)GetValue(SelectedDateProperty); }
        set { SetValue(SelectedDateProperty, value); }
    }

    public new static readonly DependencyProperty SelectedDateProperty =
        DependencyProperty.Register(nameof(SelectedDate), typeof(DateOnly?), typeof(DateOnlyPicker), new FrameworkPropertyMetadata(null,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedDateOnlyChanged));

    private static void SelectedDateOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is not DateOnlyPicker dop)
            return;

        var dateTime = dop.SelectedDate?.ToDateTime(TimeOnly.MinValue);
        if (((DatePicker)dop).SelectedDate != dateTime)
            ((DatePicker)dop).SelectedDate = dateTime;
    }

    protected override void OnSelectedDateChanged(SelectionChangedEventArgs e)
    {
        base.OnSelectedDateChanged(e);
        var dateOnly = base.SelectedDate.HasValue ? DateOnly.FromDateTime(base.SelectedDate.Value) : (DateOnly?)null;
        if (SelectedDate != dateOnly)
            SetValue(SelectedDateProperty, dateOnly);
    }
}

Doesnt really help maui people ! As @jfversluis said its braking changes its not just as simple as the code you shown.

@WithoutAnAce
Copy link

WithoutAnAce commented Aug 7, 2023

Any news on this?

I guess a better question would be what is the preferred way of implementing an entry field for TimeOnly? and DateOnly? data types while this is still in the works?

@davidbuckleyni
Copy link

@WithoutAnAce This was a question a asked about it even about ef supporting about it https://stackoverflow.com/questions/69146423/date-only-cannot-be-mapped-sql-server-2019
I dont think their is a need for it as of yet but i could beg to differ.

@WithoutAnAce
Copy link

@davidbuckleyni That's a fair workaround for now, thank you.

@jfversluis jfversluis changed the title [Enhancement] DatePicker and TimePicker should support DateOnly? and TimeOnly? [Enhancement] DatePicker/TimePicker should support DateOnly/TimeOnly and be able to clear the value Sep 11, 2023
@Eilon Eilon removed the legacy-area-controls Label, Button, CheckBox, Slider, Stepper, Switch, Picker, Entry, Editor label May 10, 2024
@AppAffection
Copy link

What about being able to clear the value?

I am trying to convert our company's Xamarin app to Maui and I cannot figure out how to get over the hurdle of not having a nullable date picker and a nullable time picker. For instance on a timecard when someone clocks out, if the time is defaulted to something then how do I know they really entered a time? Or worse is a date picker that defaults to todays date. What if the control is bound to a property and the date is for "Physician Contacted On:"? If it is defaulting and they never contacted the physician then how do I make sure a date is not saved to the bound value? Not to mention these are pre-defined EF and database fields that ARE nullable. I do not see how anyone is able to use these controls. Am I just completely missing something here?

@LeoJHarris
Copy link

Squeaky wheel gets the oil they say.

@ederbond
Copy link
Contributor

If you're looking for a native DatePicker that supports nullable, check this package https://www.nuget.org/packages/NPicker
It was created based on the source code of .NET MAUI and fixes the non-nullable issue of that built-in control still has.
Full support for Windows, Android, iOS and MacOS

@ederbond
Copy link
Contributor

@jfversluis @Redth @hartez could you guys, assing this task to me?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

17 participants