DatePicker.cs 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  1. // (c) Copyright Microsoft Corporation.
  2. // This source is subject to the Microsoft Public License (Ms-PL).
  3. // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  4. // All other rights reserved.
  5. using System;
  6. using System.Collections.ObjectModel;
  7. using System.Diagnostics;
  8. using System.Globalization;
  9. using Avalonia.Controls.Primitives;
  10. using Avalonia.Data;
  11. using Avalonia.Input;
  12. using Avalonia.Interactivity;
  13. namespace Avalonia.Controls
  14. {
  15. /// <summary>
  16. /// Provides data for the
  17. /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
  18. /// event.
  19. /// </summary>
  20. public class DatePickerDateValidationErrorEventArgs : EventArgs
  21. {
  22. private bool _throwException;
  23. /// <summary>
  24. /// Initializes a new instance of the
  25. /// <see cref="T:Avalonia.Controls.DatePickerDateValidationErrorEventArgs" />
  26. /// class.
  27. /// </summary>
  28. /// <param name="exception">
  29. /// The initial exception from the
  30. /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
  31. /// event.
  32. /// </param>
  33. /// <param name="text">
  34. /// The text that caused the
  35. /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
  36. /// event.
  37. /// </param>
  38. public DatePickerDateValidationErrorEventArgs(Exception exception, string text)
  39. {
  40. this.Text = text;
  41. this.Exception = exception;
  42. }
  43. /// <summary>
  44. /// Gets the initial exception associated with the
  45. /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
  46. /// event.
  47. /// </summary>
  48. /// <value>
  49. /// The exception associated with the validation failure.
  50. /// </value>
  51. public Exception Exception { get; private set; }
  52. /// <summary>
  53. /// Gets the text that caused the
  54. /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
  55. /// event.
  56. /// </summary>
  57. /// <value>
  58. /// The text that caused the validation failure.
  59. /// </value>
  60. public string Text { get; private set; }
  61. /// <summary>
  62. /// Gets or sets a value indicating whether
  63. /// <see cref="P:Avalonia.Controls.DatePickerDateValidationErrorEventArgs.Exception" />
  64. /// should be thrown.
  65. /// </summary>
  66. /// <value>
  67. /// True if the exception should be thrown; otherwise, false.
  68. /// </value>
  69. /// <exception cref="T:System.ArgumentException">
  70. /// If set to true and
  71. /// <see cref="P:Avalonia.Controls.DatePickerDateValidationErrorEventArgs.Exception" />
  72. /// is null.
  73. /// </exception>
  74. public bool ThrowException
  75. {
  76. get { return this._throwException; }
  77. set
  78. {
  79. if (value && this.Exception == null)
  80. {
  81. throw new ArgumentException("Cannot Throw Null Exception");
  82. }
  83. this._throwException = value;
  84. }
  85. }
  86. }
  87. /// <summary>
  88. /// Specifies date formats for a
  89. /// <see cref="T:Avalonia.Controls.DatePicker" />.
  90. /// </summary>
  91. public enum DatePickerFormat
  92. {
  93. /// <summary>
  94. /// Specifies that the date should be displayed using unabbreviated days
  95. /// of the week and month names.
  96. /// </summary>
  97. Long = 0,
  98. /// <summary>
  99. /// Specifies that the date should be displayed using abbreviated days
  100. /// of the week and month names.
  101. /// </summary>
  102. Short = 1,
  103. /// <summary>
  104. /// Specifies that the date should be displayed using a custom format string.
  105. /// </summary>
  106. Custom = 2
  107. }
  108. public class DatePicker : TemplatedControl
  109. {
  110. private const string ElementTextBox = "PART_TextBox";
  111. private const string ElementButton = "PART_Button";
  112. private const string ElementPopup = "PART_Popup";
  113. private const string ElementCalendar = "PART_Calendar";
  114. private Calendar _calendar;
  115. private string _defaultText;
  116. private Button _dropDownButton;
  117. //private Canvas _outsideCanvas;
  118. //private Canvas _outsidePopupCanvas;
  119. private Popup _popUp;
  120. private TextBox _textBox;
  121. private IDisposable _textBoxTextChangedSubscription;
  122. private IDisposable _buttonPointerPressedSubscription;
  123. private DateTime? _onOpenSelectedDate;
  124. private bool _settingSelectedDate;
  125. private DateTime _displayDate;
  126. private DateTime? _displayDateStart;
  127. private DateTime? _displayDateEnd;
  128. private bool _isDropDownOpen;
  129. private DateTime? _selectedDate;
  130. private string _text;
  131. private bool _suspendTextChangeHandler = false;
  132. private bool _isPopupClosing = false;
  133. private bool _ignoreButtonClick = false;
  134. /// <summary>
  135. /// Gets a collection of dates that are marked as not selectable.
  136. /// </summary>
  137. /// <value>
  138. /// A collection of dates that cannot be selected. The default value is
  139. /// an empty collection.
  140. /// </value>
  141. public CalendarBlackoutDatesCollection BlackoutDates { get; private set; }
  142. public static readonly DirectProperty<DatePicker, DateTime> DisplayDateProperty =
  143. AvaloniaProperty.RegisterDirect<DatePicker, DateTime>(
  144. nameof(DisplayDate),
  145. o => o.DisplayDate,
  146. (o, v) => o.DisplayDate = v);
  147. public static readonly DirectProperty<DatePicker, DateTime?> DisplayDateStartProperty =
  148. AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>(
  149. nameof(DisplayDateStart),
  150. o => o.DisplayDateStart,
  151. (o, v) => o.DisplayDateStart = v);
  152. public static readonly DirectProperty<DatePicker, DateTime?> DisplayDateEndProperty =
  153. AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>(
  154. nameof(DisplayDateEnd),
  155. o => o.DisplayDateEnd,
  156. (o, v) => o.DisplayDateEnd = v);
  157. public static readonly StyledProperty<DayOfWeek> FirstDayOfWeekProperty =
  158. AvaloniaProperty.Register<DatePicker, DayOfWeek>(nameof(FirstDayOfWeek));
  159. public static readonly DirectProperty<DatePicker, bool> IsDropDownOpenProperty =
  160. AvaloniaProperty.RegisterDirect<DatePicker, bool>(
  161. nameof(IsDropDownOpen),
  162. o => o.IsDropDownOpen,
  163. (o, v) => o.IsDropDownOpen = v);
  164. public static readonly StyledProperty<bool> IsTodayHighlightedProperty =
  165. AvaloniaProperty.Register<DatePicker, bool>(nameof(IsTodayHighlighted));
  166. public static readonly DirectProperty<DatePicker, DateTime?> SelectedDateProperty =
  167. AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>(
  168. nameof(SelectedDate),
  169. o => o.SelectedDate,
  170. (o, v) => o.SelectedDate = v);
  171. public static readonly StyledProperty<DatePickerFormat> SelectedDateFormatProperty =
  172. AvaloniaProperty.Register<DatePicker, DatePickerFormat>(
  173. nameof(SelectedDateFormat),
  174. defaultValue: DatePickerFormat.Short,
  175. validate: IsValidSelectedDateFormat);
  176. public static readonly StyledProperty<string> CustomDateFormatStringProperty =
  177. AvaloniaProperty.Register<DatePicker, string>(
  178. nameof(CustomDateFormatString),
  179. defaultValue: "d",
  180. validate: IsValidDateFormatString);
  181. public static readonly DirectProperty<DatePicker, string> TextProperty =
  182. AvaloniaProperty.RegisterDirect<DatePicker, string>(
  183. nameof(Text),
  184. o => o.Text,
  185. (o, v) => o.Text = v);
  186. public static readonly StyledProperty<string> WatermarkProperty =
  187. TextBox.WatermarkProperty.AddOwner<DatePicker>();
  188. public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
  189. TextBox.UseFloatingWatermarkProperty.AddOwner<DatePicker>();
  190. /// <summary>
  191. /// Gets or sets the date to display.
  192. /// </summary>
  193. /// <value>
  194. /// The date to display. The default
  195. /// <see cref="P:System.DateTime.Today" />.
  196. /// </value>
  197. /// <exception cref="T:System.ArgumentOutOfRangeException">
  198. /// The specified date is not in the range defined by
  199. /// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateStart" />
  200. /// and
  201. /// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateEnd" />.
  202. /// </exception>
  203. public DateTime DisplayDate
  204. {
  205. get { return _displayDate; }
  206. set { SetAndRaise(DisplayDateProperty, ref _displayDate, value); }
  207. }
  208. /// <summary>
  209. /// Gets or sets the first date to be displayed.
  210. /// </summary>
  211. /// <value>The first date to display.</value>
  212. public DateTime? DisplayDateStart
  213. {
  214. get { return _displayDateStart; }
  215. set { SetAndRaise(DisplayDateStartProperty, ref _displayDateStart, value); }
  216. }
  217. /// <summary>
  218. /// Gets or sets the last date to be displayed.
  219. /// </summary>
  220. /// <value>The last date to display.</value>
  221. public DateTime? DisplayDateEnd
  222. {
  223. get { return _displayDateEnd; }
  224. set { SetAndRaise(DisplayDateEndProperty, ref _displayDateEnd, value); }
  225. }
  226. /// <summary>
  227. /// Gets or sets the day that is considered the beginning of the week.
  228. /// </summary>
  229. /// <value>
  230. /// A <see cref="T:System.DayOfWeek" /> representing the beginning of
  231. /// the week. The default is <see cref="F:System.DayOfWeek.Sunday" />.
  232. /// </value>
  233. public DayOfWeek FirstDayOfWeek
  234. {
  235. get { return GetValue(FirstDayOfWeekProperty); }
  236. set { SetValue(FirstDayOfWeekProperty, value); }
  237. }
  238. /// <summary>
  239. /// Gets or sets a value indicating whether the drop-down
  240. /// <see cref="T:Avalonia.Controls.Calendar" /> is open or closed.
  241. /// </summary>
  242. /// <value>
  243. /// True if the <see cref="T:Avalonia.Controls.Calendar" /> is
  244. /// open; otherwise, false. The default is false.
  245. /// </value>
  246. public bool IsDropDownOpen
  247. {
  248. get { return _isDropDownOpen; }
  249. set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
  250. }
  251. /// <summary>
  252. /// Gets or sets a value indicating whether the current date will be
  253. /// highlighted.
  254. /// </summary>
  255. /// <value>
  256. /// True if the current date is highlighted; otherwise, false. The
  257. /// default is true.
  258. /// </value>
  259. public bool IsTodayHighlighted
  260. {
  261. get { return GetValue(IsTodayHighlightedProperty); }
  262. set { SetValue(IsTodayHighlightedProperty, value); }
  263. }
  264. /// <summary>
  265. /// Gets or sets the currently selected date.
  266. /// </summary>
  267. /// <value>
  268. /// The date currently selected. The default is null.
  269. /// </value>
  270. /// <exception cref="T:System.ArgumentOutOfRangeException">
  271. /// The specified date is not in the range defined by
  272. /// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateStart" />
  273. /// and
  274. /// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateEnd" />,
  275. /// or the specified date is in the
  276. /// <see cref="P:Avalonia.Controls.DatePicker.BlackoutDates" />
  277. /// collection.
  278. /// </exception>
  279. public DateTime? SelectedDate
  280. {
  281. get { return _selectedDate; }
  282. set { SetAndRaise(SelectedDateProperty, ref _selectedDate, value); }
  283. }
  284. /// <summary>
  285. /// Gets or sets the format that is used to display the selected date.
  286. /// </summary>
  287. /// <value>
  288. /// The format that is used to display the selected date. The default is
  289. /// <see cref="F:Avalonia.Controls.DatePickerFormat.Short" />.
  290. /// </value>
  291. /// <exception cref="T:System.ArgumentOutOfRangeException">
  292. /// An specified format is not valid.
  293. /// </exception>
  294. public DatePickerFormat SelectedDateFormat
  295. {
  296. get { return GetValue(SelectedDateFormatProperty); }
  297. set { SetValue(SelectedDateFormatProperty, value); }
  298. }
  299. public string CustomDateFormatString
  300. {
  301. get { return GetValue(CustomDateFormatStringProperty); }
  302. set { SetValue(CustomDateFormatStringProperty, value); }
  303. }
  304. /// <summary>
  305. /// Gets or sets the text that is displayed by the
  306. /// <see cref="T:Avalonia.Controls.DatePicker" />.
  307. /// </summary>
  308. /// <value>
  309. /// The text displayed by the
  310. /// <see cref="T:Avalonia.Controls.DatePicker" />.
  311. /// </value>
  312. /// <exception cref="T:System.FormatException">
  313. /// The text entered cannot be parsed to a valid date, and the exception
  314. /// is not suppressed.
  315. /// </exception>
  316. /// <exception cref="T:System.ArgumentOutOfRangeException">
  317. /// The text entered parses to a date that is not selectable.
  318. /// </exception>
  319. public string Text
  320. {
  321. get { return _text; }
  322. set { SetAndRaise(TextProperty, ref _text, value); }
  323. }
  324. public string Watermark
  325. {
  326. get { return GetValue(WatermarkProperty); }
  327. set { SetValue(WatermarkProperty, value); }
  328. }
  329. public bool UseFloatingWatermark
  330. {
  331. get { return GetValue(UseFloatingWatermarkProperty); }
  332. set { SetValue(UseFloatingWatermarkProperty, value); }
  333. }
  334. /// <summary>
  335. /// Occurs when the drop-down
  336. /// <see cref="T:Avalonia.Controls.Calendar" /> is closed.
  337. /// </summary>
  338. public event EventHandler CalendarClosed;
  339. /// <summary>
  340. /// Occurs when the drop-down
  341. /// <see cref="T:Avalonia.Controls.Calendar" /> is opened.
  342. /// </summary>
  343. public event EventHandler CalendarOpened;
  344. /// <summary>
  345. /// Occurs when <see cref="P:Avalonia.Controls.DatePicker.Text" />
  346. /// is assigned a value that cannot be interpreted as a date.
  347. /// </summary>
  348. public event EventHandler<DatePickerDateValidationErrorEventArgs> DateValidationError;
  349. /// <summary>
  350. /// Occurs when the
  351. /// <see cref="P:Avalonia.Controls.DatePicker.SelectedDate" />
  352. /// property is changed.
  353. /// </summary>
  354. public event EventHandler<SelectionChangedEventArgs> SelectedDateChanged;
  355. static DatePicker()
  356. {
  357. FocusableProperty.OverrideDefaultValue<DatePicker>(true);
  358. DisplayDateProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateChanged(e));
  359. DisplayDateStartProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateStartChanged(e));
  360. DisplayDateEndProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateEndChanged(e));
  361. IsDropDownOpenProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnIsDropDownOpenChanged(e));
  362. SelectedDateProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnSelectedDateChanged(e));
  363. SelectedDateFormatProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnSelectedDateFormatChanged(e));
  364. CustomDateFormatStringProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnCustomDateFormatStringChanged(e));
  365. TextProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnTextChanged(e));
  366. }
  367. /// <summary>
  368. /// Initializes a new instance of the
  369. /// <see cref="T:Avalonia.Controls.DatePicker" /> class.
  370. /// </summary>
  371. public DatePicker()
  372. {
  373. FirstDayOfWeek = DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek;
  374. _defaultText = string.Empty;
  375. DisplayDate = DateTime.Today;
  376. }
  377. protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
  378. {
  379. if (_calendar != null)
  380. {
  381. _calendar.DayButtonMouseUp -= Calendar_DayButtonMouseUp;
  382. _calendar.DisplayDateChanged -= Calendar_DisplayDateChanged;
  383. _calendar.SelectedDatesChanged -= Calendar_SelectedDatesChanged;
  384. _calendar.PointerPressed -= Calendar_PointerPressed;
  385. _calendar.KeyDown -= Calendar_KeyDown;
  386. }
  387. _calendar = e.NameScope.Find<Calendar>(ElementCalendar);
  388. if (_calendar != null)
  389. {
  390. _calendar.SelectionMode = CalendarSelectionMode.SingleDate;
  391. _calendar.SelectedDate = SelectedDate;
  392. SetCalendarDisplayDate(DisplayDate);
  393. SetCalendarDisplayDateStart(DisplayDateStart);
  394. SetCalendarDisplayDateEnd(DisplayDateEnd);
  395. _calendar.DayButtonMouseUp += Calendar_DayButtonMouseUp;
  396. _calendar.DisplayDateChanged += Calendar_DisplayDateChanged;
  397. _calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged;
  398. _calendar.PointerPressed += Calendar_PointerPressed;
  399. _calendar.KeyDown += Calendar_KeyDown;
  400. //_calendar.SizeChanged += new SizeChangedEventHandler(Calendar_SizeChanged);
  401. //_calendar.IsTabStop = true;
  402. var currentBlackoutDays = BlackoutDates;
  403. BlackoutDates = _calendar.BlackoutDates;
  404. if(currentBlackoutDays != null)
  405. {
  406. foreach (var range in currentBlackoutDays)
  407. {
  408. BlackoutDates.Add(range);
  409. }
  410. }
  411. }
  412. if (_popUp != null)
  413. {
  414. _popUp.Child = null;
  415. _popUp.Closed -= PopUp_Closed;
  416. }
  417. _popUp = e.NameScope.Find<Popup>(ElementPopup);
  418. if(_popUp != null)
  419. {
  420. _popUp.Closed += PopUp_Closed;
  421. if (IsDropDownOpen)
  422. {
  423. OpenDropDown();
  424. }
  425. }
  426. if(_dropDownButton != null)
  427. {
  428. _dropDownButton.Click -= DropDownButton_Click;
  429. _buttonPointerPressedSubscription?.Dispose();
  430. }
  431. _dropDownButton = e.NameScope.Find<Button>(ElementButton);
  432. if(_dropDownButton != null)
  433. {
  434. _dropDownButton.Click += DropDownButton_Click;
  435. _buttonPointerPressedSubscription =
  436. _dropDownButton.AddDisposableHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true);
  437. }
  438. if (_textBox != null)
  439. {
  440. _textBox.KeyDown -= TextBox_KeyDown;
  441. _textBox.GotFocus -= TextBox_GotFocus;
  442. _textBoxTextChangedSubscription?.Dispose();
  443. }
  444. _textBox = e.NameScope.Find<TextBox>(ElementTextBox);
  445. if(!SelectedDate.HasValue)
  446. {
  447. SetWaterMarkText();
  448. }
  449. if(_textBox != null)
  450. {
  451. _textBox.KeyDown += TextBox_KeyDown;
  452. _textBox.GotFocus += TextBox_GotFocus;
  453. _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBox_TextChanged());
  454. if(SelectedDate.HasValue)
  455. {
  456. _textBox.Text = DateTimeToString(SelectedDate.Value);
  457. }
  458. else if(!String.IsNullOrEmpty(_defaultText))
  459. {
  460. _textBox.Text = _defaultText;
  461. SetSelectedDate();
  462. }
  463. }
  464. base.OnTemplateApplied(e);
  465. }
  466. protected override void OnPropertyChanged<T>(
  467. AvaloniaProperty<T> property,
  468. Optional<T> oldValue,
  469. BindingValue<T> newValue,
  470. BindingPriority priority)
  471. {
  472. base.OnPropertyChanged(property, oldValue, newValue, priority);
  473. if (property == SelectedDateProperty)
  474. {
  475. DataValidationErrors.SetError(this, newValue.Error);
  476. }
  477. }
  478. protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
  479. {
  480. base.OnPointerWheelChanged(e);
  481. if (!e.Handled && SelectedDate.HasValue && _calendar != null)
  482. {
  483. DateTime selectedDate = this.SelectedDate.Value;
  484. DateTime? newDate = DateTimeHelper.AddDays(selectedDate, e.Delta.Y > 0 ? -1 : 1);
  485. if (newDate.HasValue && Calendar.IsValidDateSelection(_calendar, newDate.Value))
  486. {
  487. SelectedDate = newDate;
  488. e.Handled = true;
  489. }
  490. }
  491. }
  492. protected override void OnGotFocus(GotFocusEventArgs e)
  493. {
  494. base.OnGotFocus(e);
  495. if(IsEnabled && _textBox != null && e.NavigationMethod == NavigationMethod.Tab)
  496. {
  497. _textBox.Focus();
  498. var text = _textBox.Text;
  499. if(!string.IsNullOrEmpty(text))
  500. {
  501. _textBox.SelectionStart = 0;
  502. _textBox.SelectionEnd = text.Length;
  503. }
  504. }
  505. }
  506. protected override void OnLostFocus(RoutedEventArgs e)
  507. {
  508. base.OnLostFocus(e);
  509. SetSelectedDate();
  510. }
  511. private void SetCalendarDisplayDate(DateTime value)
  512. {
  513. if (DateTimeHelper.CompareYearMonth(_calendar.DisplayDate, value) != 0)
  514. {
  515. _calendar.DisplayDate = DisplayDate;
  516. if (DateTime.Compare(_calendar.DisplayDate, DisplayDate) != 0)
  517. {
  518. DisplayDate = _calendar.DisplayDate;
  519. }
  520. }
  521. }
  522. private void OnDisplayDateChanged(AvaloniaPropertyChangedEventArgs e)
  523. {
  524. if (_calendar != null)
  525. {
  526. var value = (DateTime)e.NewValue;
  527. SetCalendarDisplayDate(value);
  528. }
  529. }
  530. private void SetCalendarDisplayDateStart(DateTime? value)
  531. {
  532. _calendar.DisplayDateStart = value;
  533. if (_calendar.DisplayDateStart.HasValue && DisplayDateStart.HasValue && DateTime.Compare(_calendar.DisplayDateStart.Value, DisplayDateStart.Value) != 0)
  534. {
  535. DisplayDateStart = _calendar.DisplayDateStart;
  536. }
  537. }
  538. private void OnDisplayDateStartChanged(AvaloniaPropertyChangedEventArgs e)
  539. {
  540. if (_calendar != null)
  541. {
  542. var value = (DateTime?)e.NewValue;
  543. SetCalendarDisplayDateStart(value);
  544. }
  545. }
  546. private void SetCalendarDisplayDateEnd(DateTime? value)
  547. {
  548. _calendar.DisplayDateEnd = value;
  549. if (_calendar.DisplayDateEnd.HasValue && DisplayDateEnd.HasValue && DateTime.Compare(_calendar.DisplayDateEnd.Value, DisplayDateEnd.Value) != 0)
  550. {
  551. DisplayDateEnd = _calendar.DisplayDateEnd;
  552. }
  553. }
  554. private void OnDisplayDateEndChanged(AvaloniaPropertyChangedEventArgs e)
  555. {
  556. if (_calendar != null)
  557. {
  558. var value = (DateTime?)e.NewValue;
  559. SetCalendarDisplayDateEnd(value);
  560. }
  561. }
  562. private void OnIsDropDownOpenChanged(AvaloniaPropertyChangedEventArgs e)
  563. {
  564. var oldValue = (bool)e.OldValue;
  565. var value = (bool)e.NewValue;
  566. if (_popUp != null && _popUp.Child != null)
  567. {
  568. if (value != oldValue)
  569. {
  570. if (_calendar.DisplayMode != CalendarMode.Month)
  571. {
  572. _calendar.DisplayMode = CalendarMode.Month;
  573. }
  574. if (value)
  575. {
  576. OpenDropDown();
  577. }
  578. else
  579. {
  580. _popUp.IsOpen = false;
  581. OnCalendarClosed(new RoutedEventArgs());
  582. }
  583. }
  584. }
  585. }
  586. private void OnSelectedDateChanged(AvaloniaPropertyChangedEventArgs e)
  587. {
  588. var addedDate = (DateTime?)e.NewValue;
  589. var removedDate = (DateTime?)e.OldValue;
  590. if (_calendar != null && addedDate != _calendar.SelectedDate)
  591. {
  592. _calendar.SelectedDate = addedDate;
  593. }
  594. if (SelectedDate != null)
  595. {
  596. DateTime day = SelectedDate.Value;
  597. // When the SelectedDateProperty change is done from
  598. // OnTextPropertyChanged method, two-way binding breaks if
  599. // BeginInvoke is not used:
  600. Threading.Dispatcher.UIThread.InvokeAsync(() =>
  601. {
  602. _settingSelectedDate = true;
  603. Text = DateTimeToString(day);
  604. _settingSelectedDate = false;
  605. OnDateSelected(addedDate, removedDate);
  606. });
  607. // When DatePickerDisplayDateFlag is TRUE, the SelectedDate
  608. // change is coming from the Calendar UI itself, so, we
  609. // shouldn't change the DisplayDate since it will automatically
  610. // be changed by the Calendar
  611. if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.DatePickerDisplayDateFlag))
  612. {
  613. DisplayDate = day;
  614. }
  615. if(_calendar != null)
  616. _calendar.DatePickerDisplayDateFlag = false;
  617. }
  618. else
  619. {
  620. _settingSelectedDate = true;
  621. SetWaterMarkText();
  622. _settingSelectedDate = false;
  623. OnDateSelected(addedDate, removedDate);
  624. }
  625. }
  626. private void OnDateFormatChanged()
  627. {
  628. if (_textBox != null)
  629. {
  630. if (SelectedDate.HasValue)
  631. {
  632. Text = DateTimeToString(SelectedDate.Value);
  633. }
  634. else if (string.IsNullOrEmpty(_textBox.Text))
  635. {
  636. SetWaterMarkText();
  637. }
  638. else
  639. {
  640. DateTime? date = ParseText(_textBox.Text);
  641. if (date != null)
  642. {
  643. string s = DateTimeToString((DateTime)date);
  644. Text = s;
  645. }
  646. }
  647. }
  648. }
  649. private void OnSelectedDateFormatChanged(AvaloniaPropertyChangedEventArgs e)
  650. {
  651. OnDateFormatChanged();
  652. }
  653. private void OnCustomDateFormatStringChanged(AvaloniaPropertyChangedEventArgs e)
  654. {
  655. if(SelectedDateFormat == DatePickerFormat.Custom)
  656. {
  657. OnDateFormatChanged();
  658. }
  659. }
  660. private void OnTextChanged(AvaloniaPropertyChangedEventArgs e)
  661. {
  662. var oldValue = (string)e.OldValue;
  663. var value = (string)e.NewValue;
  664. if (!_suspendTextChangeHandler)
  665. {
  666. if (value != null)
  667. {
  668. if (_textBox != null)
  669. {
  670. _textBox.Text = value;
  671. }
  672. else
  673. {
  674. _defaultText = value;
  675. }
  676. if (!_settingSelectedDate)
  677. {
  678. SetSelectedDate();
  679. }
  680. }
  681. else
  682. {
  683. if (!_settingSelectedDate)
  684. {
  685. _settingSelectedDate = true;
  686. SelectedDate = null;
  687. _settingSelectedDate = false;
  688. }
  689. }
  690. }
  691. else
  692. {
  693. SetWaterMarkText();
  694. }
  695. }
  696. /// <summary>
  697. /// Raises the
  698. /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
  699. /// event.
  700. /// </summary>
  701. /// <param name="e">
  702. /// A
  703. /// <see cref="T:Avalonia.Controls.DatePickerDateValidationErrorEventArgs" />
  704. /// that contains the event data.
  705. /// </param>
  706. protected virtual void OnDateValidationError(DatePickerDateValidationErrorEventArgs e)
  707. {
  708. DateValidationError?.Invoke(this, e);
  709. }
  710. private void OnDateSelected(DateTime? addedDate, DateTime? removedDate)
  711. {
  712. EventHandler<SelectionChangedEventArgs> handler = this.SelectedDateChanged;
  713. if (null != handler)
  714. {
  715. Collection<DateTime> addedItems = new Collection<DateTime>();
  716. Collection<DateTime> removedItems = new Collection<DateTime>();
  717. if (addedDate.HasValue)
  718. {
  719. addedItems.Add(addedDate.Value);
  720. }
  721. if (removedDate.HasValue)
  722. {
  723. removedItems.Add(removedDate.Value);
  724. }
  725. handler(this, new SelectionChangedEventArgs(SelectingItemsControl.SelectionChangedEvent, removedItems, addedItems));
  726. }
  727. }
  728. private void OnCalendarClosed(EventArgs e)
  729. {
  730. CalendarClosed?.Invoke(this, e);
  731. }
  732. private void OnCalendarOpened(EventArgs e)
  733. {
  734. CalendarOpened?.Invoke(this, e);
  735. }
  736. private void Calendar_DayButtonMouseUp(object sender, PointerReleasedEventArgs e)
  737. {
  738. Focus();
  739. IsDropDownOpen = false;
  740. }
  741. private void Calendar_DisplayDateChanged(object sender, CalendarDateChangedEventArgs e)
  742. {
  743. if (e.AddedDate != this.DisplayDate)
  744. {
  745. SetValue(DisplayDateProperty, (DateTime) e.AddedDate);
  746. }
  747. }
  748. private void Calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
  749. {
  750. Debug.Assert(e.AddedItems.Count < 2, "There should be less than 2 AddedItems!");
  751. if (e.AddedItems.Count > 0 && SelectedDate.HasValue && DateTime.Compare((DateTime)e.AddedItems[0], SelectedDate.Value) != 0)
  752. {
  753. SelectedDate = (DateTime?)e.AddedItems[0];
  754. }
  755. else
  756. {
  757. if (e.AddedItems.Count == 0)
  758. {
  759. SelectedDate = null;
  760. return;
  761. }
  762. if (!SelectedDate.HasValue)
  763. {
  764. if (e.AddedItems.Count > 0)
  765. {
  766. SelectedDate = (DateTime?)e.AddedItems[0];
  767. }
  768. }
  769. }
  770. }
  771. private void Calendar_PointerPressed(object sender, PointerPressedEventArgs e)
  772. {
  773. if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
  774. {
  775. e.Handled = true;
  776. }
  777. }
  778. private void Calendar_KeyDown(object sender, KeyEventArgs e)
  779. {
  780. Calendar c = sender as Calendar;
  781. Contract.Requires<ArgumentNullException>(c != null);
  782. if (!e.Handled && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape) && c.DisplayMode == CalendarMode.Month)
  783. {
  784. Focus();
  785. IsDropDownOpen = false;
  786. if (e.Key == Key.Escape)
  787. {
  788. SelectedDate = _onOpenSelectedDate;
  789. }
  790. }
  791. }
  792. private void TextBox_GotFocus(object sender, RoutedEventArgs e)
  793. {
  794. IsDropDownOpen = false;
  795. }
  796. private void TextBox_KeyDown(object sender, KeyEventArgs e)
  797. {
  798. if (!e.Handled)
  799. {
  800. e.Handled = ProcessDatePickerKey(e);
  801. }
  802. }
  803. private void TextBox_TextChanged()
  804. {
  805. if (_textBox != null)
  806. {
  807. _suspendTextChangeHandler = true;
  808. Text = _textBox.Text;
  809. _suspendTextChangeHandler = false;
  810. }
  811. }
  812. private void DropDownButton_PointerPressed(object sender, PointerPressedEventArgs e)
  813. {
  814. _ignoreButtonClick = _isPopupClosing;
  815. }
  816. private void DropDownButton_Click(object sender, RoutedEventArgs e)
  817. {
  818. if (!_ignoreButtonClick)
  819. {
  820. HandlePopUp();
  821. }
  822. else
  823. {
  824. _ignoreButtonClick = false;
  825. }
  826. }
  827. private void PopUp_Closed(object sender, PopupClosedEventArgs e)
  828. {
  829. IsDropDownOpen = false;
  830. if(!_isPopupClosing)
  831. {
  832. if (e.CloseEvent is PointerEventArgs pointerEvent)
  833. {
  834. pointerEvent.Handled = true;
  835. }
  836. _isPopupClosing = true;
  837. Threading.Dispatcher.UIThread.InvokeAsync(() => _isPopupClosing = false);
  838. }
  839. }
  840. private void HandlePopUp()
  841. {
  842. if (IsDropDownOpen)
  843. {
  844. Focus();
  845. IsDropDownOpen = false;
  846. }
  847. else
  848. {
  849. ProcessTextBox();
  850. }
  851. }
  852. private void OpenDropDown()
  853. {
  854. if (_calendar != null)
  855. {
  856. _calendar.Focus();
  857. OpenPopUp();
  858. _calendar.ResetStates();
  859. OnCalendarOpened(new RoutedEventArgs());
  860. }
  861. }
  862. private void OpenPopUp()
  863. {
  864. _onOpenSelectedDate = SelectedDate;
  865. _popUp.IsOpen = true;
  866. }
  867. /// <summary>
  868. /// Input text is parsed in the correct format and changed into a
  869. /// DateTime object. If the text can not be parsed TextParseError Event
  870. /// is thrown.
  871. /// </summary>
  872. /// <param name="text">Inherited code: Requires comment.</param>
  873. /// <returns>
  874. /// IT SHOULD RETURN NULL IF THE STRING IS NOT VALID, RETURN THE
  875. /// DATETIME VALUE IF IT IS VALID.
  876. /// </returns>
  877. private DateTime? ParseText(string text)
  878. {
  879. DateTime newSelectedDate;
  880. // TryParse is not used in order to be able to pass the exception to
  881. // the TextParseError event
  882. try
  883. {
  884. newSelectedDate = DateTime.Parse(text, DateTimeHelper.GetCurrentDateFormat());
  885. if (Calendar.IsValidDateSelection(this._calendar, newSelectedDate))
  886. {
  887. return newSelectedDate;
  888. }
  889. else
  890. {
  891. var dateValidationError = new DatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException(nameof(text), "SelectedDate value is not valid."), text);
  892. OnDateValidationError(dateValidationError);
  893. if (dateValidationError.ThrowException)
  894. {
  895. throw dateValidationError.Exception;
  896. }
  897. }
  898. }
  899. catch (FormatException ex)
  900. {
  901. DatePickerDateValidationErrorEventArgs textParseError = new DatePickerDateValidationErrorEventArgs(ex, text);
  902. OnDateValidationError(textParseError);
  903. if (textParseError.ThrowException)
  904. {
  905. throw textParseError.Exception;
  906. }
  907. }
  908. return null;
  909. }
  910. private string DateTimeToString(DateTime d)
  911. {
  912. DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
  913. switch (SelectedDateFormat)
  914. {
  915. case DatePickerFormat.Short:
  916. return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.ShortDatePattern, dtfi));
  917. case DatePickerFormat.Long:
  918. return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.LongDatePattern, dtfi));
  919. case DatePickerFormat.Custom:
  920. return string.Format(CultureInfo.CurrentCulture, d.ToString(CustomDateFormatString, dtfi));
  921. }
  922. return null;
  923. }
  924. private bool ProcessDatePickerKey(KeyEventArgs e)
  925. {
  926. switch (e.Key)
  927. {
  928. case Key.Enter:
  929. {
  930. SetSelectedDate();
  931. return true;
  932. }
  933. case Key.Down:
  934. {
  935. if ((e.KeyModifiers & KeyModifiers.Control) == KeyModifiers.Control)
  936. {
  937. HandlePopUp();
  938. return true;
  939. }
  940. break;
  941. }
  942. }
  943. return false;
  944. }
  945. private void ProcessTextBox()
  946. {
  947. SetSelectedDate();
  948. IsDropDownOpen = true;
  949. _calendar.Focus();
  950. }
  951. private void SetSelectedDate()
  952. {
  953. if (_textBox != null)
  954. {
  955. if (!string.IsNullOrEmpty(_textBox.Text))
  956. {
  957. string s = _textBox.Text;
  958. if (SelectedDate != null)
  959. {
  960. // If the string value of the SelectedDate and the
  961. // TextBox string value are equal, we do not parse the
  962. // string again if we do an extra parse, we lose data in
  963. // M/d/yy format.
  964. // ex: SelectedDate = DateTime(1008,12,19) but when
  965. // "12/19/08" is parsed it is interpreted as
  966. // DateTime(2008,12,19)
  967. string selectedDate = DateTimeToString(SelectedDate.Value);
  968. if (selectedDate == s)
  969. {
  970. return;
  971. }
  972. }
  973. DateTime? d = SetTextBoxValue(s);
  974. if (SelectedDate != d)
  975. {
  976. SelectedDate = d;
  977. }
  978. }
  979. else
  980. {
  981. if (SelectedDate != null)
  982. {
  983. SelectedDate = null;
  984. }
  985. }
  986. }
  987. else
  988. {
  989. DateTime? d = SetTextBoxValue(_defaultText);
  990. if (SelectedDate != d)
  991. {
  992. SelectedDate = d;
  993. }
  994. }
  995. }
  996. private DateTime? SetTextBoxValue(string s)
  997. {
  998. if (string.IsNullOrEmpty(s))
  999. {
  1000. SetValue(TextProperty, s);
  1001. return SelectedDate;
  1002. }
  1003. else
  1004. {
  1005. DateTime? d = ParseText(s);
  1006. if (d != null)
  1007. {
  1008. SetValue(TextProperty, s);
  1009. return d;
  1010. }
  1011. else
  1012. {
  1013. // If parse error: TextBox should have the latest valid
  1014. // SelectedDate value:
  1015. if (SelectedDate != null)
  1016. {
  1017. string newtext = this.DateTimeToString(SelectedDate.Value);
  1018. SetValue(TextProperty, newtext);
  1019. return SelectedDate;
  1020. }
  1021. else
  1022. {
  1023. SetWaterMarkText();
  1024. return null;
  1025. }
  1026. }
  1027. }
  1028. }
  1029. private void SetWaterMarkText()
  1030. {
  1031. if (_textBox != null)
  1032. {
  1033. if (string.IsNullOrEmpty(Watermark) && !UseFloatingWatermark)
  1034. {
  1035. DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
  1036. Text = string.Empty;
  1037. _defaultText = string.Empty;
  1038. var watermarkFormat = "<{0}>";
  1039. string watermarkText;
  1040. switch (SelectedDateFormat)
  1041. {
  1042. case DatePickerFormat.Long:
  1043. {
  1044. watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.LongDatePattern.ToString());
  1045. break;
  1046. }
  1047. case DatePickerFormat.Short:
  1048. default:
  1049. {
  1050. watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.ShortDatePattern.ToString());
  1051. break;
  1052. }
  1053. }
  1054. _textBox.Watermark = watermarkText;
  1055. }
  1056. else
  1057. {
  1058. _textBox.ClearValue(TextBox.WatermarkProperty);
  1059. }
  1060. }
  1061. }
  1062. private static bool IsValidSelectedDateFormat(DatePickerFormat value)
  1063. {
  1064. return value == DatePickerFormat.Long
  1065. || value == DatePickerFormat.Short
  1066. || value == DatePickerFormat.Custom;
  1067. }
  1068. private static bool IsValidDateFormatString(string formatString)
  1069. {
  1070. return !string.IsNullOrWhiteSpace(formatString);
  1071. }
  1072. private static DateTime DiscardDayTime(DateTime d)
  1073. {
  1074. int year = d.Year;
  1075. int month = d.Month;
  1076. DateTime newD = new DateTime(year, month, 1, 0, 0, 0);
  1077. return newD;
  1078. }
  1079. private static DateTime? DiscardTime(DateTime? d)
  1080. {
  1081. if (d == null)
  1082. {
  1083. return null;
  1084. }
  1085. else
  1086. {
  1087. DateTime discarded = (DateTime) d;
  1088. int year = discarded.Year;
  1089. int month = discarded.Month;
  1090. int day = discarded.Day;
  1091. DateTime newD = new DateTime(year, month, day, 0, 0, 0);
  1092. return newD;
  1093. }
  1094. }
  1095. }
  1096. }