A cross-platform UI framework for .NET
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

// (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;
}
}
}
}