diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.Properties.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.Properties.cs
index 864ba86ef0..5209744bb3 100644
--- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.Properties.cs
+++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.Properties.cs
@@ -1,6 +1,7 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
+using Avalonia.Data.Converters;
using Avalonia.Layout;
namespace Avalonia.Controls
@@ -74,6 +75,14 @@ namespace Avalonia.Controls
defaultValue: "d",
validate: IsValidDateFormatString);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty TextConverterProperty =
+ AvaloniaProperty.Register(
+ nameof(TextConverter),
+ defaultBindingMode: BindingMode.OneWay);
+
///
/// Defines the property.
///
@@ -265,11 +274,33 @@ namespace Avalonia.Controls
set => SetValue(SelectedDateFormatProperty, value);
}
+ ///
+ /// Gets or sets the custom date format used for display (and/or parsing)
+ /// the date displayed to the user. Not used unless
+ /// is .
+ ///
+ ///
+ /// The default is "d".
+ ///
public string CustomDateFormatString
{
get => GetValue(CustomDateFormatStringProperty);
set => SetValue(CustomDateFormatStringProperty, value);
}
+
+ ///
+ /// Gets or sets the custom bidirectional Text-Value converter for custom date
+ /// string formatting (e.g. for converting user input formats to DateTime or
+ /// displaying a DateTime in a custom string format).
+ /// If set, has priority for formatting over .
+ /// Convert: DateTime -> string
+ /// ConvertBack: string -> DateTIme
+ ///
+ public IValueConverter? TextConverter
+ {
+ get => GetValue(TextConverterProperty);
+ set => SetValue(TextConverterProperty, value);
+ }
///
/// Gets or sets the text that is displayed by the .
diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
index 6194d4c14e..a96dd013c7 100644
--- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
+++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
@@ -716,15 +716,22 @@ namespace Avalonia.Controls
///
private DateTime? ParseText(string text)
{
- DateTime newSelectedDate;
+ DateTime? newSelectedDate;
// TryParse is not used in order to be able to pass the exception to
// the TextParseError event
try
{
- newSelectedDate = SelectedDateFormat == CalendarDatePickerFormat.Custom && !string.IsNullOrEmpty(CustomDateFormatString) ?
- DateTime.ParseExact(text, CustomDateFormatString, DateTimeHelper.GetCurrentDateFormat()) :
- DateTime.Parse(text, DateTimeHelper.GetCurrentDateFormat());
+ if (TextConverter != null)
+ {
+ newSelectedDate = TextConverter.ConvertBack(text, typeof(DateTime?), null, CultureInfo.CurrentCulture) as DateTime?;
+ }
+ else
+ {
+ newSelectedDate = SelectedDateFormat == CalendarDatePickerFormat.Custom && !string.IsNullOrEmpty(CustomDateFormatString) ?
+ DateTime.ParseExact(text, CustomDateFormatString, DateTimeHelper.GetCurrentDateFormat()) :
+ DateTime.Parse(text, DateTimeHelper.GetCurrentDateFormat());
+ }
if (Calendar.IsValidDateSelection(this._calendar!, newSelectedDate))
{
@@ -757,6 +764,10 @@ namespace Avalonia.Controls
private string? DateTimeToString(DateTime d)
{
+ if (TextConverter != null)
+ {
+ return TextConverter.Convert(d, typeof(string), null, CultureInfo.CurrentCulture) as string;
+ }
DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
switch (SelectedDateFormat)
@@ -856,7 +867,9 @@ namespace Avalonia.Controls
DateTime? d = ParseText(s);
if (d != null)
{
- SetValue(TextProperty, s);
+ // make sure displayed text is reformatted to correct date format
+ string? newtext = DateTimeToString((DateTime)d);
+ SetValue(TextProperty, newtext);
return d;
}
else
diff --git a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs
index b620601f71..0ec8bd5ef8 100644
--- a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs
@@ -2,6 +2,7 @@ using System;
using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
+using Avalonia.Data.Converters;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.UnitTests;
@@ -98,6 +99,102 @@ namespace Avalonia.Controls.UnitTests
}
}
+ private class CalendarDatePickerTextConverter : IValueConverter
+ {
+ // date to text
+ public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is DateTime d)
+ return d.ToString("yyyy-MM-dd"); // always return a single format (for this test)
+ return AvaloniaProperty.UnsetValue;
+ }
+
+ // text to date
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ var str = value?.ToString();
+ if (str == null)
+ return AvaloniaProperty.UnsetValue;
+ // allow for a few different date formats
+ string[] formats = ["yyyy-MM-dd", "MM dd yyyy", "dd.MM.yyyy"];
+ if (DateTime.TryParseExact(str, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateValue))
+ {
+ return dateValue;
+ }
+ return AvaloniaProperty.UnsetValue;
+ }
+ }
+
+ [Fact]
+ public void Setting_Date_Manually_Uses_Text_Converter()
+ {
+ CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
+ using (UnitTestApplication.Start(Services))
+ {
+ CalendarDatePicker datePicker = CreateControl();
+ datePicker.SelectedDateFormat = CalendarDatePickerFormat.Custom;
+ datePicker.CustomDateFormatString = "dd.MM.yyyy";
+ datePicker.TextConverter = new CalendarDatePickerTextConverter();
+ var tb = GetTextBox(datePicker);
+
+ datePicker.SelectedDate = new DateTime(2024, 2, 13);
+ // DateTimeToString called async so need to let that complete before testing value
+ Threading.Dispatcher.UIThread.RunJobs(null, TestContext.Current.CancellationToken);
+ Assert.Equal("2024-02-13", datePicker.Text);
+ Assert.True(CompareDates(datePicker.SelectedDate!.Value, new DateTime(2024, 2, 13)));
+
+ // null input results in empty string for text
+ datePicker.SelectedDate = null;
+
+ Assert.Equal("", datePicker.Text);
+ Assert.Null(datePicker.SelectedDate);
+ }
+ }
+
+ [Fact]
+ public void Setting_Date_String_Manually_Can_Accept_Multiple_Formats()
+ {
+ CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
+ using (UnitTestApplication.Start(Services))
+ {
+ CalendarDatePicker datePicker = CreateControl();
+ datePicker.SelectedDateFormat = CalendarDatePickerFormat.Custom;
+ datePicker.CustomDateFormatString = "dd.MM.yyyy";
+ datePicker.TextConverter = new CalendarDatePickerTextConverter();
+ var tb = GetTextBox(datePicker);
+
+ // parser can work with same format as CustomDateFormatString (but TextConverter must handle it)
+ tb.Clear();
+ RaiseTextEvent(tb, "17.10.2024");
+ RaiseKeyEvent(tb, Key.Enter, KeyModifiers.None);
+ Assert.Equal("2024-10-17", datePicker.Text);
+ Assert.True(CompareDates(datePicker.SelectedDate!.Value, new DateTime(2024, 10, 17)));
+
+ // can also handle parsing other formats that the user enters, too
+ tb.Clear();
+ RaiseTextEvent(tb, "2024-02-13");
+ RaiseKeyEvent(tb, Key.Enter, KeyModifiers.None);
+
+ Assert.Equal("2024-02-13", datePicker.Text);
+ Assert.True(CompareDates(datePicker.SelectedDate.Value, new DateTime(2024, 2, 13)));
+
+ tb.Clear();
+ RaiseTextEvent(tb, "04 22 2026");
+ RaiseKeyEvent(tb, Key.Enter, KeyModifiers.None);
+
+ Assert.Equal("2026-04-22", datePicker.Text);
+ Assert.True(CompareDates(datePicker.SelectedDate.Value, new DateTime(2026, 4, 22)));
+
+ // invalid input results in going back to last known (valid) date
+ tb.Clear();
+ RaiseTextEvent(tb, "Not A Valid Date");
+ RaiseKeyEvent(tb, Key.Enter, KeyModifiers.None);
+
+ Assert.Equal("2026-04-22", datePicker.Text);
+ Assert.True(CompareDates(datePicker.SelectedDate.Value, new DateTime(2026, 4, 22)));
+ }
+ }
+
private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of());