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