PageRenderTime 57ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-toolkit/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelector.cs

https://bitbucket.org/jeremejevs/milk-manager
C# | 1778 lines | 1112 code | 239 blank | 427 comment | 272 complexity | 29c7b77772068135a0cf8a5fee91be21 MD5 | raw file
  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;
  7. using System.Collections.Generic;
  8. using System.Collections.ObjectModel;
  9. using System.Collections.Specialized;
  10. using System.Diagnostics.CodeAnalysis;
  11. using System.Linq;
  12. using System.Windows;
  13. using System.Windows.Controls;
  14. using System.Windows.Media;
  15. using Microsoft.Phone.Controls.Primitives;
  16. namespace Microsoft.Phone.Controls
  17. {
  18. /// <summary>
  19. /// Represents a virtualizing list designed for grouped lists. Can also be
  20. /// used with flat lists.
  21. /// </summary>
  22. /// <QualityBand>Preview</QualityBand>
  23. [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "LongListSelector is a complicated control.")]
  24. [TemplatePart(Name = TemplatedListBoxName, Type = typeof(TemplatedListBox))]
  25. public partial class LongListSelector : Control
  26. {
  27. #region Constants
  28. /// <summary>
  29. /// The templated list box name.
  30. /// </summary>
  31. private const string TemplatedListBoxName = "TemplatedListBox";
  32. /// <summary>
  33. /// This constant is not actively used in the new version, however, the
  34. /// value is exposed through a deprecated property. For backward-
  35. /// compatibility only.
  36. /// </summary>
  37. private const double BufferSizeDefault = 1.0;
  38. /// <summary>
  39. /// The Scrolling state name.
  40. /// </summary>
  41. private const string ScrollingState = "Scrolling";
  42. /// <summary>
  43. /// The NotScrolling state name.
  44. /// </summary>
  45. private const string NotScrollingState = "NotScrolling";
  46. /// <summary>
  47. /// The vertical compression top state name.
  48. /// </summary>
  49. private const string CompressionTop = "CompressionTop";
  50. /// <summary>
  51. /// The vertical compression bottom state name.
  52. /// </summary>
  53. private const string CompressionBottom = "CompressionBottom";
  54. /// <summary>
  55. /// The absense of vertical compression state name.
  56. /// </summary>
  57. private const string NoVerticalCompression = "NoVerticalCompression";
  58. /// <summary>
  59. /// The vertical compression state name.
  60. /// </summary>
  61. private const string VerticalCompressionStateName = "VerticalCompression";
  62. /// <summary>
  63. /// The name of the scroll states visual state group.
  64. /// </summary>
  65. private const string ScrollStatesGroupName = "ScrollStates";
  66. #endregion
  67. /// <summary>
  68. /// Reference to the ListBox hosted in this control.
  69. /// </summary>
  70. private TemplatedListBox _listBox;
  71. /// <summary>
  72. /// Reference to the visual state group for scrolling.
  73. /// </summary>
  74. private VisualStateGroup _scrollGroup;
  75. /// <summary>
  76. /// Reference to the visual state group for vertical compression.
  77. /// </summary>
  78. private VisualStateGroup _verticalCompressionGroup;
  79. /// <summary>
  80. /// // Used to listen for changes in the ItemsSource
  81. /// (_rootCollection = ItemsSource as INotifyCollectionChanged).
  82. /// </summary>
  83. private INotifyCollectionChanged _rootCollection;
  84. /// <summary>
  85. /// Used to listen for changes in the groups within ItemsSource.
  86. /// </summary>
  87. private List<INotifyCollectionChanged> _groupCollections = new List<INotifyCollectionChanged>();
  88. /// <summary>
  89. /// Indicates the LLS is in the process of setting the SelectedItem back to its old value.
  90. /// </summary>
  91. private bool _isResettingSelectedItem = false;
  92. #region Properties
  93. /// <summary>
  94. /// Gets or sets whether the list is flat instead of a group hierarchy.
  95. /// </summary>
  96. public bool IsFlatList { get; set; }
  97. /// <summary>
  98. /// Gets or sets whether the list can be (temporarily) scrolled off of the ends.
  99. /// </summary>
  100. [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Backward compatible public setter.")]
  101. [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "value", Justification = "Backward compatible public setter.")]
  102. [Obsolete("IsBouncy is always set to true.")]
  103. public bool IsBouncy
  104. {
  105. get { return true; }
  106. set { }
  107. }
  108. /// <summary>
  109. /// Gets whether a list header is shown.
  110. /// </summary>
  111. private bool HasListHeader { get { return ListHeaderTemplate != null || ListHeader is UIElement; } }
  112. /// <summary>
  113. /// Gets whether a list footer is shown.
  114. /// </summary>
  115. private bool HasListFooter { get { return ListFooterTemplate != null || ListFooter is UIElement; } }
  116. /// <summary>
  117. /// Gets whether or not the user is manipulating the list, or if an inertial animation is taking place.
  118. /// </summary>
  119. public bool IsScrolling
  120. {
  121. get;
  122. private set;
  123. }
  124. /// <summary>
  125. /// Gets whether or not stretching is taking place.
  126. /// </summary>
  127. public bool IsStretching
  128. {
  129. get;
  130. private set;
  131. }
  132. #endregion
  133. #region Dependency Properties
  134. #region ItemsSource DependencyProperty
  135. /// <summary>
  136. /// The DataSource property. Where all of the items come from.
  137. /// </summary>
  138. public IEnumerable ItemsSource
  139. {
  140. get { return (IEnumerable)GetValue(ItemsSourceProperty); }
  141. set { SetValue(ItemsSourceProperty, value); }
  142. }
  143. /// <summary>
  144. /// The DataSource DependencyProperty.
  145. /// </summary>
  146. public static readonly DependencyProperty ItemsSourceProperty =
  147. DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(LongListSelector), new PropertyMetadata(null, OnItemsSourceChanged));
  148. private static void OnItemsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  149. {
  150. ((LongListSelector)obj).OnItemsSourceChanged();
  151. }
  152. #endregion
  153. #region SelectedItem DependencyProperty
  154. /// <summary>
  155. /// Gets or sets the selected item.
  156. /// </summary>
  157. public object SelectedItem
  158. {
  159. get { return GetValue(SelectedItemProperty); }
  160. set { SetValue(SelectedItemProperty, value); }
  161. }
  162. /// <summary>
  163. /// The SelectedItem DependencyProperty.
  164. /// </summary>
  165. public static readonly DependencyProperty SelectedItemProperty =
  166. DependencyProperty.Register("SelectedItem", typeof(object), typeof(LongListSelector), new PropertyMetadata(null, OnSelectedItemChanged));
  167. /// <summary>
  168. /// Called when the selected item changes
  169. /// </summary>
  170. private static void OnSelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  171. {
  172. var lls = ((LongListSelector)obj);
  173. bool shouldFireSelectionChanged = !lls._isResettingSelectedItem;
  174. // Set the selected item of the ListBox if necessary
  175. if (lls._listBox != null)
  176. {
  177. LongListSelectorItem selectedListBoxItem = lls._listBox.SelectedItem as LongListSelectorItem;
  178. if (e.NewValue == null)
  179. {
  180. lls._listBox.SelectedItem = null;
  181. }
  182. else if (selectedListBoxItem == null || e.NewValue != selectedListBoxItem.Item || selectedListBoxItem.ItemType != LongListSelectorItemType.Item)
  183. {
  184. bool itemFound = false;
  185. // Find the corresponding LLS item in the listbox.
  186. foreach (LongListSelectorItem item in lls._listBox.Items)
  187. {
  188. if (item.ItemType == LongListSelectorItemType.Item && item.Item == e.NewValue)
  189. {
  190. lls._listBox.SelectedItem = item;
  191. itemFound = true;
  192. break;
  193. }
  194. }
  195. // If the item doesn't exist in the list, select null
  196. if (!itemFound)
  197. {
  198. lls._isResettingSelectedItem = true;
  199. try
  200. {
  201. lls.SelectedItem = null;
  202. }
  203. finally
  204. {
  205. lls._isResettingSelectedItem = false;
  206. }
  207. }
  208. }
  209. }
  210. // Fire the SelectionChanged event
  211. if (shouldFireSelectionChanged && e.OldValue != lls.SelectedItem)
  212. {
  213. var handler = lls.SelectionChanged;
  214. if (handler != null)
  215. {
  216. var added = new List<object>();
  217. var removed = new List<object>();
  218. if (e.OldValue != null)
  219. {
  220. removed.Add(e.OldValue);
  221. }
  222. if (lls.SelectedItem != null)
  223. {
  224. added.Add(lls.SelectedItem);
  225. }
  226. handler(obj, new SelectionChangedEventArgs(removed, added));
  227. }
  228. }
  229. }
  230. #endregion
  231. #region ListHeader DependencyProperty
  232. /// <summary>
  233. /// The ListHeader property. Will be used as the first scrollItem in the list.
  234. /// </summary>
  235. public object ListHeader
  236. {
  237. get { return (object)GetValue(ListHeaderProperty); }
  238. set { SetValue(ListHeaderProperty, value); }
  239. }
  240. /// <summary>
  241. /// The ListHeader DependencyProperty.
  242. /// </summary>
  243. public static readonly DependencyProperty ListHeaderProperty =
  244. DependencyProperty.Register("ListHeader", typeof(object), typeof(LongListSelector), new PropertyMetadata(null));
  245. #endregion
  246. #region ListHeaderTemplate DependencyProperty
  247. /// <summary>
  248. /// The ListHeaderTemplate provides the template for the ListHeader.
  249. /// </summary>
  250. public DataTemplate ListHeaderTemplate
  251. {
  252. get { return (DataTemplate)GetValue(ListHeaderTemplateProperty); }
  253. set { SetValue(ListHeaderTemplateProperty, value); }
  254. }
  255. /// <summary>
  256. /// The ListHeaderTemplate DependencyProperty.
  257. /// </summary>
  258. public static readonly DependencyProperty ListHeaderTemplateProperty =
  259. DependencyProperty.Register("ListHeaderTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null, OnDataTemplateChanged));
  260. #endregion
  261. #region ListFooter DependencyProperty
  262. /// <summary>
  263. /// The ListFooter property. Will be used as the first scrollItem in the list.
  264. /// </summary>
  265. public object ListFooter
  266. {
  267. get { return (object)GetValue(ListFooterProperty); }
  268. set { SetValue(ListFooterProperty, value); }
  269. }
  270. /// <summary>
  271. /// The ListFooter DependencyProperty.
  272. /// </summary>
  273. public static readonly DependencyProperty ListFooterProperty =
  274. DependencyProperty.Register("ListFooter", typeof(object), typeof(LongListSelector), new PropertyMetadata(null));
  275. #endregion
  276. #region ListFooterTemplate DependencyProperty
  277. /// <summary>
  278. /// The ListFooterTemplate provides the template for the ListFooter.
  279. /// </summary>
  280. public DataTemplate ListFooterTemplate
  281. {
  282. get { return (DataTemplate)GetValue(ListFooterTemplateProperty); }
  283. set { SetValue(ListFooterTemplateProperty, value); }
  284. }
  285. /// <summary>
  286. /// The ListFooterTemplate DependencyProperty.
  287. /// </summary>
  288. public static readonly DependencyProperty ListFooterTemplateProperty =
  289. DependencyProperty.Register("ListFooterTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null, OnDataTemplateChanged));
  290. #endregion
  291. #region GroupHeaderTemplate DependencyProperty
  292. /// <summary>
  293. /// The GroupHeaderTemplate provides the template for the groups in the items view.
  294. /// </summary>
  295. public DataTemplate GroupHeaderTemplate
  296. {
  297. get { return (DataTemplate)GetValue(GroupHeaderProperty); }
  298. set { SetValue(GroupHeaderProperty, value); }
  299. }
  300. /// <summary>
  301. /// The GroupHeaderTemplate DependencyProperty.
  302. /// </summary>
  303. public static readonly DependencyProperty GroupHeaderProperty =
  304. DependencyProperty.Register("GroupHeaderTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null, OnDataTemplateChanged));
  305. #endregion
  306. #region GroupFooterTemplate DependencyProperty
  307. /// <summary>
  308. /// The GroupFooterTemplate provides the template for the groups in the items view.
  309. /// </summary>
  310. public DataTemplate GroupFooterTemplate
  311. {
  312. get { return (DataTemplate)GetValue(GroupFooterProperty); }
  313. set { SetValue(GroupFooterProperty, value); }
  314. }
  315. /// <summary>
  316. /// The GroupFooterTemplate DependencyProperty.
  317. /// </summary>
  318. public static readonly DependencyProperty GroupFooterProperty =
  319. DependencyProperty.Register("GroupFooterTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null, OnDataTemplateChanged));
  320. #endregion
  321. #region ItemTemplate DependencyProperty
  322. /// <summary>
  323. /// The ItemTemplate provides the template for the items in the items view.
  324. /// </summary>
  325. public DataTemplate ItemTemplate
  326. {
  327. get { return (DataTemplate)GetValue(ItemsTemplateProperty); }
  328. set { SetValue(ItemsTemplateProperty, value); }
  329. }
  330. /// <summary>
  331. /// The ItemTemplate DependencyProperty.
  332. /// </summary>
  333. public static readonly DependencyProperty ItemsTemplateProperty =
  334. DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null, OnDataTemplateChanged));
  335. #endregion
  336. #region DisplayAllGroups DependencyProperty
  337. /// <summary>
  338. /// Display all groups whether or not they have items.
  339. /// </summary>
  340. public bool DisplayAllGroups
  341. {
  342. get { return (bool)GetValue(DisplayAllGroupsProperty); }
  343. set { SetValue(DisplayAllGroupsProperty, value); }
  344. }
  345. /// <summary>
  346. /// DisplayAllGroups DependencyProperty
  347. /// </summary>
  348. public static readonly DependencyProperty DisplayAllGroupsProperty =
  349. DependencyProperty.Register("DisplayAllGroups", typeof(bool), typeof(LongListSelector), new PropertyMetadata(false, OnDisplayAllGroupsChanged));
  350. #endregion
  351. #region GroupItemTemplate DependencyProperty
  352. /// <summary>
  353. /// The GroupItemTemplate specifies the template that will be used in group view mode.
  354. /// </summary>
  355. public DataTemplate GroupItemTemplate
  356. {
  357. get { return (DataTemplate)GetValue(GroupItemTemplateProperty); }
  358. set { SetValue(GroupItemTemplateProperty, value); }
  359. }
  360. /// <summary>
  361. /// The GroupItemTemplate DependencyProperty.
  362. /// </summary>
  363. public static readonly DependencyProperty GroupItemTemplateProperty =
  364. DependencyProperty.Register("GroupItemTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null));
  365. #endregion
  366. #region GroupItemsPanel DependencyProperty
  367. /// <summary>
  368. /// The GroupItemsPanel specifies the panel that will be used in group view mode.
  369. /// </summary>
  370. public ItemsPanelTemplate GroupItemsPanel
  371. {
  372. get { return (ItemsPanelTemplate)GetValue(GroupItemsPanelProperty); }
  373. set { SetValue(GroupItemsPanelProperty, value); }
  374. }
  375. /// <summary>
  376. /// The GroupItemsPanel DependencyProperty.
  377. /// </summary>
  378. public static readonly DependencyProperty GroupItemsPanelProperty =
  379. DependencyProperty.Register("GroupItemsPanel", typeof(ItemsPanelTemplate), typeof(LongListSelector), new PropertyMetadata(null));
  380. #endregion
  381. #region BufferSize DependencyProperty
  382. /// <summary>
  383. /// The number of "screens" (as defined by the ActualHeight of the LongListSelector) above and below the visible
  384. /// items of the list that will be filled with items.
  385. /// </summary>
  386. [Obsolete("BufferSize no longer affect items virtualization")]
  387. public double BufferSize
  388. {
  389. get { return (double)GetValue(BufferSizeProperty); }
  390. set { SetValue(BufferSizeProperty, value); }
  391. }
  392. /// <summary>
  393. /// The BufferSize DependencyProperty
  394. /// </summary>
  395. [Obsolete("BufferSizeProperty no longer affect items virtualization")]
  396. public static readonly DependencyProperty BufferSizeProperty =
  397. DependencyProperty.Register("BufferSize", typeof(double), typeof(LongListSelector), new PropertyMetadata(BufferSizeDefault, OnBufferSizeChanged));
  398. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
  399. private static void OnBufferSizeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  400. {
  401. double newValue = (double)e.NewValue;
  402. if (newValue < 0)
  403. {
  404. throw new ArgumentOutOfRangeException("BufferSize");
  405. }
  406. }
  407. #endregion
  408. #region MaximumFlickVelocity DependencyProperty
  409. /// <summary>
  410. /// The maximum velocity for flicks, in pixels per second.
  411. /// </summary>
  412. [Obsolete("MaximumFlickVelocity is not used anymore.")]
  413. public double MaximumFlickVelocity
  414. {
  415. get { return (double)GetValue(MaximumFlickVelocityProperty); }
  416. set { SetValue(MaximumFlickVelocityProperty, value); }
  417. }
  418. /// <summary>
  419. /// The MaximumFlickVelocity DependencyProperty.
  420. /// </summary>
  421. [Obsolete("MaximumFlickVelocityProperty is not used anymore.")]
  422. public static readonly DependencyProperty MaximumFlickVelocityProperty =
  423. DependencyProperty.Register("MaximumFlickVelocity", typeof(double), typeof(LongListSelector), new PropertyMetadata(MotionParameters.MaximumSpeed));
  424. #endregion
  425. #region ShowListHeader DependencyProperty
  426. /// <summary>
  427. /// Controls whether or not the ListHeader is shown.
  428. /// </summary>
  429. public bool ShowListHeader
  430. {
  431. get { return (bool)GetValue(ShowListHeaderProperty); }
  432. set { SetValue(ShowListHeaderProperty, value); }
  433. }
  434. /// <summary>
  435. /// The ShowListHeader DependencyProperty.
  436. /// </summary>
  437. public static readonly DependencyProperty ShowListHeaderProperty =
  438. DependencyProperty.Register("ShowListHeader", typeof(bool), typeof(LongListSelector), new PropertyMetadata(true, OnShowListHeaderChanged));
  439. private static void OnShowListHeaderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  440. {
  441. LongListSelector control = (LongListSelector)obj;
  442. if (control._listBox != null)
  443. {
  444. Collection<LongListSelectorItem> tuples = (Collection<LongListSelectorItem>)control._listBox.ItemsSource;
  445. if (control.ShowListHeader)
  446. {
  447. control.AddListHeader(tuples);
  448. }
  449. else
  450. {
  451. RemoveListHeader(tuples);
  452. }
  453. }
  454. }
  455. #endregion
  456. #region ShowListFooter DependencyProperty
  457. /// <summary>
  458. /// Controls whether or not the ListFooter is shown.
  459. /// </summary>
  460. public bool ShowListFooter
  461. {
  462. get { return (bool)GetValue(ShowListFooterProperty); }
  463. set { SetValue(ShowListFooterProperty, value); }
  464. }
  465. /// <summary>
  466. /// The ShowListFooter DependencyProperty.
  467. /// </summary>
  468. public static readonly DependencyProperty ShowListFooterProperty =
  469. DependencyProperty.Register("ShowListFooter", typeof(bool), typeof(LongListSelector), new PropertyMetadata(true, OnShowListFooterChanged));
  470. private static void OnShowListFooterChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  471. {
  472. LongListSelector control = (LongListSelector)obj;
  473. if (control._listBox != null)
  474. {
  475. Collection<LongListSelectorItem> tuples = (Collection<LongListSelectorItem>)control._listBox.ItemsSource;
  476. if (control.ShowListFooter)
  477. {
  478. control.AddListFooter(tuples);
  479. }
  480. else
  481. {
  482. RemoveListFooter(tuples);
  483. }
  484. }
  485. }
  486. #endregion
  487. #endregion
  488. #region Events
  489. /// <summary>
  490. /// Occurs when the currently selected item changes.
  491. /// </summary>
  492. public event SelectionChangedEventHandler SelectionChanged;
  493. /// <summary>
  494. /// Occurs when this control starts scrolling.
  495. /// </summary>
  496. public event EventHandler ScrollingStarted;
  497. /// <summary>
  498. /// Occurs when this control stops scrolling.
  499. /// </summary>
  500. public event EventHandler ScrollingCompleted;
  501. /// <summary>
  502. /// Occurs when the group Popup's IsOpen has been set to true.
  503. /// </summary>
  504. public event EventHandler<GroupViewOpenedEventArgs> GroupViewOpened;
  505. /// <summary>
  506. /// Occurs when the group popup is about to close.
  507. /// </summary>
  508. public event EventHandler<GroupViewClosingEventArgs> GroupViewClosing;
  509. /// <summary>
  510. /// Occurs when an item is about to be "realized".
  511. /// </summary>
  512. public event EventHandler<LinkUnlinkEventArgs> Link;
  513. /// <summary>
  514. /// Occurs when an item is about to be "un-realized".
  515. /// </summary>
  516. public event EventHandler<LinkUnlinkEventArgs> Unlink;
  517. /// <summary>
  518. /// Occurs when the user has dragged the items up from the bottom as far as they can go.
  519. /// </summary>
  520. public event EventHandler StretchingBottom;
  521. /// <summary>
  522. /// Occurs when the user is no longer stretching.
  523. /// </summary>
  524. public event EventHandler StretchingCompleted;
  525. /// <summary>
  526. /// Occurs when the user has dragged the items down from the top as far as they can go.
  527. /// </summary>
  528. public event EventHandler StretchingTop;
  529. #endregion
  530. #region Constructor
  531. /// <summary>
  532. /// Initializes a new instance of <see cref="LongListSelector"/>.
  533. /// </summary>
  534. public LongListSelector()
  535. {
  536. DefaultStyleKey = typeof(LongListSelector);
  537. }
  538. #endregion
  539. //
  540. // Public methods
  541. //
  542. #region ScrollTo(...)
  543. /// <summary>
  544. /// Instantly jump to the specified item.
  545. /// </summary>
  546. /// <param name="item">Item to jump to.</param>
  547. public void ScrollTo(object item)
  548. {
  549. if (_listBox != null && item != null)
  550. {
  551. ObservableCollection<LongListSelectorItem> tuples = (ObservableCollection<LongListSelectorItem>)_listBox.ItemsSource;
  552. LongListSelectorItem lastTuple = tuples[tuples.Count - 1];
  553. _listBox.ScrollIntoView(lastTuple);
  554. UpdateLayout();
  555. foreach (LongListSelectorItem tuple in _listBox.ItemsSource)
  556. {
  557. if (tuple.Item != null && tuple.Item.Equals(item))
  558. {
  559. _listBox.ScrollIntoView(tuple);
  560. break;
  561. }
  562. }
  563. }
  564. }
  565. #endregion
  566. #region ScrollToGroup(...)
  567. /// <summary>
  568. /// Instantly jump to the specified group.
  569. /// </summary>
  570. /// <param name="group">Group to jump to.</param>
  571. public void ScrollToGroup(object group)
  572. {
  573. ScrollTo(group);
  574. }
  575. #endregion
  576. #region DisplayGroupView()
  577. /// <summary>
  578. /// Invokes the group view if a GroupItemTemplate has been defined.
  579. /// </summary>
  580. public void DisplayGroupView()
  581. {
  582. if (GroupItemTemplate != null && !IsFlatList)
  583. {
  584. OpenPopup();
  585. }
  586. }
  587. #endregion
  588. #region CloseGroupView()
  589. /// <summary>
  590. /// Closes the group view unconditionally.
  591. /// </summary>
  592. /// <remarks>Does not raise the GroupViewClosingEventArgs event.</remarks>
  593. public void CloseGroupView()
  594. {
  595. ClosePopup(null, false);
  596. }
  597. #endregion
  598. // Obsolete:
  599. #region AnimateTo(...)
  600. /// <summary>
  601. /// Animate the scrolling of the list to the specified item.
  602. /// </summary>
  603. /// <param name="item">Item to scroll to.</param>
  604. [Obsolete("AnimateTo(...) call ScrollTo(...) to jump without animation to the given item.")]
  605. public void AnimateTo(object item)
  606. {
  607. ScrollTo(item);
  608. }
  609. #endregion
  610. /// <summary>
  611. /// Returns either containers or items for either all items with
  612. /// containers or just the visible ones, as specified by the
  613. /// parameters.
  614. /// </summary>
  615. /// <param name="onlyItemsInView">When true, will return values for
  616. /// only items that are in view.</param>
  617. /// <param name="getContainers">When true, will return the containers
  618. /// rather than the items.</param>
  619. /// <returns>Returns either containers or items for either all items
  620. /// with containers or just the visible ones, as specified by the
  621. /// parameters. </returns>
  622. [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "getContainers", Justification = "This is an obsolete old method that cannot change now.")]
  623. [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "onlyItemsInView", Justification = "This is an obsolete old method that cannot change now.")]
  624. [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This is an obsolete old method that cannot change now.")]
  625. [Obsolete("GetItemsWithContainers(...) always returns an empty collection of items. Rely on Link/Unlink to know an item get realized or unrealized.")]
  626. public ICollection<Object> GetItemsWithContainers(bool onlyItemsInView, bool getContainers)
  627. {
  628. return new Collection<Object>();
  629. }
  630. #region GetItemsInView()
  631. /// <summary>
  632. /// Returns all of the items that are currently in view.
  633. /// This is not the same as the items that have associated visual elements: there are usually some visuals offscreen.
  634. /// This might be an empty list if scrolling is happening too quickly.
  635. /// </summary>
  636. /// <returns>Returns all of the items that are currently in view.</returns>
  637. [Obsolete("GetItemsInView() always returns an empty collection of items. Rely on Link/Unlink to know an item get realized or unrealized.")]
  638. public ICollection<Object> GetItemsInView()
  639. {
  640. return GetItemsWithContainers(true, false);
  641. }
  642. #endregion
  643. #region OnItemsSourceChanged()
  644. /// <summary>
  645. /// Called when the ItemsSource dependency property changes.
  646. /// </summary>
  647. private void OnItemsSourceChanged()
  648. {
  649. // Reload the whole list.
  650. LoadDataIntoListBox();
  651. }
  652. #endregion
  653. #region OnApplyTemplate()
  654. /// <summary>
  655. /// Called whenever a template gets applied on this control.
  656. /// </summary>
  657. public override void OnApplyTemplate()
  658. {
  659. base.OnApplyTemplate();
  660. // Unsubscribe from events we registered for in the past.
  661. if (_listBox != null)
  662. {
  663. _listBox.SelectionChanged -= OnListBoxSelectionChanged;
  664. _listBox.Link -= OnLink;
  665. _listBox.Unlink -= OnUnlink;
  666. }
  667. if (_scrollGroup != null)
  668. {
  669. _scrollGroup.CurrentStateChanging -= OnScrollStateChanging;
  670. }
  671. // Locates and setup the TemplatedListBox in the template. If no TemplatedListBox is found, we
  672. // initialize one.
  673. _listBox = GetTemplateChild(TemplatedListBoxName) as TemplatedListBox ?? new TemplatedListBox();
  674. _listBox.ListHeaderTemplate = ListHeaderTemplate;
  675. _listBox.ListFooterTemplate = ListFooterTemplate;
  676. _listBox.GroupHeaderTemplate = GroupHeaderTemplate;
  677. _listBox.GroupFooterTemplate = GroupFooterTemplate;
  678. _listBox.ItemTemplate = ItemTemplate;
  679. _listBox.SelectionChanged += OnListBoxSelectionChanged;
  680. _listBox.Link += OnLink;
  681. _listBox.Unlink += OnUnlink;
  682. // Retrieves the ScrollViewer of the list box.
  683. ScrollViewer sv = _listBox.GetFirstLogicalChildByType<ScrollViewer>(true);
  684. if (sv != null)
  685. {
  686. // Visual States are always on the first child of the control template
  687. FrameworkElement element = VisualTreeHelper.GetChild(sv, 0) as FrameworkElement;
  688. if (element != null)
  689. {
  690. _scrollGroup = VisualStates.TryGetVisualStateGroup(sv, ScrollStatesGroupName);
  691. if (_scrollGroup != null)
  692. {
  693. _scrollGroup.CurrentStateChanging += OnScrollStateChanging;
  694. }
  695. _verticalCompressionGroup = VisualStates.TryGetVisualStateGroup(sv, VerticalCompressionStateName);
  696. if(_verticalCompressionGroup != null)
  697. {
  698. _verticalCompressionGroup.CurrentStateChanging += OnStretchStateChanging;
  699. }
  700. }
  701. }
  702. LoadDataIntoListBox();
  703. }
  704. #endregion
  705. #region LoadDataIntoListBox()
  706. /// <summary>
  707. /// Loads ItemsSource into the hosted list box.
  708. /// </summary>
  709. private void LoadDataIntoListBox()
  710. {
  711. if (_listBox != null)
  712. {
  713. ObservableCollection<LongListSelectorItem> tuples = new ObservableCollection<LongListSelectorItem>();
  714. AddListHeader(tuples);
  715. UnsubscribeFromAllCollections();
  716. // if it's a flat list, add the items without grouping.
  717. if (IsFlatList)
  718. {
  719. if (ItemsSource != null)
  720. {
  721. foreach (object item in ItemsSource)
  722. {
  723. tuples.Add(new LongListSelectorItem() { Item = item, ItemType = LongListSelectorItemType.Item });
  724. }
  725. }
  726. }
  727. // Otherwise, apply the grouping logic.
  728. else
  729. {
  730. IEnumerable groups = ItemsSource;
  731. if (groups != null)
  732. {
  733. foreach (object group in groups)
  734. {
  735. AddGroup(group, tuples);
  736. }
  737. }
  738. }
  739. AddListFooter(tuples);
  740. _rootCollection = ItemsSource as INotifyCollectionChanged;
  741. if (_rootCollection != null)
  742. {
  743. _rootCollection.CollectionChanged += OnCollectionChanged;
  744. }
  745. _listBox.ItemsSource = tuples;
  746. }
  747. }
  748. #endregion
  749. #region List/Footer Headers
  750. /// <summary>
  751. /// Adds a list header to the given list.
  752. /// </summary>
  753. private void AddListHeader(IList<LongListSelectorItem> tuples)
  754. {
  755. if (HasListHeader && ShowListHeader && // Adds the list header if it got a template or if it's a UI element itself.
  756. (tuples.Count == 0 || tuples[0].ItemType != LongListSelectorItemType.ListHeader)) // Also, make sure its not already there
  757. {
  758. tuples.Insert(0, new LongListSelectorItem() { Item = ListHeader, ItemType = LongListSelectorItemType.ListHeader });
  759. }
  760. }
  761. /// <summary>
  762. /// Adds a list header to the listbox.
  763. /// </summary>
  764. private void AddListHeader()
  765. {
  766. AddListHeader((ObservableCollection<LongListSelectorItem>)_listBox.ItemsSource);
  767. }
  768. /// <summary>
  769. /// Removes the list header from the given list.
  770. /// </summary>
  771. private static void RemoveListHeader(IList<LongListSelectorItem> tuples)
  772. {
  773. if (tuples.Count > 0 && tuples[0].ItemType == LongListSelectorItemType.ListHeader)
  774. {
  775. tuples.RemoveAt(0);
  776. }
  777. }
  778. /// <summary>
  779. /// Removes the list header from the listbox.
  780. /// </summary>
  781. private void RemoveListHeader()
  782. {
  783. RemoveListHeader((ObservableCollection<LongListSelectorItem>)_listBox.ItemsSource);
  784. }
  785. /// <summary>
  786. /// Adds a list footer to the given list.
  787. /// </summary>
  788. private void AddListFooter(IList<LongListSelectorItem> tuples)
  789. {
  790. if (HasListFooter && ShowListFooter && // Adds the list footer if it got a template or if it's a UI element itself.
  791. (tuples.Count == 0 || tuples[tuples.Count - 1].ItemType != LongListSelectorItemType.ListFooter)) // Also, make sure its not already there
  792. {
  793. tuples.Add(new LongListSelectorItem() { Item = ListFooter, ItemType = LongListSelectorItemType.ListFooter });
  794. }
  795. }
  796. /// <summary>
  797. /// Adds a list footer to the listbox.
  798. /// </summary>
  799. private void AddListFooter()
  800. {
  801. AddListFooter((ObservableCollection<LongListSelectorItem>)_listBox.ItemsSource);
  802. }
  803. /// <summary>
  804. /// Removes the list footer from the given list.
  805. /// </summary>
  806. private static void RemoveListFooter(IList<LongListSelectorItem> tuples)
  807. {
  808. LongListSelectorItem lastTuple = tuples[tuples.Count - 1];
  809. if (lastTuple != null && lastTuple.ItemType == LongListSelectorItemType.ListFooter)
  810. {
  811. tuples.RemoveAt(tuples.Count - 1);
  812. }
  813. }
  814. /// <summary>
  815. /// Removes the list footer from the listbox.
  816. /// </summary>
  817. private void RemoveListFooter()
  818. {
  819. RemoveListFooter((ObservableCollection<LongListSelectorItem>)_listBox.ItemsSource);
  820. }
  821. #endregion
  822. #region AddGroup(...)
  823. /// <summary>
  824. /// Adds a group to a list of tuples.
  825. /// </summary>
  826. /// <param name="groupToAdd">Group to add.</param>
  827. /// <param name="tuples">List to which the method will add the group.</param>
  828. private void AddGroup(object groupToAdd, IList tuples)
  829. {
  830. IEnumerable group = groupToAdd as IEnumerable;
  831. if (group != null)
  832. {
  833. bool groupHasItems = false;
  834. // Adds the group header
  835. if (GroupHeaderTemplate != null)
  836. {
  837. tuples.Add(new LongListSelectorItem() { Item = group, ItemType = LongListSelectorItemType.GroupHeader });
  838. }
  839. // For each group header, add its items
  840. foreach (object item in group)
  841. {
  842. tuples.Add(new LongListSelectorItem() { Item = item, ItemType = LongListSelectorItemType.Item, Group = group });
  843. groupHasItems = true;
  844. }
  845. // Add the group footer if the group has items or if we must display all groups whether or not they have items.
  846. if (groupHasItems || DisplayAllGroups)
  847. {
  848. if (GroupFooterTemplate != null)
  849. {
  850. tuples.Add(new LongListSelectorItem() { Item = group, ItemType = LongListSelectorItemType.GroupFooter });
  851. }
  852. }
  853. // Otherwise, remove the group header
  854. else if (GroupHeaderTemplate != null)
  855. {
  856. tuples.RemoveAt(tuples.Count - 1);
  857. }
  858. // Subscribe to collection change event
  859. INotifyCollectionChanged groupCollection = group as INotifyCollectionChanged;
  860. if (groupCollection != null && !_groupCollections.Contains(groupCollection))
  861. {
  862. groupCollection.CollectionChanged += OnCollectionChanged;
  863. _groupCollections.Add(groupCollection);
  864. }
  865. }
  866. }
  867. #endregion
  868. #region AddGroupHeadersAndFooters(...)
  869. /// <summary>
  870. /// Adds group headers or footers after their template switch from being null
  871. /// to an actual value.
  872. /// </summary>
  873. /// <param name="addHeaders">Specifies whether or not to add group headers.</param>
  874. /// <param name="addFooters">Specifies whether or not to add group footers.</param>
  875. /// <remarks>Used only when templates for group headers/footers switch from being null.
  876. /// In this case, they must be added to the lisbox if a group is not empty or DisplayAllGroups is true.
  877. /// For performance reasons, this method makes an assumption that headers/footers are not already present.
  878. /// Which is the case when its called from OnDataTemplateChanged.</remarks>
  879. private void AddGroupHeadersAndFooters(bool addHeaders, bool addFooters)
  880. {
  881. int indexInListBox = 0;
  882. if (HasListHeader && ShowListHeader)
  883. {
  884. ++indexInListBox;
  885. }
  886. IEnumerable groups = ItemsSource;
  887. ObservableCollection<LongListSelectorItem> tuples = (ObservableCollection<LongListSelectorItem>)_listBox.ItemsSource;
  888. if (groups != null)
  889. {
  890. foreach (object group in groups)
  891. {
  892. var groupAsEnumerable = group as IEnumerable;
  893. if (groupAsEnumerable != null)
  894. {
  895. int itemsCount = GetHeadersAndItemsCountFromGroup(groupAsEnumerable);
  896. // Adds the group header
  897. if (addHeaders && GroupHeaderTemplate != null && itemsCount > 0)
  898. {
  899. tuples.Insert(indexInListBox, new LongListSelectorItem
  900. {
  901. Item = group,
  902. ItemType = LongListSelectorItemType.GroupHeader
  903. });
  904. }
  905. indexInListBox += itemsCount;
  906. // Adds the group footer
  907. if (addFooters && GroupFooterTemplate != null && itemsCount > 0)
  908. {
  909. tuples.Insert(indexInListBox - 1, new LongListSelectorItem
  910. {
  911. Item = group,
  912. ItemType = LongListSelectorItemType.GroupFooter
  913. });
  914. }
  915. }
  916. }
  917. }
  918. }
  919. /// <summary>
  920. /// Adds group headers after the GroupHeaderTeamplate switch from being null
  921. /// to an actual value.
  922. /// </summary>
  923. private void AddGroupHeaders()
  924. {
  925. AddGroupHeadersAndFooters(true, false);
  926. }
  927. /// <summary>
  928. /// Adds group headers after the GroupFooterTeamplate switch from being null
  929. /// to an actual value.
  930. /// </summary>
  931. private void AddGroupFooters()
  932. {
  933. AddGroupHeadersAndFooters(false, true);
  934. }
  935. #endregion
  936. #region RemoveAllGroupHeadersAndFooters(...)
  937. /// <summary>
  938. /// Removes group headers or footers after their template becomes null.
  939. /// </summary>
  940. /// <param name="removeHeaders">Specifies whether or not to remove group headers.</param>
  941. /// <param name="removeFooters">Specifies whether or not to remove group footers.</param>
  942. private void RemoveAllGroupHeadersAndFooters(bool removeHeaders, bool removeFooters)
  943. {
  944. ObservableCollection<LongListSelectorItem> tuples = (ObservableCollection<LongListSelectorItem>)_listBox.ItemsSource;
  945. for (int i = 0; i < tuples.Count; ++i)
  946. {
  947. if ((removeHeaders && tuples[i].ItemType == LongListSelectorItemType.GroupHeader) ||
  948. (removeFooters && tuples[i].ItemType == LongListSelectorItemType.GroupFooter))
  949. {
  950. tuples.RemoveAt(i--); // the -- is there so we don't skip tuples
  951. }
  952. }
  953. }
  954. private void RemoveAllGroupHeaders()
  955. {
  956. RemoveAllGroupHeadersAndFooters(true, false);
  957. }
  958. private void RemoveAllGroupFooters()
  959. {
  960. RemoveAllGroupHeadersAndFooters(false, true);
  961. }
  962. #endregion
  963. #region UnsubscribeFromAllCollections()
  964. /// <summary>
  965. /// Unsubscrives from every collection in ItemsSource.
  966. /// </summary>
  967. private void UnsubscribeFromAllCollections()
  968. {
  969. if (_rootCollection != null)
  970. {
  971. _rootCollection.CollectionChanged -= OnCollectionChanged;
  972. }
  973. foreach (INotifyCollectionChanged collection in _groupCollections)
  974. {
  975. collection.CollectionChanged -= OnCollectionChanged;
  976. }
  977. _rootCollection = null;
  978. _groupCollections.Clear();
  979. }
  980. #endregion
  981. #region InsertNewGroups(...)
  982. /// <summary>
  983. /// Inserts new groups in the list box.
  984. /// </summary>
  985. /// <param name="newGroups">List of the new groups to insert.</param>
  986. /// <param name="newGroupsIndex">Insertion index relative to the collection.</param>
  987. private void InsertNewGroups(IList newGroups, int newGroupsIndex)
  988. {
  989. ObservableCollection<LongListSelectorItem> tuples = (ObservableCollection<LongListSelectorItem>)_listBox.ItemsSource;
  990. // 1 - Builds items tuples for the new groups
  991. List<LongListSelectorItem> newGroupsTuples = new List<LongListSelectorItem>();
  992. foreach (object group in newGroups)
  993. {
  994. AddGroup(group, newGroupsTuples);
  995. }
  996. if (newGroupsTuples.Count > 0)
  997. {
  998. // 2 - Finds insertion index in the list box
  999. int insertIndex = GetGroupIndexInListBox(newGroupsIndex);
  1000. // 3 - Inserts the new items into the list box
  1001. foreach (LongListSelectorItem tuple in newGroupsTuples)
  1002. {
  1003. tuples.Insert(insertIndex++, tuple);
  1004. }
  1005. }
  1006. }
  1007. #endregion
  1008. #region InsertNewItems(...)
  1009. /// <summary>
  1010. /// Inserts new items in the list box.
  1011. /// </summary>
  1012. /// <param name="newItems">List of new items to insert.</param>
  1013. /// <param name="newItemsIndex">Insertion index relative to the collection</param>
  1014. /// <param name="group">Group into which the items are inserted. Can be null if IsFlatList == true</param>
  1015. private void InsertNewItems(IList newItems, int newItemsIndex, IEnumerable group)
  1016. {
  1017. ObservableCollection<LongListSelectorItem> tuples = (ObservableCollection<LongListSelectorItem>)_listBox.ItemsSource;
  1018. // 1 - Builds items tuples for the new items
  1019. List<LongListSelectorItem> newItemsTuples = new List<LongListSelectorItem>();
  1020. foreach (object item in newItems)
  1021. {
  1022. newItemsTuples.Add(new LongListSelectorItem
  1023. {
  1024. Group = group,
  1025. Item = item,
  1026. ItemType = LongListSelectorItemType.Item
  1027. });
  1028. }
  1029. // 2 - Finds the insertion index in the listbox
  1030. // Since a single group might be referenced by more than one, we might need to update more than one spot in the listbox
  1031. if (group != null)
  1032. {
  1033. int i = 0;
  1034. bool groupWasNotDisplayed = ((IList)group).Count == newItems.Count && !DisplayAllGroups;
  1035. foreach (object g in ItemsSource)
  1036. {
  1037. if (g == group)
  1038. {
  1039. int insertIndex = GetGroupIndexInListBox(i);
  1040. if (GroupHeaderTemplate != null)
  1041. {
  1042. if (groupWasNotDisplayed)
  1043. {
  1044. tuples.Insert(insertIndex, new LongListSelectorItem() { ItemType = LongListSelectorItemType.GroupHeader, Item = group });
  1045. }
  1046. ++insertIndex;
  1047. }
  1048. insertIndex += newItemsIndex;
  1049. // 3 - Inserts the new items into the list box
  1050. foreach (LongListSelectorItem tuple in newItemsTuples)
  1051. {
  1052. tuples.Insert(insertIndex++, tuple);
  1053. }
  1054. if (groupWasNotDisplayed && GroupFooterTemplate != null)
  1055. {
  1056. tuples.Insert(insertIndex++, new LongListSelectorItem() { ItemType = LongListSelectorItemType.GroupFooter, Item = group });
  1057. }
  1058. }
  1059. ++i;
  1060. }
  1061. }
  1062. else
  1063. {
  1064. int insertIndex = newItemsIndex;
  1065. if (HasListHeader && ShowListHeader)
  1066. {
  1067. ++insertIndex;
  1068. }
  1069. // 3 - Inserts the new items into the list box
  1070. foreach (LongListSelectorItem tuple in newItemsTuples)
  1071. {
  1072. tuples.Insert(insertIndex++, tuple);
  1073. }
  1074. }
  1075. }
  1076. #endregion
  1077. #region RemoveOldGroups(...)
  1078. /// <summary>
  1079. /// Removes groups from the list box.
  1080. /// </summary>
  1081. /// <param name="oldGroups">List of groups to remove.</param>
  1082. /// <param name="oldGroupsIndex">Start index relative to the root collection.</param>
  1083. private void RemoveOldGroups(IList oldGroups, int oldGroupsIndex)
  1084. {
  1085. ObservableCollection<LongListSelectorItem> tuples = (ObservableCollection<LongListSelectorItem>)_listBox.ItemsSource;
  1086. // 1 - Find the index at which we start removing groups
  1087. int removeStartIndex = 0;
  1088. if (oldGroupsIndex > 0)
  1089. {
  1090. removeStartIndex = GetGroupIndexInListBox(oldGroupsIndex - 1);
  1091. IEnumerable group = ((IList)ItemsSource)[oldGroupsIndex - 1] as IEnumerable;
  1092. if (group != null)
  1093. {
  1094. removeStartIndex += GetHeadersAndItemsCountFromGroup(group);
  1095. }
  1096. }
  1097. else
  1098. {
  1099. if (HasListHeader && ShowListHeader)
  1100. {
  1101. ++removeStartIndex;
  1102. }
  1103. }
  1104. // 2 - Calculates how many items to delete from the ListBox
  1105. int itemsToRemoveCount = GetItemsCountFromGroups(oldGroups);
  1106. // 3 - Removes the old items from the ListBox
  1107. for (int i = 0; i < itemsToRemoveCount; ++i)
  1108. {
  1109. tuples.RemoveAt(removeStartIndex);
  1110. }
  1111. // 4 - Unsubscribe from the old groups
  1112. foreach (INotifyCollectionChanged collection in oldGroups)
  1113. {
  1114. collection.CollectionChanged -= OnCollectionChanged;
  1115. }
  1116. }
  1117. #endregion
  1118. #region RemoveOldItems(...)
  1119. /// <summary>
  1120. /// Removes old items from a group or from the root collection.
  1121. /// </summary>
  1122. /// <param name="oldItems">List of items to remove.</param>
  1123. /// <param name="oldItemsIndex">Start index relative to the group or root collection.</param>
  1124. /// <param name="group">Group from which items are removed. Can be null if IsFlatList == true.</param>
  1125. private void RemoveOldItems(IList oldItems, int oldItemsIndex, IEnumerable group)
  1126. {
  1127. ObservableCollection<LongListSelectorItem> tuples = (ObservableCollection<LongListSelectorItem>)_listBox.ItemsSource;
  1128. // 1 - Finds the remove index in the listbox
  1129. // Since a single group might be referenced by more than one, we might need to update more than one group
  1130. if (group != null)
  1131. {
  1132. int i = 0;
  1133. foreach (object g in ItemsSource)
  1134. {
  1135. if (g == group)
  1136. {
  1137. int removeIndex = GetGroupIndexInListBox(i);
  1138. removeIndex += oldItemsIndex;
  1139. if (GroupHeaderTemplate != null)
  1140. {
  1141. ++removeIndex;
  1142. }
  1143. // 2 - Removes the old items
  1144. for (int j = 0; j < oldItems.Count; ++j)
  1145. {
  1146. tuples.RemoveAt(removeIndex);
  1147. }
  1148. // 3 - Hides the group header and footer if it's empty and DisplayAllGroups is false
  1149. if (((IList)group).Count == 0 && !DisplayAllGroups)
  1150. {
  1151. if (GroupFooterTemplate != null)
  1152. {
  1153. tuples.RemoveAt(removeIndex); // Removes the group footer
  1154. }
  1155. if (GroupHeaderTemplate != null)
  1156. {
  1157. tuples.RemoveAt(removeIndex - 1); // Removes the group header
  1158. }
  1159. }
  1160. }
  1161. ++i;
  1162. }
  1163. }
  1164. else
  1165. {
  1166. int removeIndex = oldItemsIndex;
  1167. if (HasListHeader && ShowListHeader)
  1168. {
  1169. ++removeIndex;
  1170. }
  1171. for(int i = 0; i < oldItems.Count; ++i)
  1172. tuples.RemoveAt(removeIndex);
  1173. }
  1174. }
  1175. #endregion
  1176. #region GetGroupIndexInListBox(...)
  1177. /// <summary>
  1178. /// Returns, for a group, an index relative to the templated list box from an index relative to the root collection.
  1179. /// </summary>
  1180. /// <param name="indexInLLS">Index relative to the root collection.</param>
  1181. /// <returns>Returns, for a group, an index relative to the templated list box from an index relative to the root collection.</returns>
  1182. private int GetGroupIndexInListBox(int indexInLLS)
  1183. {
  1184. int indexInListBox = 0, index = 0;
  1185. if (HasListHeader && ShowListHeader)
  1186. {
  1187. ++indexInListBox;
  1188. }
  1189. IEnumerable groups = ItemsSource;
  1190. if (groups != null)
  1191. {
  1192. foreach (object group in groups)
  1193. {
  1194. if (indexInLLS == index)
  1195. {
  1196. break;
  1197. }
  1198. ++index;
  1199. var groupAsEnumerable = group as IEnumerable;
  1200. if (groupAsEnumerable != null)
  1201. {
  1202. indexInListBox += GetHeadersAndItemsCountFromGroup(groupAsEnumerable);
  1203. }
  1204. }
  1205. }
  1206. return indexInListBox;
  1207. }
  1208. #endregion
  1209. #region GetItemsCountFromGroups(...)
  1210. /// <summary>
  1211. /// Returns the number of items in a list of groups.
  1212. /// </summary>
  1213. /// <param name="groups">List of groups from which to retrieve the items count.</param>
  1214. /// <returns>Returns the number of items in a list of groups.</returns>
  1215. private int GetItemsCountFromGroups(IEnumerable groups)
  1216. {
  1217. int count = 0;
  1218. foreach (object g in groups)
  1219. {
  1220. IEnumerable group = g as IEnumerable;
  1221. if (group != null)
  1222. {
  1223. count += GetHeadersAndItemsCountFromGroup(group);
  1224. }
  1225. }
  1226. return count;
  1227. }
  1228. #endregion
  1229. #region GetItemsCountFromGroup(...)
  1230. /// <summary>
  1231. /// Returns the number of items in a group including the group header.
  1232. /// </summary>
  1233. /// <param name="group">Group from which to retrieve the items count.</param>
  1234. /// <returns>Returns the number of items in a group including the group header.</returns>
  1235. private int GetHeadersAndItemsCountFromGroup(IEnumerable group)
  1236. {
  1237. int count = 0;
  1238. IList groupAsList = group as IList;
  1239. if (groupAsList != null)
  1240. {
  1241. count += groupAsList.Count;
  1242. }
  1243. else
  1244. {
  1245. count += group.Cast<object>().Count();
  1246. }
  1247. bool groupHasItems = count > 0;
  1248. if ((groupHasItems || DisplayAllGroups) && GroupHeaderTemplate != null)
  1249. {
  1250. ++count;
  1251. }
  1252. if ((groupHasItems || DisplayAllGroups) && GroupFooterTemplate != null)
  1253. {
  1254. ++count;
  1255. }
  1256. return count;
  1257. }
  1258. #endregion
  1259. #region UpdateListBoxItemsTemplate(...)
  1260. /// <summary>
  1261. /// Updates the templates for a given item type in the list box.
  1262. /// </summary>
  1263. /// <param name="itemType">Item type for which to update the template.</param>
  1264. /// <param name="newTemplate">New template that will replace the old one.</param>
  1265. private void UpdateItemsTemplate(LongListSelectorItemType itemType, DataTemplate newTemplate)
  1266. {
  1267. if (_listBox != null)
  1268. {
  1269. // Update template for items that have been linked (realized)
  1270. IEnumerable<TemplatedListBoxItem> items = _listBox.GetLogicalChildrenByType<TemplatedListBoxItem>(false);
  1271. foreach (TemplatedListBoxItem item in items)
  1272. {
  1273. LongListSelectorItem tuple = (LongListSelectorItem)item.Tuple;
  1274. if (tuple.ItemType == itemType)
  1275. {
  1276. item.ContentTemplate = newTemplate;
  1277. }
  1278. }
  1279. // Save the new template so they can be applied in the future when new items
  1280. // are linked (realized)
  1281. switch (itemType)
  1282. {
  1283. case LongListSelectorItemType.ListHeader:
  1284. _listBox.ListHeaderTemplate = newTemplate;
  1285. break;
  1286. case LongListSelectorItemType.ListFooter:
  1287. _listBox.ListFooterTemplate = newTemplate;
  1288. break;
  1289. case LongListSelectorItemType.GroupHeader:
  1290. _listBox.GroupHeaderTemplate = newTemplate;
  1291. break;
  1292. case LongListSelectorItemType.GroupFooter:
  1293. _listBox.GroupFooterTemplate = newTemplate;
  1294. break;
  1295. case LongListSelectorItemType.Item:
  1296. _listBox.ItemTemplate = newTemplate;
  1297. break;
  1298. }
  1299. }
  1300. }
  1301. #endregion
  1302. #region OnDataTemplateChanged(...)
  1303. /// <summary>
  1304. /// Called when a data template has changed.
  1305. /// </summary>
  1306. private static void OnDataTemplateChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  1307. {
  1308. LongListSelector lls = (LongListSelector)o;
  1309. if (lls._listBox == null)
  1310. return;
  1311. DataTemplate newTemplate = (DataTemplate)e.NewValue;
  1312. if (e.Property == ListHeaderTemplateProperty)
  1313. {
  1314. lls.UpdateItemsTemplate(LongListSelectorItemType.ListHeader, newTemplate);
  1315. // If the old value was null, we might need to add the list header.
  1316. if (e.OldValue == null)
  1317. {
  1318. lls.AddListHeader(); // Will add a list header if it's not already there.
  1319. }
  1320. // If we don't have a list header anymore, then remove it from the listbox
  1321. else if (e.NewValue == null && !lls.HasListHeader)
  1322. {
  1323. lls.RemoveListHeader();
  1324. }
  1325. }
  1326. else if (e.Property == ListFooterTemplateProperty)
  1327. {
  1328. lls.UpdateItemsTemplate(LongListSelectorItemType.ListFooter, newTemplate);
  1329. // If the old value was null, we might need to add the list footer.
  1330. if (e.OldValue == null)
  1331. {
  1332. lls.AddListFooter(); // Will add a list footer if it's not already there.
  1333. }
  1334. // If we don't have a list footer anymore, then remove it from the listbox
  1335. else if (e.NewValue == null && !lls.HasListHeader)
  1336. {
  1337. lls.RemoveListFooter();
  1338. }
  1339. }
  1340. else if (e.Property == GroupHeaderProperty)
  1341. {
  1342. lls.UpdateItemsTemplate(LongListSelectorItemType.GroupHeader, newTemplate);
  1343. // If the old value was null, this means we might need to add group headers to the listbox
  1344. if (e.OldValue == null)
  1345. {
  1346. lls.AddGroupHeaders();
  1347. }
  1348. // If the new value is null, this means we might need to remove group headers from the listbox
  1349. else if (e.NewValue == null)
  1350. {
  1351. lls.RemoveAllGroupHeaders();
  1352. }
  1353. }
  1354. else if(e.Property == GroupFooterProperty)
  1355. {
  1356. lls.UpdateItemsTemplate(LongListSelectorItemType.GroupFooter, newTemplate);
  1357. // If the old value was null, this means we might need to add group footers to the listbox
  1358. if (e.OldValue == null)
  1359. {
  1360. lls.AddGroupFooters();
  1361. }
  1362. // If the new value is null, this means we might need to remove group footers from the listbox
  1363. else if (e.NewValue == null)
  1364. {
  1365. lls.RemoveAllGroupFooters();
  1366. }
  1367. }
  1368. else if (e.Property == ItemsTemplateProperty)
  1369. {
  1370. lls.UpdateItemsTemplate(LongListSelectorItemType.Item, newTemplate);
  1371. }
  1372. }
  1373. #endregion
  1374. #region OnDisplayAllGroupsChanged(...)
  1375. /// <summary>
  1376. /// Called when the DisplayAllGroups dependency property changes
  1377. /// </summary>
  1378. private static void OnDisplayAllGroupsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  1379. {
  1380. ((LongListSelector)obj).LoadDataIntoListBox();
  1381. }
  1382. #endregion
  1383. #region OnListBoxSelectionChanged(...)
  1384. /// <summary>
  1385. /// Called when there is a change in the selected item(s) in the listbox.
  1386. /// </summary>
  1387. private void OnListBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
  1388. {
  1389. // Group navigation
  1390. //var group = (from t in (IEnumerable<object>)e.AddedItems where ((ItemTuple)t).ItemType == ItemType.GroupHeader select (ItemTuple)t).FirstOrDefault();
  1391. LongListSelectorItem group = null;
  1392. foreach (LongListSelectorItem tuple in e.AddedItems)
  1393. {
  1394. if (tuple.ItemType == LongListSelectorItemType.GroupHeader)
  1395. {
  1396. group = tuple;
  1397. break;
  1398. }
  1399. }
  1400. if (group != null)
  1401. {
  1402. if (_listBox != null)
  1403. {
  1404. _listBox.SelectedItem = null;
  1405. }
  1406. SelectedItem = null;
  1407. DisplayGroupView();
  1408. }
  1409. else
  1410. {
  1411. if (e.AddedItems.Count > 0 && ((LongListSelectorItem)e.AddedItems[0]).ItemType == LongListSelectorItemType.Item)
  1412. {
  1413. SelectedItem = ((LongListSelectorItem)e.AddedItems[0]).Item;
  1414. }
  1415. else
  1416. {
  1417. if (_listBox != null)
  1418. {
  1419. _listBox.SelectedItem = null;
  1420. }
  1421. SelectedItem = null;
  1422. }
  1423. }
  1424. }
  1425. #endregion
  1426. #region OnCollectionChanged(...)
  1427. /// <summary>
  1428. /// Called when there is a change in the root or a group collection.
  1429. /// </summary>
  1430. private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  1431. {
  1432. var senderAsEnumerable = sender as IEnumerable;
  1433. switch (e.Action)
  1434. {
  1435. case NotifyCollectionChangedAction.Add:
  1436. if (sender == _rootCollection)
  1437. {
  1438. if (IsFlatList)
  1439. {
  1440. InsertNewItems(e.NewItems, e.NewStartingIndex, null);
  1441. }
  1442. else
  1443. {
  1444. InsertNewGroups(e.NewItems, e.NewStartingIndex);
  1445. }
  1446. }
  1447. else
  1448. {
  1449. InsertNewItems(e.NewItems, e.NewStartingIndex, senderAsEnumerable);
  1450. }
  1451. break;
  1452. case NotifyCollectionChangedAction.Remove:
  1453. if (sender == _rootCollection)
  1454. {
  1455. if (IsFlatList)
  1456. {
  1457. RemoveOldItems(e.OldItems, e.OldStartingIndex, null);
  1458. }
  1459. else
  1460. {
  1461. RemoveOldGroups(e.OldItems, e.OldStartingIndex);
  1462. }
  1463. }
  1464. else
  1465. {
  1466. RemoveOldItems(e.OldItems, e.OldStartingIndex, senderAsEnumerable);
  1467. }
  1468. break;
  1469. case NotifyCollectionChangedAction.Replace:
  1470. case NotifyCollectionChangedAction.Reset:
  1471. LoadDataIntoListBox();
  1472. break;
  1473. }
  1474. }
  1475. #endregion
  1476. #region OnScrollStateChanging(...)
  1477. /// <summary>
  1478. /// Called when the scrolling state of the list box changes.
  1479. /// </summary>
  1480. private void OnScrollStateChanging(object sender, VisualStateChangedEventArgs e)
  1481. {
  1482. IsScrolling = e.NewState.Name == ScrollingState;
  1483. if (e.NewState.Name == ScrollingState && ScrollingStarted != null)
  1484. {
  1485. ScrollingStarted(this, null);
  1486. }
  1487. else if (e.NewState.Name == NotScrollingState && ScrollingCompleted != null)
  1488. {
  1489. ScrollingCompleted(this, null);
  1490. }
  1491. }
  1492. #endregion
  1493. #region OnScrollStateChanging(...)
  1494. /// <summary>
  1495. /// Called when the scrolling state of the list box changes.
  1496. /// </summary>
  1497. private void OnStretchStateChanging(object sender, VisualStateChangedEventArgs e)
  1498. {
  1499. IsStretching = e.NewState.Name == CompressionBottom || e.NewState.Name == CompressionTop;
  1500. if (e.NewState.Name == CompressionTop && StretchingTop != null)
  1501. {
  1502. StretchingTop(this, null);
  1503. }
  1504. else if (e.NewState.Name == CompressionBottom && StretchingBottom != null)
  1505. {
  1506. StretchingBottom(this, null);
  1507. }
  1508. else if (e.NewState.Name == NoVerticalCompression && StretchingCompleted != null)
  1509. {
  1510. StretchingCompleted(this, null);
  1511. }
  1512. }
  1513. #endregion
  1514. #region OnLink(...)
  1515. /// <summary>
  1516. /// Called when an item gets realized.
  1517. /// </summary>
  1518. void OnLink(object sender, LinkUnlinkEventArgs e)
  1519. {
  1520. if (Link != null)
  1521. {
  1522. Link(this, e);
  1523. }
  1524. }
  1525. #endregion
  1526. #region OnUnlink(...)
  1527. /// <summary>
  1528. /// Called when an item gets unrealized.
  1529. /// </summary>
  1530. void OnUnlink(object sender, LinkUnlinkEventArgs e)
  1531. {
  1532. if (Unlink != null)
  1533. {
  1534. Unlink(this, e);
  1535. }
  1536. }
  1537. #endregion
  1538. }
  1539. }