SelectedDatesCollection.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  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 Avalonia.Threading;
  6. using System;
  7. using System.Collections.ObjectModel;
  8. using System.Threading;
  9. namespace Avalonia.Controls.Primitives
  10. {
  11. public sealed class SelectedDatesCollection : ObservableCollection<DateTime>
  12. {
  13. /// <summary>
  14. /// Inherited code: Requires comment.
  15. /// </summary>
  16. private Collection<DateTime> _addedItems;
  17. /// <summary>
  18. /// Inherited code: Requires comment.
  19. /// </summary>
  20. private bool _isCleared;
  21. /// <summary>
  22. /// Inherited code: Requires comment.
  23. /// </summary>
  24. private bool _isRangeAdded;
  25. /// <summary>
  26. /// Inherited code: Requires comment.
  27. /// </summary>
  28. private Calendar _owner;
  29. /// <summary>
  30. /// Initializes a new instance of the
  31. /// <see cref="T:Avalonia.Controls.Primitives.SelectedDatesCollection" />
  32. /// class.
  33. /// </summary>
  34. /// <param name="owner">
  35. /// The <see cref="T:Avalonia.Controls.Calendar" /> associated
  36. /// with this object.
  37. /// </param>
  38. public SelectedDatesCollection(Calendar owner)
  39. {
  40. _owner = owner;
  41. _addedItems = new Collection<DateTime>();
  42. }
  43. private void InvokeCollectionChanged(System.Collections.IList removedItems, System.Collections.IList addedItems)
  44. {
  45. _owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(null, addedItems, removedItems));
  46. }
  47. /// <summary>
  48. /// Adds all the dates in the specified range, which includes the first
  49. /// and last dates, to the collection.
  50. /// </summary>
  51. /// <param name="start">The first date to add to the collection.</param>
  52. /// <param name="end">The last date to add to the collection.</param>
  53. public void AddRange(DateTime start, DateTime end)
  54. {
  55. DateTime? rangeStart;
  56. // increment parameter specifies if the Days were selected in
  57. // Descending order or Ascending order based on this value, we add
  58. // the days in the range either in Ascending order or in Descending
  59. // order
  60. int increment = (DateTime.Compare(end, start) >= 0) ? 1 : -1;
  61. _addedItems.Clear();
  62. rangeStart = start;
  63. _isRangeAdded = true;
  64. if (_owner.IsMouseSelection)
  65. {
  66. // In Mouse Selection we allow the user to be able to add
  67. // multiple ranges in one action in MultipleRange Mode. In
  68. // SingleRange Mode, we only add the first selected range.
  69. while (rangeStart.HasValue && DateTime.Compare(end, rangeStart.Value) != -increment)
  70. {
  71. if (Calendar.IsValidDateSelection(_owner, rangeStart))
  72. {
  73. Add(rangeStart.Value);
  74. }
  75. else
  76. {
  77. if (_owner.SelectionMode == CalendarSelectionMode.SingleRange)
  78. {
  79. _owner.HoverEnd = rangeStart.Value.AddDays(-increment);
  80. break;
  81. }
  82. }
  83. rangeStart = DateTimeHelper.AddDays(rangeStart.Value, increment);
  84. }
  85. }
  86. else
  87. {
  88. // If CalendarSelectionMode.SingleRange and a user
  89. // programmatically tries to add multiple ranges, we will throw
  90. // away the old range and replace it with the new one. In order
  91. // to provide the removed items without an additional event, we
  92. // are calling ClearInternal
  93. if (_owner.SelectionMode == CalendarSelectionMode.SingleRange && Count > 0)
  94. {
  95. foreach (DateTime item in this)
  96. {
  97. _owner.RemovedItems.Add(item);
  98. }
  99. ClearInternal();
  100. }
  101. while (rangeStart.HasValue && DateTime.Compare(end, rangeStart.Value) != -increment)
  102. {
  103. Add(rangeStart.Value);
  104. rangeStart = DateTimeHelper.AddDays(rangeStart.Value, increment);
  105. }
  106. }
  107. _owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(null, _addedItems, _owner.RemovedItems));
  108. _owner.RemovedItems.Clear();
  109. _owner.UpdateMonths();
  110. _isRangeAdded = false;
  111. }
  112. /// <summary>
  113. /// Removes all items from the collection.
  114. /// </summary>
  115. /// <remarks>
  116. /// This implementation raises the CollectionChanged event.
  117. /// </remarks>
  118. protected override void ClearItems()
  119. {
  120. EnsureValidThread();
  121. Collection<DateTime> addedItems = new Collection<DateTime>();
  122. Collection<DateTime> removedItems = new Collection<DateTime>();
  123. foreach (DateTime item in this)
  124. {
  125. removedItems.Add(item);
  126. }
  127. base.ClearItems();
  128. // The event fires after SelectedDate changes
  129. if (_owner.SelectionMode != CalendarSelectionMode.None && _owner.SelectedDate != null)
  130. {
  131. _owner.SelectedDate = null;
  132. }
  133. if (removedItems.Count != 0)
  134. {
  135. InvokeCollectionChanged(removedItems, addedItems);
  136. }
  137. _owner.UpdateMonths();
  138. }
  139. /// <summary>
  140. /// Inserts an item into the collection at the specified index.
  141. /// </summary>
  142. /// <param name="index">
  143. /// The zero-based index at which item should be inserted.
  144. /// </param>
  145. /// <param name="item">The object to insert.</param>
  146. /// <remarks>
  147. /// This implementation raises the CollectionChanged event.
  148. /// </remarks>
  149. protected override void InsertItem(int index, DateTime item)
  150. {
  151. EnsureValidThread();
  152. if (!Contains(item))
  153. {
  154. Collection<DateTime> addedItems = new Collection<DateTime>();
  155. if (CheckSelectionMode())
  156. {
  157. if (Calendar.IsValidDateSelection(_owner, item))
  158. {
  159. // If the Collection is cleared since it is SingleRange
  160. // and it had another range set the index to 0
  161. if (_isCleared)
  162. {
  163. index = 0;
  164. _isCleared = false;
  165. }
  166. base.InsertItem(index, item);
  167. // The event fires after SelectedDate changes
  168. if (index == 0 && !(_owner.SelectedDate.HasValue && DateTime.Compare(_owner.SelectedDate.Value, item) == 0))
  169. {
  170. _owner.SelectedDate = item;
  171. }
  172. if (!_isRangeAdded)
  173. {
  174. addedItems.Add(item);
  175. InvokeCollectionChanged(_owner.RemovedItems, addedItems);
  176. _owner.RemovedItems.Clear();
  177. int monthDifference = DateTimeHelper.CompareYearMonth(item, _owner.DisplayDateInternal);
  178. if (monthDifference < 2 && monthDifference > -2)
  179. {
  180. _owner.UpdateMonths();
  181. }
  182. }
  183. else
  184. {
  185. _addedItems.Add(item);
  186. }
  187. }
  188. else
  189. {
  190. throw new ArgumentOutOfRangeException("SelectedDate value is not valid.");
  191. }
  192. }
  193. }
  194. }
  195. /// <summary>
  196. /// Removes the item at the specified index of the collection.
  197. /// </summary>
  198. /// <param name="index">
  199. /// The zero-based index of the element to remove.
  200. /// </param>
  201. /// <remarks>
  202. /// This implementation raises the CollectionChanged event.
  203. /// </remarks>
  204. protected override void RemoveItem(int index)
  205. {
  206. EnsureValidThread();
  207. if (index >= Count)
  208. {
  209. base.RemoveItem(index);
  210. }
  211. else
  212. {
  213. Collection<DateTime> addedItems = new Collection<DateTime>();
  214. Collection<DateTime> removedItems = new Collection<DateTime>();
  215. int monthDifference = DateTimeHelper.CompareYearMonth(this[index], _owner.DisplayDateInternal);
  216. removedItems.Add(this[index]);
  217. base.RemoveItem(index);
  218. // The event fires after SelectedDate changes
  219. if (index == 0)
  220. {
  221. if (Count > 0)
  222. {
  223. _owner.SelectedDate = this[0];
  224. }
  225. else
  226. {
  227. _owner.SelectedDate = null;
  228. }
  229. }
  230. InvokeCollectionChanged(removedItems, addedItems);
  231. if (monthDifference < 2 && monthDifference > -2)
  232. {
  233. _owner.UpdateMonths();
  234. }
  235. }
  236. }
  237. /// <summary>
  238. /// Replaces the element at the specified index.
  239. /// </summary>
  240. /// <param name="index">
  241. /// The zero-based index of the element to replace.
  242. /// </param>
  243. /// <param name="item">
  244. /// The new value for the element at the specified index.
  245. /// </param>
  246. /// <remarks>
  247. /// This implementation raises the CollectionChanged event.
  248. /// </remarks>
  249. protected override void SetItem(int index, DateTime item)
  250. {
  251. EnsureValidThread();
  252. if (!Contains(item))
  253. {
  254. Collection<DateTime> addedItems = new Collection<DateTime>();
  255. Collection<DateTime> removedItems = new Collection<DateTime>();
  256. if (index >= Count)
  257. {
  258. base.SetItem(index, item);
  259. }
  260. else
  261. {
  262. if (item != null && DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item))
  263. {
  264. removedItems.Add(this[index]);
  265. base.SetItem(index, item);
  266. addedItems.Add(item);
  267. // The event fires after SelectedDate changes
  268. if (index == 0 && !(_owner.SelectedDate.HasValue && DateTime.Compare(_owner.SelectedDate.Value, item) == 0))
  269. {
  270. _owner.SelectedDate = item;
  271. }
  272. InvokeCollectionChanged(removedItems, addedItems);
  273. int monthDifference = DateTimeHelper.CompareYearMonth(item, _owner.DisplayDateInternal);
  274. if (monthDifference < 2 && monthDifference > -2)
  275. {
  276. _owner.UpdateMonths();
  277. }
  278. }
  279. }
  280. }
  281. }
  282. internal void ClearInternal()
  283. {
  284. base.ClearItems();
  285. }
  286. private bool CheckSelectionMode()
  287. {
  288. if (_owner.SelectionMode == CalendarSelectionMode.None)
  289. {
  290. throw new InvalidOperationException("The SelectedDate property cannot be set when the selection mode is None.");
  291. }
  292. if (_owner.SelectionMode == CalendarSelectionMode.SingleDate && Count > 0)
  293. {
  294. throw new InvalidOperationException("The SelectedDates collection can be changed only in a multiple selection mode. Use the SelectedDate in a single selection mode.");
  295. }
  296. // if user tries to add an item into the SelectedDates in
  297. // SingleRange mode, we throw away the old range and replace it with
  298. // the new one in order to provide the removed items without an
  299. // additional event, we are calling ClearInternal
  300. if (_owner.SelectionMode == CalendarSelectionMode.SingleRange && !_isRangeAdded && Count > 0)
  301. {
  302. foreach (DateTime item in this)
  303. {
  304. _owner.RemovedItems.Add(item);
  305. }
  306. ClearInternal();
  307. _isCleared = true;
  308. }
  309. return true;
  310. }
  311. private void EnsureValidThread()
  312. {
  313. Dispatcher.UIThread.VerifyAccess();
  314. }
  315. }
  316. }