CalendarDatePicker.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  1. // (c) Copyright Microsoft Corporation.
  2. // This source is subject to the Microsoft Public License (Ms-PL).
  3. // Please see https://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.Reactive;
  10. using Avalonia.Controls.Metadata;
  11. using Avalonia.Controls.Primitives;
  12. using Avalonia.Data;
  13. using Avalonia.Input;
  14. using Avalonia.Interactivity;
  15. namespace Avalonia.Controls
  16. {
  17. /// <summary>
  18. /// A date selection control that allows the user to select dates from a drop down calendar.
  19. /// </summary>
  20. [TemplatePart(ElementButton, typeof(Button))]
  21. [TemplatePart(ElementCalendar, typeof(Calendar))]
  22. [TemplatePart(ElementPopup, typeof(Popup))]
  23. [TemplatePart(ElementTextBox, typeof(TextBox))]
  24. [PseudoClasses(pcFlyoutOpen, pcPressed)]
  25. public partial class CalendarDatePicker : TemplatedControl
  26. {
  27. protected const string pcPressed = ":pressed";
  28. protected const string pcFlyoutOpen = ":flyout-open";
  29. private const string ElementTextBox = "PART_TextBox";
  30. private const string ElementButton = "PART_Button";
  31. private const string ElementPopup = "PART_Popup";
  32. private const string ElementCalendar = "PART_Calendar";
  33. private Calendar? _calendar;
  34. private string _defaultText;
  35. private Button? _dropDownButton;
  36. private Popup? _popUp;
  37. private TextBox? _textBox;
  38. private IDisposable? _textBoxTextChangedSubscription;
  39. private IDisposable? _buttonPointerPressedSubscription;
  40. private DateTime? _onOpenSelectedDate;
  41. private bool _settingSelectedDate;
  42. private bool _suspendTextChangeHandler;
  43. private bool _isPopupClosing;
  44. private bool _ignoreButtonClick;
  45. private bool _isFlyoutOpen;
  46. private bool _isPressed;
  47. /// <summary>
  48. /// Occurs when the drop-down
  49. /// <see cref="T:Avalonia.Controls.Calendar" /> is closed.
  50. /// </summary>
  51. public event EventHandler? CalendarClosed;
  52. /// <summary>
  53. /// Occurs when the drop-down
  54. /// <see cref="T:Avalonia.Controls.Calendar" /> is opened.
  55. /// </summary>
  56. public event EventHandler? CalendarOpened;
  57. /// <summary>
  58. /// Occurs when <see cref="P:Avalonia.Controls.DatePicker.Text" />
  59. /// is assigned a value that cannot be interpreted as a date.
  60. /// </summary>
  61. public event EventHandler<CalendarDatePickerDateValidationErrorEventArgs>? DateValidationError;
  62. /// <summary>
  63. /// Occurs when the
  64. /// <see cref="P:Avalonia.Controls.CalendarDatePicker.SelectedDate" />
  65. /// property is changed.
  66. /// </summary>
  67. public event EventHandler<SelectionChangedEventArgs>? SelectedDateChanged;
  68. static CalendarDatePicker()
  69. {
  70. FocusableProperty.OverrideDefaultValue<CalendarDatePicker>(true);
  71. }
  72. /// <summary>
  73. /// Initializes a new instance of the <see cref="CalendarDatePicker" /> class.
  74. /// </summary>
  75. public CalendarDatePicker()
  76. {
  77. SetCurrentValue(FirstDayOfWeekProperty, DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek);
  78. _defaultText = string.Empty;
  79. SetCurrentValue(DisplayDateProperty, DateTime.Today);
  80. }
  81. /// <summary>
  82. /// Updates the visual state of the control by applying latest PseudoClasses.
  83. /// </summary>
  84. protected void UpdatePseudoClasses()
  85. {
  86. PseudoClasses.Set(pcFlyoutOpen, _isFlyoutOpen);
  87. PseudoClasses.Set(pcPressed, _isPressed);
  88. }
  89. /// <inheritdoc/>
  90. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  91. {
  92. if (_calendar != null)
  93. {
  94. _calendar.DayButtonMouseUp -= Calendar_DayButtonMouseUp;
  95. _calendar.DisplayDateChanged -= Calendar_DisplayDateChanged;
  96. _calendar.SelectedDatesChanged -= Calendar_SelectedDatesChanged;
  97. _calendar.PointerReleased -= Calendar_PointerReleased;
  98. _calendar.KeyDown -= Calendar_KeyDown;
  99. }
  100. _calendar = e.NameScope.Find<Calendar>(ElementCalendar);
  101. if (_calendar != null)
  102. {
  103. _calendar.SelectionMode = CalendarSelectionMode.SingleDate;
  104. _calendar.DayButtonMouseUp += Calendar_DayButtonMouseUp;
  105. _calendar.DisplayDateChanged += Calendar_DisplayDateChanged;
  106. _calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged;
  107. _calendar.PointerReleased += Calendar_PointerReleased;
  108. _calendar.KeyDown += Calendar_KeyDown;
  109. var currentBlackoutDays = BlackoutDates;
  110. BlackoutDates = _calendar.BlackoutDates;
  111. if(currentBlackoutDays != null)
  112. {
  113. foreach (var range in currentBlackoutDays)
  114. {
  115. BlackoutDates.Add(range);
  116. }
  117. }
  118. }
  119. if (_popUp != null)
  120. {
  121. _popUp.Child = null;
  122. _popUp.Closed -= PopUp_Closed;
  123. }
  124. _popUp = e.NameScope.Find<Popup>(ElementPopup);
  125. if(_popUp != null)
  126. {
  127. _popUp.Closed += PopUp_Closed;
  128. if (IsDropDownOpen)
  129. {
  130. OpenDropDown();
  131. }
  132. }
  133. if(_dropDownButton != null)
  134. {
  135. _dropDownButton.Click -= DropDownButton_Click;
  136. _buttonPointerPressedSubscription?.Dispose();
  137. }
  138. _dropDownButton = e.NameScope.Find<Button>(ElementButton);
  139. if(_dropDownButton != null)
  140. {
  141. _dropDownButton.Click += DropDownButton_Click;
  142. _buttonPointerPressedSubscription = new CompositeDisposable(
  143. _dropDownButton.AddDisposableHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true),
  144. _dropDownButton.AddDisposableHandler(PointerReleasedEvent, DropDownButton_PointerReleased, handledEventsToo: true));
  145. }
  146. if (_textBox != null)
  147. {
  148. _textBox.KeyDown -= TextBox_KeyDown;
  149. _textBox.GotFocus -= TextBox_GotFocus;
  150. _textBoxTextChangedSubscription?.Dispose();
  151. }
  152. _textBox = e.NameScope.Find<TextBox>(ElementTextBox);
  153. if(!SelectedDate.HasValue)
  154. {
  155. SetWaterMarkText();
  156. }
  157. if(_textBox != null)
  158. {
  159. _textBox.KeyDown += TextBox_KeyDown;
  160. _textBox.GotFocus += TextBox_GotFocus;
  161. _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(_ => TextBox_TextChanged());
  162. if(SelectedDate.HasValue)
  163. {
  164. _textBox.Text = DateTimeToString(SelectedDate.Value);
  165. }
  166. else if(!String.IsNullOrEmpty(_defaultText))
  167. {
  168. _textBox.Text = _defaultText;
  169. SetSelectedDate();
  170. }
  171. }
  172. UpdatePseudoClasses();
  173. }
  174. /// <inheritdoc/>
  175. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
  176. {
  177. // CustomDateFormatString
  178. if (change.Property == CustomDateFormatStringProperty)
  179. {
  180. if (SelectedDateFormat == CalendarDatePickerFormat.Custom)
  181. {
  182. OnDateFormatChanged();
  183. }
  184. }
  185. // IsDropDownOpen
  186. else if (change.Property == IsDropDownOpenProperty)
  187. {
  188. var (oldValue, newValue) = change.GetOldAndNewValue<bool>();
  189. if (_popUp != null && _popUp.Child != null)
  190. {
  191. if (newValue != oldValue)
  192. {
  193. if (_calendar!.DisplayMode != CalendarMode.Month)
  194. {
  195. _calendar.DisplayMode = CalendarMode.Month;
  196. }
  197. if (newValue)
  198. {
  199. OpenDropDown();
  200. }
  201. else
  202. {
  203. _popUp.IsOpen = false;
  204. _isFlyoutOpen = _popUp.IsOpen;
  205. _isPressed = false;
  206. UpdatePseudoClasses();
  207. OnCalendarClosed(new RoutedEventArgs());
  208. }
  209. }
  210. }
  211. }
  212. // SelectedDate
  213. else if (change.Property == SelectedDateProperty)
  214. {
  215. var (removedDate, addedDate) = change.GetOldAndNewValue<DateTime?>();
  216. if (SelectedDate != null)
  217. {
  218. DateTime day = SelectedDate.Value;
  219. // When the SelectedDateProperty change is done from
  220. // OnTextPropertyChanged method, two-way binding breaks if
  221. // BeginInvoke is not used:
  222. Threading.Dispatcher.UIThread.InvokeAsync(() =>
  223. {
  224. _settingSelectedDate = true;
  225. SetCurrentValue(TextProperty, DateTimeToString(day));
  226. _settingSelectedDate = false;
  227. OnDateSelected(addedDate, removedDate);
  228. });
  229. // When DatePickerDisplayDateFlag is TRUE, the SelectedDate
  230. // change is coming from the Calendar UI itself, so, we
  231. // shouldn't change the DisplayDate since it will automatically
  232. // be changed by the Calendar
  233. if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.CalendarDatePickerDisplayDateFlag))
  234. {
  235. SetCurrentValue(DisplayDateProperty, day);
  236. }
  237. if(_calendar != null)
  238. {
  239. _calendar.CalendarDatePickerDisplayDateFlag = false;
  240. }
  241. }
  242. else
  243. {
  244. _settingSelectedDate = true;
  245. SetWaterMarkText();
  246. _settingSelectedDate = false;
  247. OnDateSelected(addedDate, removedDate);
  248. }
  249. }
  250. // SelectedDateFormat
  251. else if (change.Property == SelectedDateFormatProperty)
  252. {
  253. OnDateFormatChanged();
  254. }
  255. // Text
  256. else if (change.Property == TextProperty)
  257. {
  258. var (_, newValue) = change.GetOldAndNewValue<string?>();
  259. if (!_suspendTextChangeHandler)
  260. {
  261. if (newValue != null)
  262. {
  263. if (_textBox != null)
  264. {
  265. _textBox.Text = newValue;
  266. }
  267. else
  268. {
  269. _defaultText = newValue;
  270. }
  271. if (!_settingSelectedDate)
  272. {
  273. SetSelectedDate();
  274. }
  275. }
  276. else
  277. {
  278. if (!_settingSelectedDate)
  279. {
  280. _settingSelectedDate = true;
  281. SetCurrentValue(SelectedDateProperty, null);
  282. _settingSelectedDate = false;
  283. }
  284. }
  285. }
  286. else
  287. {
  288. SetWaterMarkText();
  289. }
  290. }
  291. base.OnPropertyChanged(change);
  292. }
  293. /// <inheritdoc/>
  294. protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
  295. {
  296. if (property == SelectedDateProperty)
  297. {
  298. DataValidationErrors.SetError(this, error);
  299. }
  300. base.UpdateDataValidation(property, state, error);
  301. }
  302. /// <inheritdoc/>
  303. protected override void OnPointerPressed(PointerPressedEventArgs e)
  304. {
  305. base.OnPointerPressed(e);
  306. if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
  307. {
  308. e.Handled = true;
  309. _ignoreButtonClick = _isPopupClosing;
  310. _isPressed = true;
  311. UpdatePseudoClasses();
  312. }
  313. }
  314. /// <inheritdoc/>
  315. protected override void OnPointerReleased(PointerReleasedEventArgs e)
  316. {
  317. base.OnPointerReleased(e);
  318. if (_isPressed && e.InitialPressMouseButton == MouseButton.Left)
  319. {
  320. e.Handled = true;
  321. if (!_ignoreButtonClick)
  322. {
  323. TogglePopUp();
  324. }
  325. else
  326. {
  327. _ignoreButtonClick = false;
  328. }
  329. _isPressed = false;
  330. UpdatePseudoClasses();
  331. }
  332. }
  333. /// <inheritdoc/>
  334. protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
  335. {
  336. base.OnPointerCaptureLost(e);
  337. _isPressed = false;
  338. UpdatePseudoClasses();
  339. }
  340. /// <inheritdoc/>
  341. protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
  342. {
  343. base.OnPointerWheelChanged(e);
  344. if (!e.Handled && SelectedDate.HasValue && _calendar != null)
  345. {
  346. DateTime selectedDate = this.SelectedDate.Value;
  347. DateTime? newDate = DateTimeHelper.AddDays(selectedDate, e.Delta.Y > 0 ? -1 : 1);
  348. if (newDate.HasValue && Calendar.IsValidDateSelection(_calendar, newDate.Value))
  349. {
  350. SetCurrentValue(SelectedDateProperty, newDate);
  351. e.Handled = true;
  352. }
  353. }
  354. }
  355. /// <inheritdoc/>
  356. protected override void OnGotFocus(GotFocusEventArgs e)
  357. {
  358. base.OnGotFocus(e);
  359. if(IsEnabled && _textBox != null && e.NavigationMethod == NavigationMethod.Tab)
  360. {
  361. _textBox.Focus();
  362. var text = _textBox.Text;
  363. if(!string.IsNullOrEmpty(text))
  364. {
  365. _textBox.SelectionStart = 0;
  366. _textBox.SelectionEnd = text.Length;
  367. }
  368. }
  369. }
  370. /// <inheritdoc/>
  371. protected override void OnLostFocus(RoutedEventArgs e)
  372. {
  373. base.OnLostFocus(e);
  374. _isPressed = false;
  375. UpdatePseudoClasses();
  376. SetSelectedDate();
  377. }
  378. /// <inheritdoc/>
  379. protected override void OnKeyUp(KeyEventArgs e)
  380. {
  381. var key = e.Key;
  382. if ((key == Key.Space || key == Key.Enter) && IsEffectivelyEnabled) // Key.GamepadA is not currently supported
  383. {
  384. // Since the TextBox is used for direct date entry,
  385. // it isn't supported to open the popup/flyout using these keys.
  386. // Other controls open the popup/flyout here.
  387. }
  388. else if (key == Key.Down && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) && IsEffectivelyEnabled)
  389. {
  390. // It is only possible to open the popup using these keys.
  391. // This is important as the down key is handled by calendar.
  392. // If down also closed the popup, the date would move 1 week
  393. // and then close the popup. This isn't user friendly at all.
  394. // (calendar doesn't mark as handled either).
  395. // The Escape key will still close the popup.
  396. if (IsDropDownOpen == false)
  397. {
  398. e.Handled = true;
  399. if (!_ignoreButtonClick)
  400. {
  401. TogglePopUp();
  402. }
  403. else
  404. {
  405. _ignoreButtonClick = false;
  406. }
  407. UpdatePseudoClasses();
  408. }
  409. }
  410. base.OnKeyUp(e);
  411. }
  412. private void OnDateFormatChanged()
  413. {
  414. if (_textBox != null)
  415. {
  416. if (SelectedDate.HasValue)
  417. {
  418. SetCurrentValue(TextProperty, DateTimeToString(SelectedDate.Value));
  419. }
  420. else if (string.IsNullOrEmpty(_textBox.Text))
  421. {
  422. SetWaterMarkText();
  423. }
  424. else
  425. {
  426. DateTime? date = ParseText(_textBox.Text);
  427. if (date != null)
  428. {
  429. string? s = DateTimeToString((DateTime)date);
  430. SetCurrentValue(TextProperty, s);
  431. }
  432. }
  433. }
  434. }
  435. /// <summary>
  436. /// Raises the
  437. /// <see cref="E:Avalonia.Controls.CalendarDatePicker.DateValidationError" />
  438. /// event.
  439. /// </summary>
  440. /// <param name="e">
  441. /// A
  442. /// <see cref="T:Avalonia.Controls.CalendarDatePickerDateValidationErrorEventArgs" />
  443. /// that contains the event data.
  444. /// </param>
  445. protected virtual void OnDateValidationError(CalendarDatePickerDateValidationErrorEventArgs e)
  446. {
  447. DateValidationError?.Invoke(this, e);
  448. }
  449. private void OnDateSelected(DateTime? addedDate, DateTime? removedDate)
  450. {
  451. EventHandler<SelectionChangedEventArgs>? handler = this.SelectedDateChanged;
  452. if (null != handler)
  453. {
  454. Collection<DateTime> addedItems = new Collection<DateTime>();
  455. Collection<DateTime> removedItems = new Collection<DateTime>();
  456. if (addedDate.HasValue)
  457. {
  458. addedItems.Add(addedDate.Value);
  459. }
  460. if (removedDate.HasValue)
  461. {
  462. removedItems.Add(removedDate.Value);
  463. }
  464. handler(this, new SelectionChangedEventArgs(SelectingItemsControl.SelectionChangedEvent, removedItems, addedItems));
  465. }
  466. }
  467. private void OnCalendarClosed(EventArgs e)
  468. {
  469. CalendarClosed?.Invoke(this, e);
  470. }
  471. private void OnCalendarOpened(EventArgs e)
  472. {
  473. CalendarOpened?.Invoke(this, e);
  474. }
  475. private void Calendar_DayButtonMouseUp(object? sender, PointerReleasedEventArgs e)
  476. {
  477. Focus();
  478. SetCurrentValue(IsDropDownOpenProperty, false);
  479. }
  480. private void Calendar_DisplayDateChanged(object? sender, CalendarDateChangedEventArgs e)
  481. {
  482. if (e.AddedDate != this.DisplayDate)
  483. {
  484. SetValue(DisplayDateProperty, (DateTime) e.AddedDate!);
  485. }
  486. }
  487. private void Calendar_SelectedDatesChanged(object? sender, SelectionChangedEventArgs e)
  488. {
  489. Debug.Assert(e.AddedItems.Count < 2, "There should be less than 2 AddedItems!");
  490. if (e.AddedItems.Count > 0 && SelectedDate.HasValue && DateTime.Compare((DateTime)e.AddedItems[0]!, SelectedDate.Value) != 0)
  491. {
  492. SetCurrentValue(SelectedDateProperty, (DateTime?)e.AddedItems[0]);
  493. }
  494. else
  495. {
  496. if (e.AddedItems.Count == 0)
  497. {
  498. SetCurrentValue(SelectedDateProperty, null);
  499. return;
  500. }
  501. if (!SelectedDate.HasValue)
  502. {
  503. if (e.AddedItems.Count > 0)
  504. {
  505. SetCurrentValue(SelectedDateProperty, (DateTime?)e.AddedItems[0]);
  506. }
  507. }
  508. }
  509. }
  510. private void Calendar_PointerReleased(object? sender, PointerReleasedEventArgs e)
  511. {
  512. if (e.InitialPressMouseButton == MouseButton.Left)
  513. {
  514. e.Handled = true;
  515. }
  516. }
  517. private void Calendar_KeyDown(object? sender, KeyEventArgs e)
  518. {
  519. if (!e.Handled
  520. && sender is Calendar { DisplayMode: CalendarMode.Month }
  521. && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape))
  522. {
  523. Focus();
  524. SetCurrentValue(IsDropDownOpenProperty, false);
  525. if (e.Key == Key.Escape)
  526. {
  527. SetCurrentValue(SelectedDateProperty, _onOpenSelectedDate);
  528. }
  529. }
  530. }
  531. private void TextBox_GotFocus(object? sender, RoutedEventArgs e)
  532. {
  533. SetCurrentValue(IsDropDownOpenProperty, false);
  534. }
  535. private void TextBox_KeyDown(object? sender, KeyEventArgs e)
  536. {
  537. if (!e.Handled)
  538. {
  539. e.Handled = ProcessDatePickerKey(e);
  540. }
  541. }
  542. private void TextBox_TextChanged()
  543. {
  544. if (_textBox != null)
  545. {
  546. _suspendTextChangeHandler = true;
  547. SetCurrentValue(TextProperty, _textBox.Text);
  548. _suspendTextChangeHandler = false;
  549. }
  550. }
  551. private void DropDownButton_PointerPressed(object? sender, PointerPressedEventArgs e)
  552. {
  553. _ignoreButtonClick = _isPopupClosing;
  554. _isPressed = true;
  555. UpdatePseudoClasses();
  556. }
  557. private void DropDownButton_PointerReleased(object? sender, PointerReleasedEventArgs e)
  558. {
  559. _isPressed = false;
  560. UpdatePseudoClasses();
  561. }
  562. private void DropDownButton_Click(object? sender, RoutedEventArgs e)
  563. {
  564. if (!_ignoreButtonClick)
  565. {
  566. TogglePopUp();
  567. }
  568. else
  569. {
  570. _ignoreButtonClick = false;
  571. }
  572. }
  573. private void PopUp_Closed(object? sender, EventArgs e)
  574. {
  575. SetCurrentValue(IsDropDownOpenProperty, false);
  576. if(!_isPopupClosing)
  577. {
  578. _isPopupClosing = true;
  579. Threading.Dispatcher.UIThread.InvokeAsync(() => _isPopupClosing = false);
  580. }
  581. }
  582. /// <summary>
  583. /// Toggles the <see cref="IsDropDownOpen"/> property to open/close the calendar popup.
  584. /// This will automatically adjust control focus as well.
  585. /// </summary>
  586. private void TogglePopUp()
  587. {
  588. if (IsDropDownOpen)
  589. {
  590. Focus();
  591. SetCurrentValue(IsDropDownOpenProperty, false);
  592. }
  593. else
  594. {
  595. SetSelectedDate();
  596. SetCurrentValue(IsDropDownOpenProperty, true);
  597. _calendar!.Focus();
  598. }
  599. }
  600. private void OpenDropDown()
  601. {
  602. if (_calendar != null)
  603. {
  604. _calendar.Focus();
  605. // Open the PopUp
  606. _onOpenSelectedDate = SelectedDate;
  607. _popUp!.IsOpen = true;
  608. _isFlyoutOpen = _popUp!.IsOpen;
  609. UpdatePseudoClasses();
  610. _calendar.ResetStates();
  611. OnCalendarOpened(new RoutedEventArgs());
  612. }
  613. }
  614. /// <summary>
  615. /// Input text is parsed in the correct format and changed into a
  616. /// DateTime object. If the text can not be parsed TextParseError Event
  617. /// is thrown.
  618. /// </summary>
  619. /// <param name="text">Inherited code: Requires comment.</param>
  620. /// <returns>
  621. /// IT SHOULD RETURN NULL IF THE STRING IS NOT VALID, RETURN THE
  622. /// DATETIME VALUE IF IT IS VALID.
  623. /// </returns>
  624. private DateTime? ParseText(string text)
  625. {
  626. DateTime newSelectedDate;
  627. // TryParse is not used in order to be able to pass the exception to
  628. // the TextParseError event
  629. try
  630. {
  631. newSelectedDate = DateTime.Parse(text, DateTimeHelper.GetCurrentDateFormat());
  632. if (Calendar.IsValidDateSelection(this._calendar!, newSelectedDate))
  633. {
  634. return newSelectedDate;
  635. }
  636. else
  637. {
  638. var dateValidationError = new CalendarDatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException(nameof(text), "SelectedDate value is not valid."), text);
  639. OnDateValidationError(dateValidationError);
  640. if (dateValidationError.ThrowException)
  641. {
  642. throw dateValidationError.Exception;
  643. }
  644. }
  645. }
  646. catch (FormatException ex)
  647. {
  648. CalendarDatePickerDateValidationErrorEventArgs textParseError = new CalendarDatePickerDateValidationErrorEventArgs(ex, text);
  649. OnDateValidationError(textParseError);
  650. if (textParseError.ThrowException)
  651. {
  652. throw textParseError.Exception;
  653. }
  654. }
  655. return null;
  656. }
  657. private string? DateTimeToString(DateTime d)
  658. {
  659. DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
  660. switch (SelectedDateFormat)
  661. {
  662. case CalendarDatePickerFormat.Short:
  663. return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.ShortDatePattern, dtfi));
  664. case CalendarDatePickerFormat.Long:
  665. return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.LongDatePattern, dtfi));
  666. case CalendarDatePickerFormat.Custom:
  667. return string.Format(CultureInfo.CurrentCulture, d.ToString(CustomDateFormatString, dtfi));
  668. }
  669. return null;
  670. }
  671. private bool ProcessDatePickerKey(KeyEventArgs e)
  672. {
  673. switch (e.Key)
  674. {
  675. case Key.Enter:
  676. {
  677. SetSelectedDate();
  678. return true;
  679. }
  680. case Key.Down:
  681. {
  682. if ((e.KeyModifiers & KeyModifiers.Control) == KeyModifiers.Control)
  683. {
  684. TogglePopUp();
  685. return true;
  686. }
  687. break;
  688. }
  689. }
  690. return false;
  691. }
  692. private void SetSelectedDate()
  693. {
  694. if (_textBox != null)
  695. {
  696. if (!string.IsNullOrEmpty(_textBox.Text))
  697. {
  698. string s = _textBox.Text;
  699. if (SelectedDate != null)
  700. {
  701. // If the string value of the SelectedDate and the
  702. // TextBox string value are equal, we do not parse the
  703. // string again if we do an extra parse, we lose data in
  704. // M/d/yy format.
  705. // ex: SelectedDate = DateTime(1008,12,19) but when
  706. // "12/19/08" is parsed it is interpreted as
  707. // DateTime(2008,12,19)
  708. string? selectedDate = DateTimeToString(SelectedDate.Value);
  709. if (selectedDate == s)
  710. {
  711. return;
  712. }
  713. }
  714. DateTime? d = SetTextBoxValue(s);
  715. if (SelectedDate != d)
  716. {
  717. SetCurrentValue(SelectedDateProperty, d);
  718. }
  719. }
  720. else
  721. {
  722. if (SelectedDate != null)
  723. {
  724. SetCurrentValue(SelectedDateProperty, null);
  725. }
  726. }
  727. }
  728. else
  729. {
  730. DateTime? d = SetTextBoxValue(_defaultText);
  731. if (SelectedDate != d)
  732. {
  733. SetCurrentValue(SelectedDateProperty, d);
  734. }
  735. }
  736. }
  737. private DateTime? SetTextBoxValue(string s)
  738. {
  739. if (string.IsNullOrEmpty(s))
  740. {
  741. SetValue(TextProperty, s);
  742. return SelectedDate;
  743. }
  744. else
  745. {
  746. DateTime? d = ParseText(s);
  747. if (d != null)
  748. {
  749. SetValue(TextProperty, s);
  750. return d;
  751. }
  752. else
  753. {
  754. // If parse error: TextBox should have the latest valid
  755. // SelectedDate value:
  756. if (SelectedDate != null)
  757. {
  758. string? newtext = this.DateTimeToString(SelectedDate.Value);
  759. SetValue(TextProperty, newtext);
  760. return SelectedDate;
  761. }
  762. else
  763. {
  764. SetWaterMarkText();
  765. return null;
  766. }
  767. }
  768. }
  769. }
  770. private void SetWaterMarkText()
  771. {
  772. if (_textBox != null)
  773. {
  774. if (string.IsNullOrEmpty(Watermark) && !UseFloatingWatermark)
  775. {
  776. DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
  777. SetCurrentValue(TextProperty, string.Empty);
  778. _defaultText = string.Empty;
  779. var watermarkFormat = "<{0}>";
  780. string watermarkText;
  781. switch (SelectedDateFormat)
  782. {
  783. case CalendarDatePickerFormat.Long:
  784. {
  785. watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.LongDatePattern.ToString());
  786. break;
  787. }
  788. case CalendarDatePickerFormat.Short:
  789. default:
  790. {
  791. watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.ShortDatePattern.ToString());
  792. break;
  793. }
  794. }
  795. _textBox.Watermark = watermarkText;
  796. }
  797. else
  798. {
  799. _textBox.ClearValue(TextBox.WatermarkProperty);
  800. }
  801. }
  802. }
  803. private static bool IsValidSelectedDateFormat(CalendarDatePickerFormat value)
  804. {
  805. return value == CalendarDatePickerFormat.Long
  806. || value == CalendarDatePickerFormat.Short
  807. || value == CalendarDatePickerFormat.Custom;
  808. }
  809. private static bool IsValidDateFormatString(string formatString)
  810. {
  811. return !string.IsNullOrWhiteSpace(formatString);
  812. }
  813. }
  814. }