diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index 0b4463ddb7..37f9da0c43 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -60,6 +60,9 @@
Designer
+
+ Designer
+
Designer
@@ -128,6 +131,9 @@
DropDownPage.xaml
+
+ DatePickerPage.xaml
+
ExpanderPage.xaml
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 311a61a6dc..060369e404 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -7,11 +7,12 @@
-
+
+
diff --git a/samples/ControlCatalog/Pages/DatePickerPage.xaml b/samples/ControlCatalog/Pages/DatePickerPage.xaml
new file mode 100644
index 0000000000..92cfa7e178
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DatePickerPage.xaml
@@ -0,0 +1,46 @@
+
+
+ DatePicker
+ A control for selecting dates with a calendar drop-down
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/ControlCatalog/Pages/DatePickerPage.xaml.cs b/samples/ControlCatalog/Pages/DatePickerPage.xaml.cs
new file mode 100644
index 0000000000..ef01887c9e
--- /dev/null
+++ b/samples/ControlCatalog/Pages/DatePickerPage.xaml.cs
@@ -0,0 +1,36 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using System;
+
+namespace ControlCatalog.Pages
+{
+ public class DatePickerPage : UserControl
+ {
+ public DatePickerPage()
+ {
+ InitializeComponent();
+
+ var dp1 = this.FindControl("DatePicker1");
+ var dp2 = this.FindControl("DatePicker2");
+ var dp3 = this.FindControl("DatePicker3");
+ var dp4 = this.FindControl("DatePicker4");
+ var dp5 = this.FindControl("DatePicker5");
+
+ dp1.SelectedDate = DateTime.Today;
+ dp2.SelectedDate = DateTime.Today.AddDays(10);
+ dp3.SelectedDate = DateTime.Today.AddDays(20);
+ dp5.SelectedDate = DateTime.Today;
+
+ dp4.TemplateApplied += (s, e) =>
+ {
+ dp4.BlackoutDates.AddDatesInPast();
+ };
+
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/DatePicker.cs
new file mode 100644
index 0000000000..418ef50b2c
--- /dev/null
+++ b/src/Avalonia.Controls/Calendar/DatePicker.cs
@@ -0,0 +1,1188 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using System;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Provides data for the
+ ///
+ /// event.
+ ///
+ public class DatePickerDateValidationErrorEventArgs : EventArgs
+ {
+ private bool _throwException;
+
+ ///
+ /// Initializes a new instance of the
+ ///
+ /// class.
+ ///
+ ///
+ /// The initial exception from the
+ ///
+ /// event.
+ ///
+ ///
+ /// The text that caused the
+ ///
+ /// event.
+ ///
+ public DatePickerDateValidationErrorEventArgs(Exception exception, string text)
+ {
+ this.Text = text;
+ this.Exception = exception;
+ }
+
+ ///
+ /// Gets the initial exception associated with the
+ ///
+ /// event.
+ ///
+ ///
+ /// The exception associated with the validation failure.
+ ///
+ public Exception Exception { get; private set; }
+
+ ///
+ /// Gets the text that caused the
+ ///
+ /// event.
+ ///
+ ///
+ /// The text that caused the validation failure.
+ ///
+ public string Text { get; private set; }
+
+ ///
+ /// Gets or sets a value indicating whether
+ ///
+ /// should be thrown.
+ ///
+ ///
+ /// True if the exception should be thrown; otherwise, false.
+ ///
+ ///
+ /// If set to true and
+ ///
+ /// is null.
+ ///
+ public bool ThrowException
+ {
+ get { return this._throwException; }
+ set
+ {
+ if (value && this.Exception == null)
+ {
+ throw new ArgumentException("Cannot Throw Null Exception");
+ }
+ this._throwException = value;
+ }
+ }
+ }
+
+ ///
+ /// Specifies date formats for a
+ /// .
+ ///
+ public enum DatePickerFormat
+ {
+ ///
+ /// Specifies that the date should be displayed using unabbreviated days
+ /// of the week and month names.
+ ///
+ Long = 0,
+
+ ///
+ /// Specifies that the date should be displayed using abbreviated days
+ /// of the week and month names.
+ ///
+ Short = 1,
+
+ ///
+ /// Specifies that the date should be displayed using a custom format string.
+ ///
+ Custom = 2
+ }
+
+ public class DatePicker : TemplatedControl
+ {
+ private const string ElementTextBox = "PART_TextBox";
+ private const string ElementButton = "PART_Button";
+ private const string ElementPopup = "PART_Popup";
+ private const string ElementCalendar = "PART_Calendar";
+
+ private Calendar _calendar;
+ private string _defaultText;
+ private Button _dropDownButton;
+ //private Canvas _outsideCanvas;
+ //private Canvas _outsidePopupCanvas;
+ private Popup _popUp;
+ private TextBox _textBox;
+ private IDisposable _textBoxTextChangedSubscription;
+ private IDisposable _buttonPointerPressedSubscription;
+
+ private DateTime? _onOpenSelectedDate;
+ private bool _settingSelectedDate;
+
+ private DateTime _displayDate;
+ private DateTime? _displayDateStart;
+ private DateTime? _displayDateEnd;
+ private bool _isDropDownOpen;
+ private DateTime? _selectedDate;
+ private string _text;
+ private bool _suspendTextChangeHandler = false;
+ private bool _isPopupClosing = false;
+ private bool _ignoreButtonClick = false;
+
+ ///
+ /// Gets a collection of dates that are marked as not selectable.
+ ///
+ ///
+ /// A collection of dates that cannot be selected. The default value is
+ /// an empty collection.
+ ///
+ public CalendarBlackoutDatesCollection BlackoutDates { get; private set; }
+
+ public static readonly DirectProperty DisplayDateProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(DisplayDate),
+ o => o.DisplayDate,
+ (o, v) => o.DisplayDate = v);
+ public static readonly DirectProperty DisplayDateStartProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(DisplayDateStart),
+ o => o.DisplayDateStart,
+ (o, v) => o.DisplayDateStart = v);
+ public static readonly DirectProperty DisplayDateEndProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(DisplayDateEnd),
+ o => o.DisplayDateEnd,
+ (o, v) => o.DisplayDateEnd = v);
+ public static readonly StyledProperty FirstDayOfWeekProperty =
+ AvaloniaProperty.Register(nameof(FirstDayOfWeek));
+
+ public static readonly DirectProperty IsDropDownOpenProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(IsDropDownOpen),
+ o => o.IsDropDownOpen,
+ (o, v) => o.IsDropDownOpen = v);
+
+ public static readonly StyledProperty IsTodayHighlightedProperty =
+ AvaloniaProperty.Register(nameof(IsTodayHighlighted));
+ public static readonly DirectProperty SelectedDateProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(SelectedDate),
+ o => o.SelectedDate,
+ (o, v) => o.SelectedDate = v);
+
+ public static readonly StyledProperty SelectedDateFormatProperty =
+ AvaloniaProperty.Register(
+ nameof(SelectedDateFormat),
+ defaultValue: DatePickerFormat.Short,
+ validate: ValidateSelectedDateFormat);
+
+ public static readonly StyledProperty CustomDateFormatStringProperty =
+ AvaloniaProperty.Register(
+ nameof(CustomDateFormatString),
+ defaultValue: "d",
+ validate: ValidateDateFormatString);
+
+ public static readonly DirectProperty TextProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(Text),
+ o => o.Text,
+ (o, v) => o.Text = v);
+ public static readonly StyledProperty WatermarkProperty =
+ TextBox.WatermarkProperty.AddOwner();
+ public static readonly StyledProperty UseFloatingWatermarkProperty =
+ TextBox.UseFloatingWatermarkProperty.AddOwner();
+
+
+ ///
+ /// Gets or sets the date to display.
+ ///
+ ///
+ /// The date to display. The default
+ /// .
+ ///
+ ///
+ /// The specified date is not in the range defined by
+ ///
+ /// and
+ /// .
+ ///
+ public DateTime DisplayDate
+ {
+ get { return _displayDate; }
+ set { SetAndRaise(DisplayDateProperty, ref _displayDate, value); }
+ }
+
+ ///
+ /// Gets or sets the first date to be displayed.
+ ///
+ /// The first date to display.
+ public DateTime? DisplayDateStart
+ {
+ get { return _displayDateStart; }
+ set { SetAndRaise(DisplayDateStartProperty, ref _displayDateStart, value); }
+ }
+
+ ///
+ /// Gets or sets the last date to be displayed.
+ ///
+ /// The last date to display.
+ public DateTime? DisplayDateEnd
+ {
+ get { return _displayDateEnd; }
+ set { SetAndRaise(DisplayDateEndProperty, ref _displayDateEnd, value); }
+ }
+
+ ///
+ /// Gets or sets the day that is considered the beginning of the week.
+ ///
+ ///
+ /// A representing the beginning of
+ /// the week. The default is .
+ ///
+ public DayOfWeek FirstDayOfWeek
+ {
+ get { return GetValue(FirstDayOfWeekProperty); }
+ set { SetValue(FirstDayOfWeekProperty, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the drop-down
+ /// is open or closed.
+ ///
+ ///
+ /// True if the is
+ /// open; otherwise, false. The default is false.
+ ///
+ public bool IsDropDownOpen
+ {
+ get { return _isDropDownOpen; }
+ set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the current date will be
+ /// highlighted.
+ ///
+ ///
+ /// True if the current date is highlighted; otherwise, false. The
+ /// default is true.
+ ///
+ public bool IsTodayHighlighted
+ {
+ get { return GetValue(IsTodayHighlightedProperty); }
+ set { SetValue(IsTodayHighlightedProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the currently selected date.
+ ///
+ ///
+ /// The date currently selected. The default is null.
+ ///
+ ///
+ /// The specified date is not in the range defined by
+ ///
+ /// and
+ /// ,
+ /// or the specified date is in the
+ ///
+ /// collection.
+ ///
+ public DateTime? SelectedDate
+ {
+ get { return _selectedDate; }
+ set { SetAndRaise(SelectedDateProperty, ref _selectedDate, value); }
+ }
+
+ ///
+ /// Gets or sets the format that is used to display the selected date.
+ ///
+ ///
+ /// The format that is used to display the selected date. The default is
+ /// .
+ ///
+ ///
+ /// An specified format is not valid.
+ ///
+ public DatePickerFormat SelectedDateFormat
+ {
+ get { return GetValue(SelectedDateFormatProperty); }
+ set { SetValue(SelectedDateFormatProperty, value); }
+ }
+
+ public string CustomDateFormatString
+ {
+ get { return GetValue(CustomDateFormatStringProperty); }
+ set { SetValue(CustomDateFormatStringProperty, value); }
+ }
+
+ ///
+ /// Gets or sets the text that is displayed by the
+ /// .
+ ///
+ ///
+ /// The text displayed by the
+ /// .
+ ///
+ ///
+ /// The text entered cannot be parsed to a valid date, and the exception
+ /// is not suppressed.
+ ///
+ ///
+ /// The text entered parses to a date that is not selectable.
+ ///
+ public string Text
+ {
+ get { return _text; }
+ set { SetAndRaise(TextProperty, ref _text, value); }
+ }
+
+ public string Watermark
+ {
+ get { return GetValue(WatermarkProperty); }
+ set { SetValue(WatermarkProperty, value); }
+ }
+ public bool UseFloatingWatermark
+ {
+ get { return GetValue(UseFloatingWatermarkProperty); }
+ set { SetValue(UseFloatingWatermarkProperty, value); }
+ }
+
+ ///
+ /// Occurs when the drop-down
+ /// is closed.
+ ///
+ public event EventHandler CalendarClosed;
+
+ ///
+ /// Occurs when the drop-down
+ /// is opened.
+ ///
+ public event EventHandler CalendarOpened;
+
+ ///
+ /// Occurs when
+ /// is assigned a value that cannot be interpreted as a date.
+ ///
+ public event EventHandler DateValidationError;
+
+ ///
+ /// Occurs when the
+ ///
+ /// property is changed.
+ ///
+ public event EventHandler SelectedDateChanged;
+
+ static DatePicker()
+ {
+ FocusableProperty.OverrideDefaultValue(true);
+
+ DisplayDateProperty.Changed.AddClassHandler(x => x.OnDisplayDateChanged);
+ DisplayDateStartProperty.Changed.AddClassHandler(x => x.OnDisplayDateStartChanged);
+ DisplayDateEndProperty.Changed.AddClassHandler(x => x.OnDisplayDateEndChanged);
+ IsDropDownOpenProperty.Changed.AddClassHandler(x => x.OnIsDropDownOpenChanged);
+ SelectedDateProperty.Changed.AddClassHandler(x => x.OnSelectedDateChanged);
+ SelectedDateFormatProperty.Changed.AddClassHandler(x => x.OnSelectedDateFormatChanged);
+ CustomDateFormatStringProperty.Changed.AddClassHandler(x => x.OnCustomDateFormatStringChanged);
+ TextProperty.Changed.AddClassHandler(x => x.OnTextChanged);
+ }
+ ///
+ /// Initializes a new instance of the
+ /// class.
+ ///
+ public DatePicker()
+ {
+ FirstDayOfWeek = DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek;
+ _defaultText = string.Empty;
+ DisplayDate = DateTime.Today;
+ }
+
+ protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+ {
+ if (_calendar != null)
+ {
+ _calendar.DayButtonMouseUp -= Calendar_DayButtonMouseUp;
+ _calendar.DisplayDateChanged -= Calendar_DisplayDateChanged;
+ _calendar.SelectedDatesChanged -= Calendar_SelectedDatesChanged;
+ _calendar.PointerPressed -= Calendar_PointerPressed;
+ _calendar.KeyDown -= Calendar_KeyDown;
+ }
+ _calendar = e.NameScope.Find(ElementCalendar);
+ if (_calendar != null)
+ {
+ _calendar.SelectionMode = CalendarSelectionMode.SingleDate;
+ _calendar.SelectedDate = SelectedDate;
+ SetCalendarDisplayDate(DisplayDate);
+ SetCalendarDisplayDateStart(DisplayDateStart);
+ SetCalendarDisplayDateEnd(DisplayDateEnd);
+
+ _calendar.DayButtonMouseUp += Calendar_DayButtonMouseUp;
+ _calendar.DisplayDateChanged += Calendar_DisplayDateChanged;
+ _calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged;
+ _calendar.PointerPressed += Calendar_PointerPressed;
+ _calendar.KeyDown += Calendar_KeyDown;
+ //_calendar.SizeChanged += new SizeChangedEventHandler(Calendar_SizeChanged);
+ //_calendar.IsTabStop = true;
+
+ var currentBlackoutDays = BlackoutDates;
+ BlackoutDates = _calendar.BlackoutDates;
+ if(currentBlackoutDays != null)
+ {
+ foreach (var range in currentBlackoutDays)
+ {
+ BlackoutDates.Add(range);
+ }
+ }
+ }
+
+ if (_popUp != null)
+ {
+ _popUp.Child = null;
+ _popUp.Closed -= PopUp_Closed;
+ }
+ _popUp = e.NameScope.Find(ElementPopup);
+ if(_popUp != null)
+ {
+ _popUp.Closed += PopUp_Closed;
+ if (IsDropDownOpen)
+ {
+ OpenDropDown();
+ }
+ }
+
+ if(_dropDownButton != null)
+ {
+ _dropDownButton.Click -= DropDownButton_Click;
+ _buttonPointerPressedSubscription?.Dispose();
+ }
+ _dropDownButton = e.NameScope.Find