csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1187 lines
42 KiB
1187 lines
42 KiB
// (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 System;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using Avalonia.Controls.Primitives;
|
|
using Avalonia.Data;
|
|
using Avalonia.Input;
|
|
using Avalonia.Interactivity;
|
|
|
|
namespace Avalonia.Controls
|
|
{
|
|
/// <summary>
|
|
/// Provides data for the
|
|
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
|
|
/// event.
|
|
/// </summary>
|
|
public class DatePickerDateValidationErrorEventArgs : EventArgs
|
|
{
|
|
private bool _throwException;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the
|
|
/// <see cref="T:Avalonia.Controls.DatePickerDateValidationErrorEventArgs" />
|
|
/// class.
|
|
/// </summary>
|
|
/// <param name="exception">
|
|
/// The initial exception from the
|
|
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
|
|
/// event.
|
|
/// </param>
|
|
/// <param name="text">
|
|
/// The text that caused the
|
|
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
|
|
/// event.
|
|
/// </param>
|
|
public DatePickerDateValidationErrorEventArgs(Exception exception, string text)
|
|
{
|
|
this.Text = text;
|
|
this.Exception = exception;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the initial exception associated with the
|
|
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
|
|
/// event.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The exception associated with the validation failure.
|
|
/// </value>
|
|
public Exception Exception { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets the text that caused the
|
|
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
|
|
/// event.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The text that caused the validation failure.
|
|
/// </value>
|
|
public string Text { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether
|
|
/// <see cref="P:Avalonia.Controls.DatePickerDateValidationErrorEventArgs.Exception" />
|
|
/// should be thrown.
|
|
/// </summary>
|
|
/// <value>
|
|
/// True if the exception should be thrown; otherwise, false.
|
|
/// </value>
|
|
/// <exception cref="T:System.ArgumentException">
|
|
/// If set to true and
|
|
/// <see cref="P:Avalonia.Controls.DatePickerDateValidationErrorEventArgs.Exception" />
|
|
/// is null.
|
|
/// </exception>
|
|
public bool ThrowException
|
|
{
|
|
get { return this._throwException; }
|
|
set
|
|
{
|
|
if (value && this.Exception == null)
|
|
{
|
|
throw new ArgumentException("Cannot Throw Null Exception");
|
|
}
|
|
this._throwException = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Specifies date formats for a
|
|
/// <see cref="T:Avalonia.Controls.DatePicker" />.
|
|
/// </summary>
|
|
public enum DatePickerFormat
|
|
{
|
|
/// <summary>
|
|
/// Specifies that the date should be displayed using unabbreviated days
|
|
/// of the week and month names.
|
|
/// </summary>
|
|
Long = 0,
|
|
|
|
/// <summary>
|
|
/// Specifies that the date should be displayed using abbreviated days
|
|
/// of the week and month names.
|
|
/// </summary>
|
|
Short = 1,
|
|
|
|
/// <summary>
|
|
/// Specifies that the date should be displayed using a custom format string.
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Gets a collection of dates that are marked as not selectable.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A collection of dates that cannot be selected. The default value is
|
|
/// an empty collection.
|
|
/// </value>
|
|
public CalendarBlackoutDatesCollection BlackoutDates { get; private set; }
|
|
|
|
public static readonly DirectProperty<DatePicker, DateTime> DisplayDateProperty =
|
|
AvaloniaProperty.RegisterDirect<DatePicker, DateTime>(
|
|
nameof(DisplayDate),
|
|
o => o.DisplayDate,
|
|
(o, v) => o.DisplayDate = v);
|
|
public static readonly DirectProperty<DatePicker, DateTime?> DisplayDateStartProperty =
|
|
AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>(
|
|
nameof(DisplayDateStart),
|
|
o => o.DisplayDateStart,
|
|
(o, v) => o.DisplayDateStart = v);
|
|
public static readonly DirectProperty<DatePicker, DateTime?> DisplayDateEndProperty =
|
|
AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>(
|
|
nameof(DisplayDateEnd),
|
|
o => o.DisplayDateEnd,
|
|
(o, v) => o.DisplayDateEnd = v);
|
|
public static readonly StyledProperty<DayOfWeek> FirstDayOfWeekProperty =
|
|
AvaloniaProperty.Register<DatePicker, DayOfWeek>(nameof(FirstDayOfWeek));
|
|
|
|
public static readonly DirectProperty<DatePicker, bool> IsDropDownOpenProperty =
|
|
AvaloniaProperty.RegisterDirect<DatePicker, bool>(
|
|
nameof(IsDropDownOpen),
|
|
o => o.IsDropDownOpen,
|
|
(o, v) => o.IsDropDownOpen = v);
|
|
|
|
public static readonly StyledProperty<bool> IsTodayHighlightedProperty =
|
|
AvaloniaProperty.Register<DatePicker, bool>(nameof(IsTodayHighlighted));
|
|
public static readonly DirectProperty<DatePicker, DateTime?> SelectedDateProperty =
|
|
AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>(
|
|
nameof(SelectedDate),
|
|
o => o.SelectedDate,
|
|
(o, v) => o.SelectedDate = v);
|
|
|
|
public static readonly StyledProperty<DatePickerFormat> SelectedDateFormatProperty =
|
|
AvaloniaProperty.Register<DatePicker, DatePickerFormat>(
|
|
nameof(SelectedDateFormat),
|
|
defaultValue: DatePickerFormat.Short,
|
|
validate: ValidateSelectedDateFormat);
|
|
|
|
public static readonly StyledProperty<string> CustomDateFormatStringProperty =
|
|
AvaloniaProperty.Register<DatePicker, string>(
|
|
nameof(CustomDateFormatString),
|
|
defaultValue: "d",
|
|
validate: ValidateDateFormatString);
|
|
|
|
public static readonly DirectProperty<DatePicker, string> TextProperty =
|
|
AvaloniaProperty.RegisterDirect<DatePicker, string>(
|
|
nameof(Text),
|
|
o => o.Text,
|
|
(o, v) => o.Text = v);
|
|
public static readonly StyledProperty<string> WatermarkProperty =
|
|
TextBox.WatermarkProperty.AddOwner<DatePicker>();
|
|
public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
|
|
TextBox.UseFloatingWatermarkProperty.AddOwner<DatePicker>();
|
|
|
|
|
|
/// <summary>
|
|
/// Gets or sets the date to display.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The date to display. The default
|
|
/// <see cref="P:System.DateTime.Today" />.
|
|
/// </value>
|
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
|
/// The specified date is not in the range defined by
|
|
/// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateStart" />
|
|
/// and
|
|
/// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateEnd" />.
|
|
/// </exception>
|
|
public DateTime DisplayDate
|
|
{
|
|
get { return _displayDate; }
|
|
set { SetAndRaise(DisplayDateProperty, ref _displayDate, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the first date to be displayed.
|
|
/// </summary>
|
|
/// <value>The first date to display.</value>
|
|
public DateTime? DisplayDateStart
|
|
{
|
|
get { return _displayDateStart; }
|
|
set { SetAndRaise(DisplayDateStartProperty, ref _displayDateStart, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the last date to be displayed.
|
|
/// </summary>
|
|
/// <value>The last date to display.</value>
|
|
public DateTime? DisplayDateEnd
|
|
{
|
|
get { return _displayDateEnd; }
|
|
set { SetAndRaise(DisplayDateEndProperty, ref _displayDateEnd, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the day that is considered the beginning of the week.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="T:System.DayOfWeek" /> representing the beginning of
|
|
/// the week. The default is <see cref="F:System.DayOfWeek.Sunday" />.
|
|
/// </value>
|
|
public DayOfWeek FirstDayOfWeek
|
|
{
|
|
get { return GetValue(FirstDayOfWeekProperty); }
|
|
set { SetValue(FirstDayOfWeekProperty, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the drop-down
|
|
/// <see cref="T:Avalonia.Controls.Calendar" /> is open or closed.
|
|
/// </summary>
|
|
/// <value>
|
|
/// True if the <see cref="T:Avalonia.Controls.Calendar" /> is
|
|
/// open; otherwise, false. The default is false.
|
|
/// </value>
|
|
public bool IsDropDownOpen
|
|
{
|
|
get { return _isDropDownOpen; }
|
|
set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether the current date will be
|
|
/// highlighted.
|
|
/// </summary>
|
|
/// <value>
|
|
/// True if the current date is highlighted; otherwise, false. The
|
|
/// default is true.
|
|
/// </value>
|
|
public bool IsTodayHighlighted
|
|
{
|
|
get { return GetValue(IsTodayHighlightedProperty); }
|
|
set { SetValue(IsTodayHighlightedProperty, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the currently selected date.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The date currently selected. The default is null.
|
|
/// </value>
|
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
|
/// The specified date is not in the range defined by
|
|
/// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateStart" />
|
|
/// and
|
|
/// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateEnd" />,
|
|
/// or the specified date is in the
|
|
/// <see cref="P:Avalonia.Controls.DatePicker.BlackoutDates" />
|
|
/// collection.
|
|
/// </exception>
|
|
public DateTime? SelectedDate
|
|
{
|
|
get { return _selectedDate; }
|
|
set { SetAndRaise(SelectedDateProperty, ref _selectedDate, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the format that is used to display the selected date.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The format that is used to display the selected date. The default is
|
|
/// <see cref="F:Avalonia.Controls.DatePickerFormat.Short" />.
|
|
/// </value>
|
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
|
/// An specified format is not valid.
|
|
/// </exception>
|
|
public DatePickerFormat SelectedDateFormat
|
|
{
|
|
get { return GetValue(SelectedDateFormatProperty); }
|
|
set { SetValue(SelectedDateFormatProperty, value); }
|
|
}
|
|
|
|
public string CustomDateFormatString
|
|
{
|
|
get { return GetValue(CustomDateFormatStringProperty); }
|
|
set { SetValue(CustomDateFormatStringProperty, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the text that is displayed by the
|
|
/// <see cref="T:Avalonia.Controls.DatePicker" />.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The text displayed by the
|
|
/// <see cref="T:Avalonia.Controls.DatePicker" />.
|
|
/// </value>
|
|
/// <exception cref="T:System.FormatException">
|
|
/// The text entered cannot be parsed to a valid date, and the exception
|
|
/// is not suppressed.
|
|
/// </exception>
|
|
/// <exception cref="T:System.ArgumentOutOfRangeException">
|
|
/// The text entered parses to a date that is not selectable.
|
|
/// </exception>
|
|
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); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when the drop-down
|
|
/// <see cref="T:Avalonia.Controls.Calendar" /> is closed.
|
|
/// </summary>
|
|
public event EventHandler CalendarClosed;
|
|
|
|
/// <summary>
|
|
/// Occurs when the drop-down
|
|
/// <see cref="T:Avalonia.Controls.Calendar" /> is opened.
|
|
/// </summary>
|
|
public event EventHandler CalendarOpened;
|
|
|
|
/// <summary>
|
|
/// Occurs when <see cref="P:Avalonia.Controls.DatePicker.Text" />
|
|
/// is assigned a value that cannot be interpreted as a date.
|
|
/// </summary>
|
|
public event EventHandler<DatePickerDateValidationErrorEventArgs> DateValidationError;
|
|
|
|
/// <summary>
|
|
/// Occurs when the
|
|
/// <see cref="P:Avalonia.Controls.DatePicker.SelectedDate" />
|
|
/// property is changed.
|
|
/// </summary>
|
|
public event EventHandler<SelectionChangedEventArgs> SelectedDateChanged;
|
|
|
|
static DatePicker()
|
|
{
|
|
FocusableProperty.OverrideDefaultValue<DatePicker>(true);
|
|
|
|
DisplayDateProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateChanged);
|
|
DisplayDateStartProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateStartChanged);
|
|
DisplayDateEndProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateEndChanged);
|
|
IsDropDownOpenProperty.Changed.AddClassHandler<DatePicker>(x => x.OnIsDropDownOpenChanged);
|
|
SelectedDateProperty.Changed.AddClassHandler<DatePicker>(x => x.OnSelectedDateChanged);
|
|
SelectedDateFormatProperty.Changed.AddClassHandler<DatePicker>(x => x.OnSelectedDateFormatChanged);
|
|
CustomDateFormatStringProperty.Changed.AddClassHandler<DatePicker>(x => x.OnCustomDateFormatStringChanged);
|
|
TextProperty.Changed.AddClassHandler<DatePicker>(x => x.OnTextChanged);
|
|
}
|
|
/// <summary>
|
|
/// Initializes a new instance of the
|
|
/// <see cref="T:Avalonia.Controls.DatePicker" /> class.
|
|
/// </summary>
|
|
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<Calendar>(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<Popup>(ElementPopup);
|
|
if(_popUp != null)
|
|
{
|
|
_popUp.Closed += PopUp_Closed;
|
|
if (IsDropDownOpen)
|
|
{
|
|
OpenDropDown();
|
|
}
|
|
}
|
|
|
|
if(_dropDownButton != null)
|
|
{
|
|
_dropDownButton.Click -= DropDownButton_Click;
|
|
_buttonPointerPressedSubscription?.Dispose();
|
|
}
|
|
_dropDownButton = e.NameScope.Find<Button>(ElementButton);
|
|
if(_dropDownButton != null)
|
|
{
|
|
_dropDownButton.Click += DropDownButton_Click;
|
|
_buttonPointerPressedSubscription =
|
|
_dropDownButton.AddHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true);
|
|
}
|
|
|
|
if (_textBox != null)
|
|
{
|
|
_textBox.KeyDown -= TextBox_KeyDown;
|
|
_textBox.GotFocus -= TextBox_GotFocus;
|
|
_textBoxTextChangedSubscription?.Dispose();
|
|
}
|
|
_textBox = e.NameScope.Find<TextBox>(ElementTextBox);
|
|
|
|
if(!SelectedDate.HasValue)
|
|
{
|
|
SetWaterMarkText();
|
|
}
|
|
|
|
if(_textBox != null)
|
|
{
|
|
_textBox.KeyDown += TextBox_KeyDown;
|
|
_textBox.GotFocus += TextBox_GotFocus;
|
|
_textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBox_TextChanged());
|
|
|
|
if(SelectedDate.HasValue)
|
|
{
|
|
_textBox.Text = DateTimeToString(SelectedDate.Value);
|
|
}
|
|
else if(!String.IsNullOrEmpty(_defaultText))
|
|
{
|
|
_textBox.Text = _defaultText;
|
|
SetSelectedDate();
|
|
}
|
|
}
|
|
|
|
base.OnTemplateApplied(e);
|
|
}
|
|
|
|
protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status)
|
|
{
|
|
if (property == SelectedDateProperty)
|
|
{
|
|
DataValidationErrors.SetError(this, status.Error);
|
|
}
|
|
}
|
|
|
|
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
|
{
|
|
base.OnPointerWheelChanged(e);
|
|
if (!e.Handled && SelectedDate.HasValue && _calendar != null)
|
|
{
|
|
DateTime selectedDate = this.SelectedDate.Value;
|
|
DateTime? newDate = DateTimeHelper.AddDays(selectedDate, e.Delta.Y > 0 ? -1 : 1);
|
|
if (newDate.HasValue && Calendar.IsValidDateSelection(_calendar, newDate.Value))
|
|
{
|
|
SelectedDate = newDate;
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
}
|
|
protected override void OnGotFocus(GotFocusEventArgs e)
|
|
{
|
|
base.OnGotFocus(e);
|
|
if(IsEnabled && _textBox != null && e.NavigationMethod == NavigationMethod.Tab)
|
|
{
|
|
_textBox.Focus();
|
|
var text = _textBox.Text;
|
|
if(!string.IsNullOrEmpty(text))
|
|
{
|
|
_textBox.SelectionStart = 0;
|
|
_textBox.SelectionEnd = text.Length;
|
|
}
|
|
}
|
|
}
|
|
protected override void OnLostFocus(RoutedEventArgs e)
|
|
{
|
|
base.OnLostFocus(e);
|
|
|
|
SetSelectedDate();
|
|
}
|
|
|
|
private void SetCalendarDisplayDate(DateTime value)
|
|
{
|
|
if (DateTimeHelper.CompareYearMonth(_calendar.DisplayDate, value) != 0)
|
|
{
|
|
_calendar.DisplayDate = DisplayDate;
|
|
if (DateTime.Compare(_calendar.DisplayDate, DisplayDate) != 0)
|
|
{
|
|
DisplayDate = _calendar.DisplayDate;
|
|
}
|
|
}
|
|
}
|
|
private void OnDisplayDateChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
if (_calendar != null)
|
|
{
|
|
var value = (DateTime)e.NewValue;
|
|
SetCalendarDisplayDate(value);
|
|
}
|
|
}
|
|
private void SetCalendarDisplayDateStart(DateTime? value)
|
|
{
|
|
_calendar.DisplayDateStart = value;
|
|
if (_calendar.DisplayDateStart.HasValue && DisplayDateStart.HasValue && DateTime.Compare(_calendar.DisplayDateStart.Value, DisplayDateStart.Value) != 0)
|
|
{
|
|
DisplayDateStart = _calendar.DisplayDateStart;
|
|
}
|
|
}
|
|
private void OnDisplayDateStartChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
if (_calendar != null)
|
|
{
|
|
var value = (DateTime?)e.NewValue;
|
|
SetCalendarDisplayDateStart(value);
|
|
}
|
|
}
|
|
private void SetCalendarDisplayDateEnd(DateTime? value)
|
|
{
|
|
_calendar.DisplayDateEnd = value;
|
|
if (_calendar.DisplayDateEnd.HasValue && DisplayDateEnd.HasValue && DateTime.Compare(_calendar.DisplayDateEnd.Value, DisplayDateEnd.Value) != 0)
|
|
{
|
|
DisplayDateEnd = _calendar.DisplayDateEnd;
|
|
}
|
|
|
|
}
|
|
private void OnDisplayDateEndChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
if (_calendar != null)
|
|
{
|
|
var value = (DateTime?)e.NewValue;
|
|
SetCalendarDisplayDateEnd(value);
|
|
}
|
|
}
|
|
private void OnIsDropDownOpenChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
var oldValue = (bool)e.OldValue;
|
|
var value = (bool)e.NewValue;
|
|
|
|
if (_popUp != null && _popUp.Child != null)
|
|
{
|
|
if (value != oldValue)
|
|
{
|
|
if (_calendar.DisplayMode != CalendarMode.Month)
|
|
{
|
|
_calendar.DisplayMode = CalendarMode.Month;
|
|
}
|
|
|
|
if (value)
|
|
{
|
|
OpenDropDown();
|
|
}
|
|
else
|
|
{
|
|
_popUp.IsOpen = false;
|
|
OnCalendarClosed(new RoutedEventArgs());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
private void OnSelectedDateChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
var addedDate = (DateTime?)e.NewValue;
|
|
var removedDate = (DateTime?)e.OldValue;
|
|
|
|
if (_calendar != null && addedDate != _calendar.SelectedDate)
|
|
{
|
|
_calendar.SelectedDate = addedDate;
|
|
}
|
|
|
|
if (SelectedDate != null)
|
|
{
|
|
DateTime day = SelectedDate.Value;
|
|
|
|
// When the SelectedDateProperty change is done from
|
|
// OnTextPropertyChanged method, two-way binding breaks if
|
|
// BeginInvoke is not used:
|
|
Threading.Dispatcher.UIThread.InvokeAsync(() =>
|
|
{
|
|
_settingSelectedDate = true;
|
|
Text = DateTimeToString(day);
|
|
_settingSelectedDate = false;
|
|
OnDateSelected(addedDate, removedDate);
|
|
});
|
|
|
|
// When DatePickerDisplayDateFlag is TRUE, the SelectedDate
|
|
// change is coming from the Calendar UI itself, so, we
|
|
// shouldn't change the DisplayDate since it will automatically
|
|
// be changed by the Calendar
|
|
if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.DatePickerDisplayDateFlag))
|
|
{
|
|
DisplayDate = day;
|
|
}
|
|
if(_calendar != null)
|
|
_calendar.DatePickerDisplayDateFlag = false;
|
|
}
|
|
else
|
|
{
|
|
_settingSelectedDate = true;
|
|
SetWaterMarkText();
|
|
_settingSelectedDate = false;
|
|
OnDateSelected(addedDate, removedDate);
|
|
}
|
|
}
|
|
private void OnDateFormatChanged()
|
|
{
|
|
if (_textBox != null)
|
|
{
|
|
if (SelectedDate.HasValue)
|
|
{
|
|
Text = DateTimeToString(SelectedDate.Value);
|
|
}
|
|
else if (string.IsNullOrEmpty(_textBox.Text))
|
|
{
|
|
SetWaterMarkText();
|
|
}
|
|
else
|
|
{
|
|
DateTime? date = ParseText(_textBox.Text);
|
|
|
|
if (date != null)
|
|
{
|
|
string s = DateTimeToString((DateTime)date);
|
|
Text = s;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
private void OnSelectedDateFormatChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
OnDateFormatChanged();
|
|
}
|
|
private void OnCustomDateFormatStringChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
if(SelectedDateFormat == DatePickerFormat.Custom)
|
|
{
|
|
OnDateFormatChanged();
|
|
}
|
|
}
|
|
private void OnTextChanged(AvaloniaPropertyChangedEventArgs e)
|
|
{
|
|
var oldValue = (string)e.OldValue;
|
|
var value = (string)e.NewValue;
|
|
|
|
if (!_suspendTextChangeHandler)
|
|
{
|
|
if (value != null)
|
|
{
|
|
if (_textBox != null)
|
|
{
|
|
_textBox.Text = value;
|
|
}
|
|
else
|
|
{
|
|
_defaultText = value;
|
|
}
|
|
if (!_settingSelectedDate)
|
|
{
|
|
SetSelectedDate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!_settingSelectedDate)
|
|
{
|
|
_settingSelectedDate = true;
|
|
SelectedDate = null;
|
|
_settingSelectedDate = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetWaterMarkText();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the
|
|
/// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
|
|
/// event.
|
|
/// </summary>
|
|
/// <param name="e">
|
|
/// A
|
|
/// <see cref="T:Avalonia.Controls.DatePickerDateValidationErrorEventArgs" />
|
|
/// that contains the event data.
|
|
/// </param>
|
|
protected virtual void OnDateValidationError(DatePickerDateValidationErrorEventArgs e)
|
|
{
|
|
DateValidationError?.Invoke(this, e);
|
|
}
|
|
private void OnDateSelected(DateTime? addedDate, DateTime? removedDate)
|
|
{
|
|
EventHandler<SelectionChangedEventArgs> handler = this.SelectedDateChanged;
|
|
if (null != handler)
|
|
{
|
|
Collection<DateTime> addedItems = new Collection<DateTime>();
|
|
Collection<DateTime> removedItems = new Collection<DateTime>();
|
|
|
|
if (addedDate.HasValue)
|
|
{
|
|
addedItems.Add(addedDate.Value);
|
|
}
|
|
|
|
if (removedDate.HasValue)
|
|
{
|
|
removedItems.Add(removedDate.Value);
|
|
}
|
|
|
|
handler(this, new SelectionChangedEventArgs(SelectingItemsControl.SelectionChangedEvent, addedItems, removedItems));
|
|
}
|
|
}
|
|
private void OnCalendarClosed(EventArgs e)
|
|
{
|
|
CalendarClosed?.Invoke(this, e);
|
|
}
|
|
private void OnCalendarOpened(EventArgs e)
|
|
{
|
|
CalendarOpened?.Invoke(this, e);
|
|
}
|
|
|
|
private void Calendar_DayButtonMouseUp(object sender, PointerReleasedEventArgs e)
|
|
{
|
|
Focus();
|
|
IsDropDownOpen = false;
|
|
}
|
|
private void Calendar_DisplayDateChanged(object sender, CalendarDateChangedEventArgs e)
|
|
{
|
|
if (e.AddedDate != this.DisplayDate)
|
|
{
|
|
SetValue(DisplayDateProperty, (DateTime) e.AddedDate);
|
|
}
|
|
}
|
|
private void Calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
Debug.Assert(e.AddedItems.Count < 2, "There should be less than 2 AddedItems!");
|
|
|
|
if (e.AddedItems.Count > 0 && SelectedDate.HasValue && DateTime.Compare((DateTime)e.AddedItems[0], SelectedDate.Value) != 0)
|
|
{
|
|
SelectedDate = (DateTime?)e.AddedItems[0];
|
|
}
|
|
else
|
|
{
|
|
if (e.AddedItems.Count == 0)
|
|
{
|
|
SelectedDate = null;
|
|
return;
|
|
}
|
|
|
|
if (!SelectedDate.HasValue)
|
|
{
|
|
if (e.AddedItems.Count > 0)
|
|
{
|
|
SelectedDate = (DateTime?)e.AddedItems[0];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
private void Calendar_PointerPressed(object sender, PointerPressedEventArgs e)
|
|
{
|
|
if (e.MouseButton == MouseButton.Left)
|
|
{
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
private void Calendar_KeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
Calendar c = sender as Calendar;
|
|
Contract.Requires<ArgumentNullException>(c != null);
|
|
|
|
if (!e.Handled && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape) && c.DisplayMode == CalendarMode.Month)
|
|
{
|
|
Focus();
|
|
IsDropDownOpen = false;
|
|
|
|
if (e.Key == Key.Escape)
|
|
{
|
|
SelectedDate = _onOpenSelectedDate;
|
|
}
|
|
}
|
|
}
|
|
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
|
|
{
|
|
IsDropDownOpen = false;
|
|
}
|
|
private void TextBox_KeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
if (!e.Handled)
|
|
{
|
|
e.Handled = ProcessDatePickerKey(e);
|
|
}
|
|
}
|
|
private void TextBox_TextChanged()
|
|
{
|
|
if (_textBox != null)
|
|
{
|
|
_suspendTextChangeHandler = true;
|
|
Text = _textBox.Text;
|
|
_suspendTextChangeHandler = false;
|
|
}
|
|
}
|
|
private void DropDownButton_PointerPressed(object sender, PointerPressedEventArgs e)
|
|
{
|
|
_ignoreButtonClick = _isPopupClosing;
|
|
}
|
|
private void DropDownButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
if (!_ignoreButtonClick)
|
|
{
|
|
HandlePopUp();
|
|
}
|
|
else
|
|
{
|
|
_ignoreButtonClick = false;
|
|
}
|
|
}
|
|
private void PopUp_Closed(object sender, EventArgs e)
|
|
{
|
|
IsDropDownOpen = false;
|
|
|
|
if(!_isPopupClosing)
|
|
{
|
|
_isPopupClosing = true;
|
|
Threading.Dispatcher.UIThread.InvokeAsync(() => _isPopupClosing = false);
|
|
}
|
|
}
|
|
|
|
private void HandlePopUp()
|
|
{
|
|
if (IsDropDownOpen)
|
|
{
|
|
Focus();
|
|
IsDropDownOpen = false;
|
|
}
|
|
else
|
|
{
|
|
ProcessTextBox();
|
|
}
|
|
}
|
|
private void OpenDropDown()
|
|
{
|
|
if (_calendar != null)
|
|
{
|
|
_calendar.Focus();
|
|
OpenPopUp();
|
|
_calendar.ResetStates();
|
|
OnCalendarOpened(new RoutedEventArgs());
|
|
}
|
|
}
|
|
private void OpenPopUp()
|
|
{
|
|
_onOpenSelectedDate = SelectedDate;
|
|
_popUp.IsOpen = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Input text is parsed in the correct format and changed into a
|
|
/// DateTime object. If the text can not be parsed TextParseError Event
|
|
/// is thrown.
|
|
/// </summary>
|
|
/// <param name="text">Inherited code: Requires comment.</param>
|
|
/// <returns>
|
|
/// IT SHOULD RETURN NULL IF THE STRING IS NOT VALID, RETURN THE
|
|
/// DATETIME VALUE IF IT IS VALID.
|
|
/// </returns>
|
|
private DateTime? ParseText(string text)
|
|
{
|
|
DateTime newSelectedDate;
|
|
|
|
// TryParse is not used in order to be able to pass the exception to
|
|
// the TextParseError event
|
|
try
|
|
{
|
|
newSelectedDate = DateTime.Parse(text, DateTimeHelper.GetCurrentDateFormat());
|
|
|
|
if (Calendar.IsValidDateSelection(this._calendar, newSelectedDate))
|
|
{
|
|
return newSelectedDate;
|
|
}
|
|
else
|
|
{
|
|
var dateValidationError = new DatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException("text", "SelectedDate value is not valid."), text);
|
|
OnDateValidationError(dateValidationError);
|
|
|
|
if (dateValidationError.ThrowException)
|
|
{
|
|
throw dateValidationError.Exception;
|
|
}
|
|
}
|
|
}
|
|
catch (FormatException ex)
|
|
{
|
|
DatePickerDateValidationErrorEventArgs textParseError = new DatePickerDateValidationErrorEventArgs(ex, text);
|
|
OnDateValidationError(textParseError);
|
|
|
|
if (textParseError.ThrowException)
|
|
{
|
|
throw textParseError.Exception;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
private string DateTimeToString(DateTime d)
|
|
{
|
|
DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
|
|
|
|
switch (SelectedDateFormat)
|
|
{
|
|
case DatePickerFormat.Short:
|
|
return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.ShortDatePattern, dtfi));
|
|
case DatePickerFormat.Long:
|
|
return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.LongDatePattern, dtfi));
|
|
case DatePickerFormat.Custom:
|
|
return string.Format(CultureInfo.CurrentCulture, d.ToString(CustomDateFormatString, dtfi));
|
|
}
|
|
return null;
|
|
}
|
|
private bool ProcessDatePickerKey(KeyEventArgs e)
|
|
{
|
|
|
|
switch (e.Key)
|
|
{
|
|
case Key.Enter:
|
|
{
|
|
SetSelectedDate();
|
|
return true;
|
|
}
|
|
case Key.Down:
|
|
{
|
|
if ((e.Modifiers & InputModifiers.Control) == InputModifiers.Control)
|
|
{
|
|
HandlePopUp();
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
private void ProcessTextBox()
|
|
{
|
|
SetSelectedDate();
|
|
IsDropDownOpen = true;
|
|
_calendar.Focus();
|
|
}
|
|
private void SetSelectedDate()
|
|
{
|
|
if (_textBox != null)
|
|
{
|
|
if (!string.IsNullOrEmpty(_textBox.Text))
|
|
{
|
|
string s = _textBox.Text;
|
|
|
|
if (SelectedDate != null)
|
|
{
|
|
// If the string value of the SelectedDate and the
|
|
// TextBox string value are equal, we do not parse the
|
|
// string again if we do an extra parse, we lose data in
|
|
// M/d/yy format.
|
|
// ex: SelectedDate = DateTime(1008,12,19) but when
|
|
// "12/19/08" is parsed it is interpreted as
|
|
// DateTime(2008,12,19)
|
|
string selectedDate = DateTimeToString(SelectedDate.Value);
|
|
if (selectedDate == s)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
DateTime? d = SetTextBoxValue(s);
|
|
if (!SelectedDate.Equals(d))
|
|
{
|
|
SelectedDate = d;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (SelectedDate != null)
|
|
{
|
|
SelectedDate = null;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DateTime? d = SetTextBoxValue(_defaultText);
|
|
if (!SelectedDate.Equals(d))
|
|
{
|
|
SelectedDate = d;
|
|
}
|
|
}
|
|
}
|
|
private DateTime? SetTextBoxValue(string s)
|
|
{
|
|
if (string.IsNullOrEmpty(s))
|
|
{
|
|
SetValue(TextProperty, s);
|
|
return SelectedDate;
|
|
}
|
|
else
|
|
{
|
|
DateTime? d = ParseText(s);
|
|
if (d != null)
|
|
{
|
|
SetValue(TextProperty, s);
|
|
return d;
|
|
}
|
|
else
|
|
{
|
|
// If parse error: TextBox should have the latest valid
|
|
// SelectedDate value:
|
|
if (SelectedDate != null)
|
|
{
|
|
string newtext = this.DateTimeToString(SelectedDate.Value);
|
|
SetValue(TextProperty, newtext);
|
|
return SelectedDate;
|
|
}
|
|
else
|
|
{
|
|
SetWaterMarkText();
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
private void SetWaterMarkText()
|
|
{
|
|
if (_textBox != null)
|
|
{
|
|
if (string.IsNullOrEmpty(Watermark) && !UseFloatingWatermark)
|
|
{
|
|
DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
|
|
Text = string.Empty;
|
|
_defaultText = string.Empty;
|
|
var watermarkFormat = "<{0}>";
|
|
string watermarkText;
|
|
|
|
switch (SelectedDateFormat)
|
|
{
|
|
case DatePickerFormat.Long:
|
|
{
|
|
watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.LongDatePattern.ToString());
|
|
break;
|
|
}
|
|
case DatePickerFormat.Short:
|
|
default:
|
|
{
|
|
watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.ShortDatePattern.ToString());
|
|
break;
|
|
}
|
|
}
|
|
_textBox.Watermark = watermarkText;
|
|
}
|
|
else
|
|
{
|
|
_textBox.ClearValue(TextBox.WatermarkProperty);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool IsValidSelectedDateFormat(DatePickerFormat value)
|
|
{
|
|
return value == DatePickerFormat.Long
|
|
|| value == DatePickerFormat.Short
|
|
|| value == DatePickerFormat.Custom;
|
|
}
|
|
private static DatePickerFormat ValidateSelectedDateFormat(DatePicker dp, DatePickerFormat format)
|
|
{
|
|
if(IsValidSelectedDateFormat(format))
|
|
{
|
|
return format;
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(format), "DatePickerFormat value is not valid.");
|
|
}
|
|
}
|
|
private static string ValidateDateFormatString(DatePicker dp, string formatString)
|
|
{
|
|
if(string.IsNullOrWhiteSpace(formatString))
|
|
{
|
|
throw new ArgumentException("DateFormatString value is not valid.", nameof(formatString));
|
|
}
|
|
else
|
|
{
|
|
return formatString;
|
|
}
|
|
}
|
|
private static DateTime DiscardDayTime(DateTime d)
|
|
{
|
|
int year = d.Year;
|
|
int month = d.Month;
|
|
DateTime newD = new DateTime(year, month, 1, 0, 0, 0);
|
|
return newD;
|
|
}
|
|
private static DateTime? DiscardTime(DateTime? d)
|
|
{
|
|
if (d == null)
|
|
{
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
DateTime discarded = (DateTime) d;
|
|
int year = discarded.Year;
|
|
int month = discarded.Month;
|
|
int day = discarded.Day;
|
|
DateTime newD = new DateTime(year, month, day, 0, 0, 0);
|
|
return newD;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|