PageRenderTime 33ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

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

https://bitbucket.org/jeremejevs/word-steps
C# | 2415 lines | 1731 code | 392 blank | 292 comment | 316 complexity | b51609aff59f4d21e3b91106666e38f2 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.Specialized;
  9. using System.Diagnostics;
  10. using System.Windows;
  11. using System.Windows.Controls;
  12. using System.Windows.Controls.Primitives;
  13. using System.Windows.Media;
  14. using System.Windows.Media.Animation;
  15. using System.Windows.Threading;
  16. namespace Microsoft.Phone.Controls
  17. {
  18. /// <summary>
  19. /// A virtualizing list designed for grouped lists. Can also be used with flat lists.
  20. /// </summary>
  21. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling"), TemplatePart(Name = ItemsPanelName, Type = typeof(Panel))]
  22. [TemplatePart(Name = PanningTransformName, Type = typeof(TranslateTransform))]
  23. [TemplatePart(Name = VerticalScrollBarName, Type = typeof(ScrollBar))]
  24. public partial class LongListSelector : Control
  25. {
  26. // The names of the template parts
  27. private const string ItemsPanelName = "ItemsPanel";
  28. private const string PanningTransformName = "PanningTransform";
  29. private const string VerticalScrollBarName = "VerticalScrollBar";
  30. private Panel _itemsPanel;
  31. private TranslateTransform _panningTransform;
  32. private ScrollBar _verticalScrollbar;
  33. private Popup _groupSelectorPopup;
  34. private Storyboard _panelStoryboard;
  35. private DoubleAnimation _panelAnimation;
  36. private DateTime _gestureStart;
  37. // Timer that controls how long it takes before a flick will be stopped on a touch down
  38. private DispatcherTimer _stopTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(50) };
  39. private bool _ignoreNextTap;
  40. private bool _firstDragDuringFlick;
  41. private double _lastFlickVelocity;
  42. // Duration used for animations while panning, instead of matching finger position exactly
  43. private static readonly Duration _panDuration = new Duration(TimeSpan.FromMilliseconds(100));
  44. private readonly IEasingFunction _panEase = new ExponentialEase();
  45. // Time that dragging will wait before deciding that a flick is not happening
  46. private static readonly TimeSpan _flickStopWaitTime = TimeSpan.FromMilliseconds(20);
  47. private int _scrollingTowards = -1;
  48. private const double BufferSizeDefault = 1.0;
  49. private double _bufferSizeCache = BufferSizeDefault;
  50. private double _minimumPanelScroll = float.MinValue;
  51. private double _maximumPanelScroll = 0;
  52. private bool _isLoaded;
  53. private bool _isPanning;
  54. private bool _isFlicking;
  55. private bool _isStretching;
  56. private double _dragTarget;
  57. private bool _isAnimating;
  58. private Size _availableSize;
  59. private Stack<ContentPresenter> _recycledGroupHeaders = new Stack<ContentPresenter>();
  60. private Stack<ContentPresenter> _recycledGroupFooters = new Stack<ContentPresenter>();
  61. private Stack<ContentPresenter> _recycledItems = new Stack<ContentPresenter>();
  62. private ContentPresenter _recycledListHeader = null;
  63. private ContentPresenter _recycledListFooter = null;
  64. private List<ItemTuple> _flattenedItems;
  65. private object _firstGroup;
  66. private INotifyCollectionChanged _rootCollection;
  67. private List<INotifyCollectionChanged> _groupCollections;
  68. private int _resolvedFirstIndex;
  69. private int _resolvedCount;
  70. private int _screenFirstIndex;
  71. private int _screenCount;
  72. private bool _balanceNeededForSizeChanged;
  73. #region ItemsSource DependencyProperty
  74. /// <summary>
  75. /// The DataSource property. Where all of the items come from.
  76. /// </summary>
  77. public IEnumerable ItemsSource
  78. {
  79. get { return (IEnumerable)GetValue(ItemsSourceProperty); }
  80. set { SetValue(ItemsSourceProperty, value); }
  81. }
  82. /// <summary>
  83. /// The DataSource DependencyProperty.
  84. /// </summary>
  85. public static readonly DependencyProperty ItemsSourceProperty =
  86. DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(LongListSelector), new PropertyMetadata(null, OnItemsSourceChanged));
  87. private static void OnItemsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  88. {
  89. ((LongListSelector)obj).OnItemsSourceChanged();
  90. }
  91. private void OnItemsSourceChanged()
  92. {
  93. _flattenedItems = null;
  94. if (_isLoaded)
  95. {
  96. EnsureData();
  97. }
  98. }
  99. #endregion
  100. #region ListHeader DependencyProperty
  101. /// <summary>
  102. /// The ListHeader property. Will be used as the first scrollItem in the list.
  103. /// </summary>
  104. public object ListHeader
  105. {
  106. get { return (object)GetValue(ListHeaderProperty); }
  107. set { SetValue(ListHeaderProperty, value); }
  108. }
  109. /// <summary>
  110. /// The ListHeader DependencyProperty.
  111. /// </summary>
  112. public static readonly DependencyProperty ListHeaderProperty =
  113. DependencyProperty.Register("ListHeader", typeof(object), typeof(LongListSelector), new PropertyMetadata(null));
  114. #endregion
  115. #region ListHeaderTemplate DependencyProperty
  116. /// <summary>
  117. /// The ListHeaderTemplate provides the template for the ListHeader.
  118. /// </summary>
  119. public DataTemplate ListHeaderTemplate
  120. {
  121. get { return (DataTemplate)GetValue(ListHeaderTemplateProperty); }
  122. set { SetValue(ListHeaderTemplateProperty, value); }
  123. }
  124. /// <summary>
  125. /// The ListHeaderTemplate DependencyProperty.
  126. /// </summary>
  127. public static readonly DependencyProperty ListHeaderTemplateProperty =
  128. DependencyProperty.Register("ListHeaderTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null));
  129. #endregion
  130. #region ListFooter DependencyProperty
  131. /// <summary>
  132. /// The ListFooter property. Will be used as the first scrollItem in the list.
  133. /// </summary>
  134. public object ListFooter
  135. {
  136. get { return (object)GetValue(ListFooterProperty); }
  137. set { SetValue(ListFooterProperty, value); }
  138. }
  139. /// <summary>
  140. /// The ListFooter DependencyProperty.
  141. /// </summary>
  142. public static readonly DependencyProperty ListFooterProperty =
  143. DependencyProperty.Register("ListFooter", typeof(object), typeof(LongListSelector), new PropertyMetadata(null));
  144. #endregion
  145. #region ListFooterTemplate DependencyProperty
  146. /// <summary>
  147. /// The ListFooterTemplate provides the template for the ListFooter.
  148. /// </summary>
  149. public DataTemplate ListFooterTemplate
  150. {
  151. get { return (DataTemplate)GetValue(ListFooterTemplateProperty); }
  152. set { SetValue(ListFooterTemplateProperty, value); }
  153. }
  154. /// <summary>
  155. /// The ListFooterTemplate DependencyProperty.
  156. /// </summary>
  157. public static readonly DependencyProperty ListFooterTemplateProperty =
  158. DependencyProperty.Register("ListFooterTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null));
  159. #endregion
  160. #region GroupHeaderTemplate DependencyProperty
  161. /// <summary>
  162. /// The GroupHeaderTemplate provides the template for the groups in the items view.
  163. /// </summary>
  164. public DataTemplate GroupHeaderTemplate
  165. {
  166. get { return (DataTemplate)GetValue(GroupHeaderProperty); }
  167. set { SetValue(GroupHeaderProperty, value); }
  168. }
  169. /// <summary>
  170. /// The GroupHeaderTemplate DependencyProperty.
  171. /// </summary>
  172. public static readonly DependencyProperty GroupHeaderProperty =
  173. DependencyProperty.Register("GroupHeaderTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null));
  174. #endregion
  175. #region GroupFooterTemplate DependencyProperty
  176. /// <summary>
  177. /// The GroupFooterTemplate provides the template for the groups in the items view.
  178. /// </summary>
  179. public DataTemplate GroupFooterTemplate
  180. {
  181. get { return (DataTemplate)GetValue(GroupFooterProperty); }
  182. set { SetValue(GroupFooterProperty, value); }
  183. }
  184. /// <summary>
  185. /// The GroupFooterTemplate DependencyProperty.
  186. /// </summary>
  187. public static readonly DependencyProperty GroupFooterProperty =
  188. DependencyProperty.Register("GroupFooterTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null));
  189. #endregion
  190. #region ItemTemplate DependencyProperty
  191. /// <summary>
  192. /// The ItemTemplate provides the template for the items in the items view.
  193. /// </summary>
  194. public DataTemplate ItemTemplate
  195. {
  196. get { return (DataTemplate)GetValue(ItemsTemplateProperty); }
  197. set { SetValue(ItemsTemplateProperty, value); }
  198. }
  199. /// <summary>
  200. /// The ItemTemplate DependencyProperty.
  201. /// </summary>
  202. public static readonly DependencyProperty ItemsTemplateProperty =
  203. DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null));
  204. #endregion
  205. #region GroupItemTemplate DependencyProperty
  206. /// <summary>
  207. /// The GroupItemTemplate specifies the template that will be used in group view mode.
  208. /// </summary>
  209. public DataTemplate GroupItemTemplate
  210. {
  211. get { return (DataTemplate)GetValue(GroupItemTemplateProperty); }
  212. set { SetValue(GroupItemTemplateProperty, value); }
  213. }
  214. /// <summary>
  215. /// The GroupItemTemplate DependencyProperty.
  216. /// </summary>
  217. public static readonly DependencyProperty GroupItemTemplateProperty =
  218. DependencyProperty.Register("GroupItemTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null));
  219. #endregion
  220. #region GroupItemsPanel DependencyProperty
  221. /// <summary>
  222. /// The GroupItemsPanel specifies the panel that will be used in group view mode.
  223. /// </summary>
  224. public ItemsPanelTemplate GroupItemsPanel
  225. {
  226. get { return (ItemsPanelTemplate)GetValue(GroupItemsPanelProperty); }
  227. set { SetValue(GroupItemsPanelProperty, value); }
  228. }
  229. /// <summary>
  230. /// The GroupItemsPanel DependencyProperty.
  231. /// </summary>
  232. public static readonly DependencyProperty GroupItemsPanelProperty =
  233. DependencyProperty.Register("GroupItemsPanel", typeof(ItemsPanelTemplate), typeof(LongListSelector), new PropertyMetadata(null));
  234. #endregion
  235. #region IsBouncy DependencyProperty
  236. /// <summary>
  237. /// Controls whether the list can be (temporarily) scrolled off of the ends.
  238. /// </summary>
  239. public bool IsBouncy
  240. {
  241. get { return (bool)GetValue(IsBouncyProperty); }
  242. set { SetValue(IsBouncyProperty, value); }
  243. }
  244. /// <summary>
  245. /// The IsBouncy DependencyProperty
  246. /// </summary>
  247. public static readonly DependencyProperty IsBouncyProperty =
  248. DependencyProperty.Register("IsBouncy", typeof(bool), typeof(LongListSelector), new PropertyMetadata(true));
  249. #endregion
  250. #region IsScrolling DependencyProperty
  251. /// <summary>
  252. /// Returns true if the user is manipulating the list, or if an inertial animation is taking place.
  253. /// </summary>
  254. public bool IsScrolling
  255. {
  256. get { return (bool)GetValue(IsScrollingProperty); }
  257. private set { SetValue(IsScrollingProperty, value); }
  258. }
  259. /// <summary>
  260. /// The IsScrolling DependencyProperty
  261. /// </summary>
  262. public static readonly DependencyProperty IsScrollingProperty =
  263. DependencyProperty.Register("IsScrolling", typeof(bool), typeof(LongListSelector), new PropertyMetadata(false, OnIsScrollingChanged));
  264. private static void OnIsScrollingChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  265. {
  266. LongListSelector control = (LongListSelector)obj;
  267. if ((bool) e.NewValue)
  268. {
  269. VisualStateManager.GoToState(control, "Scrolling", true);
  270. SafeRaise.Raise(control.ScrollingStarted, obj);
  271. }
  272. else
  273. {
  274. VisualStateManager.GoToState(control, "NotScrolling", true);
  275. control.BounceBack(false);
  276. SafeRaise.Raise(control.ScrollingCompleted, obj);
  277. }
  278. }
  279. #endregion
  280. #region ShowListHeader DependencyProperty
  281. /// <summary>
  282. /// Controls whether or not the ListHeader is shown.
  283. /// </summary>
  284. public bool ShowListHeader
  285. {
  286. get { return (bool)GetValue(ShowListHeaderProperty); }
  287. set { SetValue(ShowListHeaderProperty, value); }
  288. }
  289. /// <summary>
  290. /// The ShowListHeader DependencyProperty.
  291. /// </summary>
  292. public static readonly DependencyProperty ShowListHeaderProperty =
  293. DependencyProperty.Register("ShowListHeader", typeof(bool), typeof(LongListSelector), new PropertyMetadata(true, OnShowListHeaderChanged));
  294. private static void OnShowListHeaderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  295. {
  296. LongListSelector control = (LongListSelector)obj;
  297. if (control.HasListHeader && control._flattenedItems != null)
  298. {
  299. if (control.ShowListHeader)
  300. {
  301. control.OnAdd(0, ItemType.ListHeader, null, new object[] {control.ListHeader});
  302. }
  303. else
  304. {
  305. control.OnRemove(0, 1);
  306. }
  307. control.StopScrolling();
  308. control.ResetMinMax();
  309. control.Balance();
  310. control.BounceBack(true);
  311. }
  312. }
  313. #endregion
  314. #region ShowListFooter DependencyProperty
  315. /// <summary>
  316. /// Controls whether or not the ListFooter is shown.
  317. /// </summary>
  318. public bool ShowListFooter
  319. {
  320. get { return (bool)GetValue(ShowListFooterProperty); }
  321. set { SetValue(ShowListFooterProperty, value); }
  322. }
  323. /// <summary>
  324. /// The ShowListFooter DependencyProperty.
  325. /// </summary>
  326. public static readonly DependencyProperty ShowListFooterProperty =
  327. DependencyProperty.Register("ShowListFooter", typeof(bool), typeof(LongListSelector), new PropertyMetadata(true, OnShowListFooterChanged));
  328. private static void OnShowListFooterChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  329. {
  330. LongListSelector control = (LongListSelector)obj;
  331. if (control.HasListFooter && control._flattenedItems != null)
  332. {
  333. if (control.ShowListFooter)
  334. {
  335. control.OnAdd(control._flattenedItems.Count, ItemType.ListFooter, null, new object[] { control.ListFooter });
  336. }
  337. else
  338. {
  339. control.OnRemove(control._flattenedItems.Count - 1, 1);
  340. }
  341. control.StopScrolling();
  342. control.ResetMinMax();
  343. control.Balance();
  344. control.BounceBack(true);
  345. }
  346. }
  347. #endregion
  348. #region SelectedItem DependencyProperty
  349. private bool _setSelectionInternal;
  350. private bool _selectedItemChanged;
  351. private object[] EmptyList = { };
  352. private object[] _selectionList = new Object[1];
  353. /// <summary>
  354. /// Gets or sets the selected item.
  355. /// </summary>
  356. public object SelectedItem
  357. {
  358. get { return (object)GetValue(SelectedItemProperty); }
  359. set { SetValue(SelectedItemProperty, value); }
  360. }
  361. /// <summary>
  362. /// The SelectedItem DependencyProperty.
  363. /// </summary>
  364. public static readonly DependencyProperty SelectedItemProperty =
  365. DependencyProperty.Register("SelectedItem", typeof(object), typeof(LongListSelector), new PropertyMetadata(OnSelectedItemChanged));
  366. private static void OnSelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  367. {
  368. ((LongListSelector)obj).OnSelectedItemChanged(e);
  369. }
  370. private void OnSelectedItemChanged(DependencyPropertyChangedEventArgs e)
  371. {
  372. _selectedItemChanged = true;
  373. if (!_setSelectionInternal)
  374. {
  375. RaiseSelectionChangedEvent(e.NewValue);
  376. }
  377. }
  378. private void RaiseSelectionChangedEvent(object newSelection)
  379. {
  380. SelectionChangedEventHandler handler = SelectionChanged;
  381. if (handler != null)
  382. {
  383. _selectionList[0] = newSelection;
  384. handler(this, new SelectionChangedEventArgs(EmptyList, _selectionList));
  385. }
  386. }
  387. private void SetSelectedItemInternal(object newSelectedItem)
  388. {
  389. _setSelectionInternal = true;
  390. _selectedItemChanged = false;
  391. SelectedItem = newSelectedItem;
  392. if (_selectedItemChanged)
  393. {
  394. RaiseSelectionChangedEvent(newSelectedItem);
  395. }
  396. _setSelectionInternal = false;
  397. }
  398. #endregion
  399. #region BufferSize DependencyProperty
  400. /// <summary>
  401. /// The number of "screens" (as defined by the ActualHeight of the LongListSelector) above and below the visible
  402. /// items of the list that will be filled with items.
  403. /// </summary>
  404. public double BufferSize
  405. {
  406. get { return (double)GetValue(BufferSizeProperty); }
  407. set { SetValue(BufferSizeProperty, value); }
  408. }
  409. /// <summary>
  410. /// The BufferSize DependencyProperty
  411. /// </summary>
  412. public static readonly DependencyProperty BufferSizeProperty =
  413. DependencyProperty.Register("BufferSize", typeof(double), typeof(LongListSelector), new PropertyMetadata(BufferSizeDefault, OnBufferSizeChanged));
  414. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
  415. private static void OnBufferSizeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  416. {
  417. double newValue = (double)e.NewValue;
  418. if (newValue < 0)
  419. {
  420. throw new ArgumentOutOfRangeException("BufferSize");
  421. }
  422. ((LongListSelector)obj)._bufferSizeCache = newValue;
  423. }
  424. #endregion
  425. #region MaximumFlickVelocity DependencyProperty
  426. /// <summary>
  427. /// The maximum velocity for flicks, in pixels per second.
  428. /// </summary>
  429. public double MaximumFlickVelocity
  430. {
  431. get { return (double)GetValue(MaximumFlickVelocityProperty); }
  432. set { SetValue(MaximumFlickVelocityProperty, value); }
  433. }
  434. /// <summary>
  435. /// The MaximumFlickVelocity DependencyProperty.
  436. /// </summary>
  437. public static readonly DependencyProperty MaximumFlickVelocityProperty =
  438. DependencyProperty.Register("MaximumFlickVelocity", typeof(double), typeof(LongListSelector), new PropertyMetadata(MotionParameters.MaximumSpeed, OnMaximumFlickVelocityChanged));
  439. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
  440. private static void OnMaximumFlickVelocityChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  441. {
  442. double newValue = (double)e.NewValue;
  443. double coercedNewValue = Math.Min(MotionParameters.MaximumSpeed, Math.Max(newValue, 1));
  444. if (newValue != coercedNewValue)
  445. {
  446. ((LongListSelector)obj).MaximumFlickVelocity = MotionParameters.MaximumSpeed;
  447. throw new ArgumentOutOfRangeException("MaximumFlickVelocity");
  448. }
  449. }
  450. #endregion
  451. #region DisplayAllGroups DependencyProperty
  452. /// <summary>
  453. /// Display all groups whether or not they have items.
  454. /// </summary>
  455. public bool DisplayAllGroups
  456. {
  457. get { return (bool)GetValue(DisplayAllGroupsProperty); }
  458. set { SetValue(DisplayAllGroupsProperty, value); }
  459. }
  460. /// <summary>
  461. /// DisplayAllGroups DependencyProperty
  462. /// </summary>
  463. public static readonly DependencyProperty DisplayAllGroupsProperty =
  464. DependencyProperty.Register("DisplayAllGroups", typeof(bool), typeof(LongListSelector), new PropertyMetadata(false, OnDisplayAllGroupsChanged));
  465. private static void OnDisplayAllGroupsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
  466. {
  467. LongListSelector lls = (LongListSelector)obj;
  468. lls._flattenedItems = null;
  469. if (lls._isLoaded)
  470. {
  471. lls.EnsureData();
  472. }
  473. }
  474. #endregion
  475. /// <summary>
  476. /// The SelectionChanged event.
  477. /// </summary>
  478. public event SelectionChangedEventHandler SelectionChanged;
  479. /// <summary>
  480. /// Create a new instance of LongListSelector.
  481. /// </summary>
  482. public LongListSelector()
  483. {
  484. DefaultStyleKey = typeof(LongListSelector);
  485. SizeChanged += OnSizeChanged;
  486. GestureListener listener = GestureService.GetGestureListener(this);
  487. listener.GestureBegin += listener_GestureBegin;
  488. listener.GestureCompleted += listener_GestureCompleted;
  489. listener.DragStarted += listener_DragStarted;
  490. listener.DragDelta += listener_DragDelta;
  491. listener.DragCompleted += listener_DragCompleted;
  492. listener.Flick += listener_Flick;
  493. listener.Tap += listener_Tap;
  494. Loaded += LongListSelector_Loaded;
  495. Unloaded += LongListSelector_Unloaded;
  496. }
  497. /// <summary>
  498. /// Raised when the user is manipulating the list.
  499. /// </summary>
  500. public event EventHandler ScrollingStarted;
  501. /// <summary>
  502. /// Raised when the user has finished a drag or a flick completes.
  503. /// </summary>
  504. public event EventHandler ScrollingCompleted;
  505. /// <summary>
  506. /// Raised when IsBouncy is true and the user has dragged the items down from the top as far as they can go.
  507. /// </summary>
  508. public event EventHandler StretchingTop;
  509. /// <summary>
  510. /// Raised when IsBouncy is true and the user has dragged the items up from the bottom as far as they can go.
  511. /// </summary>
  512. public event EventHandler StretchingBottom;
  513. /// <summary>
  514. /// Raised when the user is no longer stretching.
  515. /// </summary>
  516. public event EventHandler StretchingCompleted;
  517. /// <summary>
  518. /// Indicates that the ContentPresenter with the item is about to be "realized".
  519. /// </summary>
  520. public event EventHandler<LinkUnlinkEventArgs> Link;
  521. /// <summary>
  522. /// Indicates that the ContentPresenter with the item is being recycled and is becoming "un-realized".
  523. /// </summary>
  524. public event EventHandler<LinkUnlinkEventArgs> Unlink;
  525. /// <summary>
  526. /// Set to true when the list is flat instead of a group hierarchy.
  527. /// </summary>
  528. public bool IsFlatList { get; set; }
  529. /// <summary>
  530. /// Instantly jump to the specified item.
  531. /// </summary>
  532. /// <param name="item">The item to scroll to.</param>
  533. public void ScrollTo(object item)
  534. {
  535. EnsureData();
  536. UpdateLayout();
  537. ContentPresenter contentPresenter;
  538. int itemIndex = GetResolvedIndex(item, out contentPresenter);
  539. if (itemIndex != -1)
  540. {
  541. StopScrolling();
  542. _panningTransform.Y = -Canvas.GetTop(contentPresenter);
  543. Balance();
  544. }
  545. itemIndex = GetFlattenedIndex(item);
  546. if (itemIndex != -1)
  547. {
  548. RecycleAllItems();
  549. ResetMinMax();
  550. StopScrolling();
  551. _panningTransform.Y = 0;
  552. _resolvedFirstIndex = itemIndex;
  553. Balance();
  554. _panningTransform.Y = GetCoercedScrollPosition(_panningTransform.Y, false);
  555. }
  556. }
  557. /// <summary>
  558. /// Animate the scrolling of the list to the specified item. Scrolling speed is capped by MaximumFlickVelocity.
  559. /// </summary>
  560. /// <param name="item">The item to scroll to.</param>
  561. public void AnimateTo(object item)
  562. {
  563. EnsureData();
  564. UpdateLayout();
  565. ContentPresenter contentPresenter;
  566. int itemIndex = GetResolvedIndex(item, out contentPresenter);
  567. if (itemIndex != -1)
  568. {
  569. // The item we are scrolling to has already been resolved, so we can set up an animation directly
  570. // to it.
  571. double newPosition = GetCoercedScrollPosition(-Canvas.GetTop(contentPresenter), false);
  572. double delta = -newPosition + _panningTransform.Y;
  573. double seconds = Math.Abs(delta) / MaximumFlickVelocity;
  574. IEasingFunction ease = PhysicsConstants.GetEasingFunction(seconds);
  575. if (_scrollingTowards != -1)
  576. {
  577. _scrollingTowards = -1;
  578. // This is the termination of an AnimateTo call where the location was
  579. // not know. Use a different ease to keep the animation smooth.
  580. ease = new ExponentialEase() { EasingMode = EasingMode.EaseOut };
  581. }
  582. IsFlicking = true;
  583. AnimatePanel(new Duration(TimeSpan.FromSeconds(seconds)), ease, newPosition);
  584. return;
  585. }
  586. itemIndex = GetFlattenedIndex(item);
  587. if (itemIndex != -1)
  588. {
  589. // Since we don't know the pixel position of the item we are scrolling to, we just go
  590. // in a direction, and stop when the item is resolved.
  591. _scrollingTowards = itemIndex;
  592. ScrollTowards();
  593. }
  594. }
  595. /// <summary>
  596. /// Returns all of the items that are currently in view. This is not the same as the items that
  597. /// have associated visual elements: there are usually some visuals offscreen. This might be
  598. /// an empty list if scrolling is happening too quickly.
  599. /// </summary>
  600. /// <returns>The items in view.</returns>
  601. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
  602. public ICollection<object> GetItemsInView()
  603. {
  604. return GetItemsWithContainers(true, false);
  605. }
  606. /// <summary>
  607. /// Used to return either containers or items for either all items with containers or just the
  608. /// visible ones, as specified by the parameters.
  609. /// </summary>
  610. /// <param name="onlyItemsInView">When true, will return values for only items that are in view.</param>
  611. /// <param name="getContainers">When true, will return the containers rather than the items.</param>
  612. /// <returns>A collection of values as specified above.</returns>
  613. public ICollection<object> GetItemsWithContainers(bool onlyItemsInView, bool getContainers)
  614. {
  615. int start = onlyItemsInView ? _screenFirstIndex : _resolvedFirstIndex;
  616. int count = onlyItemsInView ? _screenCount : _resolvedCount;
  617. object[] items = new object[count];
  618. for (int index = start; index < start + count; ++index)
  619. {
  620. items[index - start] = getContainers ? _flattenedItems[index].ContentPresenter : _flattenedItems[index].Item;
  621. }
  622. return items;
  623. }
  624. private bool IsPanning
  625. {
  626. get { return _isPanning; }
  627. set
  628. {
  629. _isPanning = value;
  630. IsScrolling = IsPanning || IsFlicking;
  631. }
  632. }
  633. private bool IsFlicking
  634. {
  635. get { return _isFlicking; }
  636. set
  637. {
  638. _isFlicking = value;
  639. IsScrolling = IsPanning || IsFlicking;
  640. }
  641. }
  642. private bool IsStretching
  643. {
  644. get { return _isStretching; }
  645. set
  646. {
  647. if (_isStretching != value)
  648. {
  649. _isStretching = value;
  650. if (_isStretching)
  651. {
  652. if (_dragTarget < _minimumPanelScroll)
  653. {
  654. SafeRaise.Raise(StretchingBottom, this);
  655. }
  656. else
  657. {
  658. SafeRaise.Raise(StretchingTop, this);
  659. }
  660. }
  661. else
  662. {
  663. SafeRaise.Raise(StretchingCompleted, this);
  664. }
  665. }
  666. }
  667. }
  668. private bool HasListHeader { get { return ListHeaderTemplate != null || ListHeader is UIElement; } }
  669. private bool HasListFooter { get { return ListFooterTemplate != null || ListFooter is UIElement; } }
  670. void LongListSelector_Loaded(object sender, RoutedEventArgs e)
  671. {
  672. _isLoaded = true;
  673. EnsureData();
  674. }
  675. void LongListSelector_Unloaded(object sender, RoutedEventArgs e)
  676. {
  677. _isLoaded = false;
  678. RecycleAllItems();
  679. }
  680. /// <summary>
  681. /// OnApplyTemplate override, used to locate template parts.
  682. /// </summary>
  683. public override void OnApplyTemplate()
  684. {
  685. base.OnApplyTemplate();
  686. // Find the template parts. Create dummy objects if parts are missing to avoid
  687. // null checks throughout the code (although we can't escape them completely.)
  688. _itemsPanel = GetTemplateChild(ItemsPanelName) as Panel ?? new Canvas();
  689. _panningTransform = GetTemplateChild(PanningTransformName) as TranslateTransform ?? new TranslateTransform();
  690. _verticalScrollbar = GetTemplateChild(VerticalScrollBarName) as ScrollBar ?? new ScrollBar();
  691. _panelAnimation = new DoubleAnimation();
  692. Storyboard.SetTarget(_panelAnimation, _panningTransform);
  693. Storyboard.SetTargetProperty(_panelAnimation, new PropertyPath("Y"));
  694. _panelStoryboard = new Storyboard();
  695. _panelStoryboard.Children.Add(_panelAnimation);
  696. _panelStoryboard.Completed += PanelStoryboardCompleted;
  697. Balance();
  698. }
  699. /// <summary>
  700. /// Override of the MeasureOverride function, to capture the available size.
  701. /// </summary>
  702. /// <param name="availableSize">The available size.</param>
  703. /// <returns>The desired size.</returns>
  704. protected override Size MeasureOverride(Size availableSize)
  705. {
  706. _availableSize.Width = availableSize.Width;
  707. _availableSize.Height = double.PositiveInfinity;
  708. return base.MeasureOverride(availableSize);
  709. }
  710. #region Event handlers
  711. private void OnSizeChanged(object sender, SizeChangedEventArgs e)
  712. {
  713. Clip = new RectangleGeometry() { Rect = new Rect(0, 0, e.NewSize.Width, e.NewSize.Height) };
  714. if (_isLoaded)
  715. {
  716. Balance();
  717. }
  718. }
  719. void listener_GestureBegin(object sender, GestureEventArgs e)
  720. {
  721. if (IsScrolling)
  722. {
  723. _ignoreNextTap = true;
  724. }
  725. _gestureStart = DateTime.Now;
  726. if (_isFlicking)
  727. {
  728. _stopTimer.Tick -= _stopTimer_Tick;
  729. _stopTimer.Tick += _stopTimer_Tick;
  730. _stopTimer.Start();
  731. }
  732. }
  733. void _stopTimer_Tick(object sender, EventArgs e)
  734. {
  735. StopScrolling();
  736. IsPanning = IsFlicking = false;
  737. }
  738. void listener_GestureCompleted(object sender, GestureEventArgs e)
  739. {
  740. _stopTimer.Tick -= _stopTimer_Tick;
  741. _ignoreNextTap = false;
  742. }
  743. private void listener_DragStarted(object sender, DragStartedGestureEventArgs e)
  744. {
  745. if (e.Direction != Orientation.Vertical)
  746. {
  747. return;
  748. }
  749. _stopTimer.Tick -= _stopTimer_Tick;
  750. _dragTarget = _panningTransform.Y;
  751. e.Handled = true;
  752. }
  753. private void listener_DragDelta(object sender, DragDeltaGestureEventArgs e)
  754. {
  755. if (e.Direction != Orientation.Vertical)
  756. {
  757. return;
  758. }
  759. TimeSpan elapsed = DateTime.Now - _gestureStart;
  760. e.Handled = true;
  761. if (elapsed > _flickStopWaitTime || Math.Sign(e.VerticalChange) != Math.Sign(_lastFlickVelocity))
  762. {
  763. IsPanning = true;
  764. IsFlicking = false;
  765. if (_firstDragDuringFlick)
  766. {
  767. StopScrolling();
  768. _firstDragDuringFlick = false;
  769. }
  770. else
  771. {
  772. AnimatePanel(_panDuration, _panEase, _dragTarget += e.VerticalChange);
  773. IsStretching = IsBouncy && (GetCoercedScrollPosition(_dragTarget, true) != _dragTarget);
  774. }
  775. }
  776. }
  777. private void listener_DragCompleted(object sender, DragCompletedGestureEventArgs e)
  778. {
  779. if (e.Direction != Orientation.Vertical)
  780. {
  781. return;
  782. }
  783. IsPanning = false;
  784. IsStretching = false;
  785. e.Handled = true;
  786. }
  787. private void listener_Flick(object sender, FlickGestureEventArgs e)
  788. {
  789. if (e.Direction != Orientation.Vertical)
  790. {
  791. return;
  792. }
  793. _stopTimer.Tick -= _stopTimer_Tick;
  794. double vMax = MaximumFlickVelocity;
  795. _lastFlickVelocity = Math.Min(vMax, Math.Max(e.VerticalVelocity, -vMax));
  796. Point velocity = new Point(0, _lastFlickVelocity);
  797. double flickDuration = PhysicsConstants.GetStopTime(velocity);
  798. Point flickEndPoint = PhysicsConstants.GetStopPoint(velocity);
  799. IEasingFunction flickEase = PhysicsConstants.GetEasingFunction(flickDuration);
  800. AnimatePanel(new Duration(TimeSpan.FromSeconds(flickDuration)), flickEase, _panningTransform.Y + flickEndPoint.Y);
  801. IsFlicking = true;
  802. _firstDragDuringFlick = true;
  803. _scrollingTowards = -1;
  804. e.Handled = true;
  805. }
  806. void listener_Tap(object sender, GestureEventArgs e)
  807. {
  808. StopScrolling();
  809. IsPanning = IsFlicking = false;
  810. }
  811. private void StopScrolling()
  812. {
  813. double position = Math.Round(_panningTransform.Y);
  814. StopAnimation();
  815. _panningTransform.Y = position;
  816. _stopTimer.Tick -= _stopTimer_Tick;
  817. _scrollingTowards = -1;
  818. }
  819. private void GroupHeaderTap(object sender, GestureEventArgs e)
  820. {
  821. _stopTimer.Tick -= _stopTimer_Tick;
  822. if (!_ignoreNextTap)
  823. {
  824. DisplayGroupView();
  825. }
  826. }
  827. private void OnItemTap(object sender, GestureEventArgs e)
  828. {
  829. if (!_ignoreNextTap)
  830. {
  831. ContentPresenter cp = (ContentPresenter)sender;
  832. SetSelectedItemInternal(cp.Content);
  833. }
  834. }
  835. private void HandleGesture(object sender, GestureEventArgs e)
  836. {
  837. e.Handled = true;
  838. }
  839. #endregion
  840. private bool IsReady() { return _itemsPanel != null && ItemsSource != null && ActualHeight > 0; }
  841. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
  842. private void Balance()
  843. {
  844. if (!IsReady() || _flattenedItems.Count == 0)
  845. {
  846. // See comment in the call below. Necessary here for when the last item is removed.
  847. CollapseRecycledElements();
  848. _resolvedFirstIndex = _resolvedCount = _screenFirstIndex = _screenCount = 0;
  849. return;
  850. }
  851. double actualHeight = ActualHeight;
  852. double viewportTop = -(_bufferSizeCache * actualHeight);
  853. double viewportBottom = (_bufferSizeCache + 1) * actualHeight;
  854. double scrollPosition = _panningTransform.Y;
  855. ContentPresenter cc;
  856. double top = 0, bottom = 0;
  857. if (_resolvedCount > 0)
  858. {
  859. // Remove first?
  860. cc = FirstResolved.ContentPresenter;
  861. top = Canvas.GetTop(cc);
  862. bottom = top + cc.DesiredSize.Height;
  863. while (_resolvedCount > 0 && bottom + scrollPosition < viewportTop)
  864. {
  865. RecycleFirst();
  866. if (_resolvedCount > 0)
  867. {
  868. cc = FirstResolved.ContentPresenter;
  869. top = Canvas.GetTop(cc);
  870. bottom = top + cc.DesiredSize.Height;
  871. }
  872. }
  873. // Remove last?
  874. if (_resolvedCount > 0)
  875. {
  876. cc = LastResolved.ContentPresenter;
  877. top = Canvas.GetTop(cc);
  878. bottom = top + cc.DesiredSize.Height;
  879. while (_resolvedCount > 0 && top + scrollPosition > viewportBottom)
  880. {
  881. RecycleLast();
  882. if (_resolvedCount > 0)
  883. {
  884. cc = LastResolved.ContentPresenter;
  885. top = Canvas.GetTop(cc);
  886. bottom = top + cc.DesiredSize.Height;
  887. }
  888. }
  889. }
  890. if (_resolvedCount == 0)
  891. {
  892. ResetMinMax();
  893. }
  894. }
  895. // Empty list?
  896. bool appendExtra = _resolvedCount == 0 && _resolvedFirstIndex == 0;
  897. if (_resolvedCount == 0)
  898. {
  899. //Debug.WriteLine("Adding first element");
  900. _resolvedFirstIndex = Math.Max(0, Math.Min(_resolvedFirstIndex, _flattenedItems.Count - 1));
  901. var t = _flattenedItems[_resolvedFirstIndex];
  902. cc = GetAndAddElementFor(t);
  903. cc.SetExtraData(_resolvedFirstIndex, cc.DesiredSize.Height);
  904. top = 0;
  905. Canvas.SetTop(cc, top);
  906. bottom = cc.DesiredSize.Height;
  907. if (_resolvedFirstIndex == 0)
  908. {
  909. _maximumPanelScroll = 0;
  910. }
  911. }
  912. // Prepend?
  913. cc = FirstResolved.ContentPresenter;
  914. top = Canvas.GetTop(cc);
  915. bottom = top + cc.DesiredSize.Height;
  916. if (top + scrollPosition >= viewportTop && _resolvedFirstIndex == 0)
  917. {
  918. _maximumPanelScroll = -top;
  919. BrakeIfGoingTooFar();
  920. }
  921. else
  922. {
  923. while (top + scrollPosition >= viewportTop && _resolvedFirstIndex > 0)
  924. {
  925. appendExtra = false;
  926. _resolvedFirstIndex = Math.Max(0, Math.Min(_resolvedFirstIndex, _flattenedItems.Count - 1));
  927. var t = _flattenedItems[--_resolvedFirstIndex];
  928. //Debug.WriteLine("Adding {0}", t.Item);
  929. cc = GetAndAddElementFor(t);
  930. cc.SetExtraData(_resolvedFirstIndex, cc.DesiredSize.Height);
  931. bottom = top;
  932. top = bottom - cc.DesiredSize.Height;
  933. Canvas.SetTop(cc, top);
  934. if (_resolvedFirstIndex == 0)
  935. {
  936. _maximumPanelScroll = -top;
  937. BrakeIfGoingTooFar();
  938. }
  939. }
  940. }
  941. // Append?
  942. cc = LastResolved.ContentPresenter;
  943. top = Canvas.GetTop(cc);
  944. bottom = top + cc.DesiredSize.Height;
  945. if (appendExtra)
  946. {
  947. // If we went to the top of the list, generate extra items so that we don't need to generate them
  948. // as soon as scrolling starts.
  949. viewportBottom += ActualHeight * _bufferSizeCache;
  950. }
  951. while (bottom + scrollPosition <= viewportBottom && _resolvedFirstIndex + _resolvedCount < _flattenedItems.Count)
  952. {
  953. _resolvedFirstIndex = Math.Max(0, Math.Min(_resolvedFirstIndex, _flattenedItems.Count - 1));
  954. var t = _flattenedItems[_resolvedFirstIndex + _resolvedCount];
  955. //Debug.WriteLine("Adding {0}", t.Item);
  956. cc = GetAndAddElementFor(t);
  957. cc.SetExtraData(_resolvedFirstIndex + _resolvedCount - 1, cc.DesiredSize.Height);
  958. top = bottom;
  959. Canvas.SetTop(cc, top);
  960. bottom = top + cc.DesiredSize.Height;
  961. }
  962. if (_resolvedFirstIndex + _resolvedCount == _flattenedItems.Count)
  963. {
  964. _minimumPanelScroll = ActualHeight - bottom;
  965. if (_minimumPanelScroll > _maximumPanelScroll)
  966. {
  967. _minimumPanelScroll = _maximumPanelScroll;
  968. }
  969. BrakeIfGoingTooFar();
  970. }
  971. // Determine which items are on the screen
  972. _screenFirstIndex = 0;
  973. _screenCount = 0;
  974. for (int itemIndex = _resolvedFirstIndex; itemIndex < _resolvedFirstIndex + _resolvedCount; ++itemIndex)
  975. {
  976. ContentPresenter cp = _flattenedItems[itemIndex].ContentPresenter;
  977. top = Canvas.GetTop(cp) + scrollPosition;
  978. bottom = top + cp.DesiredSize.Height;
  979. if ((top >= 0 && top <= ActualHeight) || (top < 0 && bottom > 0))
  980. {
  981. if (_screenCount == 0)
  982. {
  983. _screenFirstIndex = itemIndex;
  984. }
  985. ++_screenCount;
  986. }
  987. else if (_screenCount != 0)
  988. {
  989. break;
  990. }
  991. }
  992. // Adjust the scrollbar
  993. double max = Math.Max(1, _flattenedItems.Count - _screenCount);
  994. _verticalScrollbar.Maximum = max;
  995. _verticalScrollbar.Value = Math.Min(max, _screenFirstIndex);
  996. if (Math.Abs(_screenCount - _verticalScrollbar.Value) > 1)
  997. {
  998. _verticalScrollbar.ViewportSize = Math.Max(1, _screenCount);
  999. }
  1000. // This must be done to ensure proper functionality of controls, e.g. Button.
  1001. // It ensures that only items left in the recycle bin get collapsed, since
  1002. // collapsing items and then immediately making them visible can have strange
  1003. // effects, e.g. with a Button.Click on the stack.
  1004. CollapseRecycledElements();
  1005. // When AnimateTo is called, but the location of the item is not know (because it is not resolved) then
  1006. // the list is just scrolled in the right direction until the item is discovered to be in the resolved
  1007. // items list.
  1008. if (_scrollingTowards >= _resolvedFirstIndex && _scrollingTowards < _resolvedFirstIndex + _resolvedCount)
  1009. {
  1010. // Since the specified item now has a known location, scroll to it.
  1011. AnimateTo(_flattenedItems[_scrollingTowards].Item);
  1012. }
  1013. }
  1014. private ItemTuple FirstResolved
  1015. {
  1016. get
  1017. {
  1018. return _flattenedItems[_resolvedFirstIndex];
  1019. }
  1020. }
  1021. private ItemTuple LastResolved
  1022. {
  1023. get
  1024. {
  1025. int index = _resolvedFirstIndex + _resolvedCount - 1;
  1026. return _flattenedItems[index];
  1027. }
  1028. }
  1029. private void RecycleFirst()
  1030. {
  1031. if (_resolvedCount > 0)
  1032. {
  1033. var t = _flattenedItems[_resolvedFirstIndex++];
  1034. RemoveAndAddToRecycleBin(t);
  1035. }
  1036. }
  1037. private void RecycleLast()
  1038. {
  1039. if (_resolvedCount > 0)
  1040. {
  1041. var t = _flattenedItems[_resolvedFirstIndex + _resolvedCount - 1];
  1042. RemoveAndAddToRecycleBin(t);
  1043. }
  1044. }
  1045. private void RemoveAndAddToRecycleBin(ItemTuple tuple)
  1046. {
  1047. ContentPresenter cp = tuple.ContentPresenter;
  1048. switch (tuple.ItemType)
  1049. {
  1050. case ItemType.Item:
  1051. _recycledItems.Push(cp);
  1052. break;
  1053. case ItemType.GroupHeader:
  1054. _recycledGroupHeaders.Push(cp);
  1055. break;
  1056. case ItemType.GroupFooter:
  1057. _recycledGroupFooters.Push(cp);
  1058. break;
  1059. case ItemType.ListHeader:
  1060. Debug.Assert(_recycledListHeader == null);
  1061. _recycledListHeader = cp;
  1062. break;
  1063. case ItemType.ListFooter:
  1064. Debug.Assert(_recycledListFooter == null);
  1065. _recycledListFooter = cp;
  1066. break;
  1067. }
  1068. EventHandler<LinkUnlinkEventArgs> handler = Unlink;
  1069. if (handler != null)
  1070. {
  1071. handler(this, new LinkUnlinkEventArgs(cp));
  1072. }
  1073. tuple.ContentPresenter = null;
  1074. cp.Content = null;
  1075. cp.SetExtraData(-1, 0);
  1076. --_resolvedCount;
  1077. }
  1078. private void CollapseRecycledElements()
  1079. {
  1080. foreach (ContentPresenter cp in _recycledItems)
  1081. {
  1082. cp.Visibility = Visibility.Collapsed;
  1083. }
  1084. foreach (ContentPresenter cp in _recycledGroupHeaders)
  1085. {
  1086. cp.Visibility = Visibility.Collapsed;
  1087. }
  1088. foreach (ContentPresenter cp in _recycledGroupFooters)
  1089. {
  1090. cp.Visibility = Visibility.Collapsed;
  1091. }
  1092. if (_recycledListHeader != null)
  1093. {
  1094. _recycledListHeader.Visibility = Visibility.Collapsed;
  1095. }
  1096. if (_recycledListFooter != null)
  1097. {
  1098. _recycledListFooter.Visibility = Visibility.Collapsed;
  1099. }
  1100. }
  1101. private void EmptyRecycleBin()
  1102. {
  1103. if (_recycledItems != null)
  1104. {
  1105. _recycledItems.Clear();
  1106. }
  1107. if (_recycledGroupHeaders != null)
  1108. {
  1109. _recycledGroupHeaders.Clear();
  1110. }
  1111. if (_recycledGroupFooters != null)
  1112. {
  1113. _recycledGroupFooters.Clear();
  1114. }
  1115. _recycledListHeader = null;
  1116. _recycledListFooter = null;
  1117. }
  1118. private void RecycleAllItems()
  1119. {
  1120. while (_resolvedCount > 0)
  1121. {
  1122. RecycleFirst();
  1123. }
  1124. _resolvedFirstIndex = 0;
  1125. }
  1126. private ContentPresenter GetAndAddElementFor(ItemTuple tuple)
  1127. {
  1128. ContentPresenter cp = null;
  1129. bool isNew = false;
  1130. switch (tuple.ItemType)
  1131. {
  1132. case ItemType.Item:
  1133. if (_recycledItems.Count > 0)
  1134. {
  1135. cp = _recycledItems.Pop();
  1136. }
  1137. else
  1138. {
  1139. isNew = true;
  1140. cp = new ContentPresenter();
  1141. cp.ContentTemplate = ItemTemplate;
  1142. GestureService.GetGestureListener(cp).Tap += OnItemTap;
  1143. }
  1144. break;
  1145. case ItemType.GroupHeader:
  1146. if (_recycledGroupHeaders.Count > 0)
  1147. {
  1148. cp = _recycledGroupHeaders.Pop();
  1149. }
  1150. else
  1151. {
  1152. isNew = true;
  1153. cp = new ContentPresenter();
  1154. cp.ContentTemplate = GroupHeaderTemplate;
  1155. GestureService.GetGestureListener(cp).Tap += GroupHeaderTap;
  1156. }
  1157. break;
  1158. case ItemType.GroupFooter:
  1159. if (_recycledGroupFooters.Count > 0)
  1160. {
  1161. cp = _recycledGroupFooters.Pop();
  1162. }
  1163. else
  1164. {
  1165. isNew = true;
  1166. cp = new ContentPresenter();
  1167. cp.ContentTemplate = GroupFooterTemplate;
  1168. }
  1169. break;
  1170. case ItemType.ListHeader:
  1171. if (_recycledListHeader != null)
  1172. {
  1173. cp = _recycledListHeader;
  1174. _recycledListHeader = null;
  1175. }
  1176. else
  1177. {
  1178. isNew = true;
  1179. cp = new ContentPresenter();
  1180. cp.ContentTemplate = ListHeaderTemplate;
  1181. }
  1182. break;
  1183. case ItemType.ListFooter:
  1184. if (_recycledListFooter != null)
  1185. {
  1186. cp = _recycledListFooter;
  1187. _recycledListFooter = null;
  1188. }
  1189. else
  1190. {
  1191. isNew = true;
  1192. cp = new ContentPresenter();
  1193. cp.ContentTemplate = ListFooterTemplate;
  1194. }
  1195. break;
  1196. default:
  1197. break;
  1198. }
  1199. if (isNew)
  1200. {
  1201. cp.CacheMode = new BitmapCache();
  1202. _itemsPanel.Children.Add(cp);
  1203. cp.SizeChanged += new SizeChangedEventHandler(OnItemSizeChanged);
  1204. }
  1205. if (cp != null)
  1206. {
  1207. if (cp.Width != _availableSize.Width)
  1208. {
  1209. cp.Width = _availableSize.Width;
  1210. }
  1211. cp.Content = tuple.Item;
  1212. cp.Visibility = Visibility.Visible;
  1213. }
  1214. EventHandler<LinkUnlinkEventArgs> handler = Link;
  1215. if (handler != null)
  1216. {
  1217. handler(this, new LinkUnlinkEventArgs(cp));
  1218. }
  1219. tuple.ContentPresenter = cp;
  1220. cp.Measure(_availableSize);
  1221. ++_resolvedCount;
  1222. return cp;
  1223. }
  1224. private void OnItemSizeChanged(object sender, SizeChangedEventArgs e)
  1225. {
  1226. // This will get called when items get containers as well as when an item that already
  1227. // have containers are changed. The initial container size is saved after the Measure
  1228. // call in Balance, and updated here if necessary. It is compared to the incoming size
  1229. // to see if the size of the container really changed or if the event is being raised
  1230. // because the container was recycled.
  1231. ContentPresenter cp = sender as ContentPresenter;
  1232. double delta = e.NewSize.Height - e.PreviousSize.Height;
  1233. _minimumPanelScroll -= delta;
  1234. if (cp != null)
  1235. {
  1236. int index;
  1237. double lastDesiredHeight;
  1238. cp.GetExtraData(out index, out lastDesiredHeight);
  1239. if (lastDesiredHeight == e.NewSize.Height)
  1240. {
  1241. return;
  1242. }
  1243. cp.SetExtraData(index, e.NewSize.Height);
  1244. if (index < _screenFirstIndex)
  1245. {
  1246. // Adjust the item that changed and all previous items
  1247. while (index >= _resolvedFirstIndex)
  1248. {
  1249. double top = Canvas.GetTop(_flattenedItems[index].ContentPresenter);
  1250. top -= delta;
  1251. Canvas.SetTop(_flattenedItems[index].ContentPresenter, top);
  1252. --index;
  1253. }
  1254. }
  1255. else
  1256. {
  1257. // Adjust items AFTER the item that changed
  1258. int resolvedLastIndex = _resolvedFirstIndex + _resolvedCount - 1;
  1259. ++index; // The item that changed size doesn't have to move
  1260. while (index <= resolvedLastIndex)
  1261. {
  1262. double top = Canvas.GetTop(_flattenedItems[index].ContentPresenter);
  1263. top += delta;
  1264. Canvas.SetTop(_flattenedItems[index].ContentPresenter, top);
  1265. ++index;
  1266. }
  1267. }
  1268. if (!_balanceNeededForSizeChanged)
  1269. {
  1270. _balanceNeededForSizeChanged = true;
  1271. LayoutUpdated += LongListSelector_LayoutUpdated;
  1272. }
  1273. }
  1274. }
  1275. void LongListSelector_LayoutUpdated(object sender, EventArgs e)
  1276. {
  1277. _balanceNeededForSizeChanged = false;
  1278. LayoutUpdated -= LongListSelector_LayoutUpdated;
  1279. Balance();
  1280. }
  1281. private void EnsureData()
  1282. {
  1283. if (_flattenedItems == null || _flattenedItems.Count == 0)
  1284. {
  1285. FlattenData();
  1286. Balance();
  1287. }
  1288. }
  1289. private void FlattenData()
  1290. {
  1291. bool groupHeaderExists = GroupHeaderTemplate != null;
  1292. bool groupFooterExists = GroupFooterTemplate != null;
  1293. bool displayAllGroups = DisplayAllGroups;
  1294. _flattenedItems = new List<ItemTuple>();
  1295. _firstGroup = null;
  1296. SetSelectedItemInternal(null);
  1297. _resolvedFirstIndex = 0;
  1298. _resolvedCount = 0;
  1299. _firstGroup = null;
  1300. if (_panningTransform != null)
  1301. {
  1302. StopAnimation();
  1303. _panningTransform.Y = 0;
  1304. ResetMinMax();
  1305. }
  1306. ResetMinMax();
  1307. if (_itemsPanel != null)
  1308. {
  1309. _itemsPanel.Children.Clear();
  1310. }
  1311. EmptyRecycleBin();
  1312. if (_rootCollection != null)
  1313. {
  1314. _rootCollection.CollectionChanged -= OnRootCollectionChanged;
  1315. }
  1316. _rootCollection = null;
  1317. if (_groupCollections != null)
  1318. {
  1319. foreach (INotifyCollectionChanged gc in _groupCollections)
  1320. {
  1321. if (gc != null)
  1322. {
  1323. gc.CollectionChanged -= OnGroupCollectionChanged;
  1324. }
  1325. }
  1326. }
  1327. _groupCollections = new List<INotifyCollectionChanged>();
  1328. if (ItemsSource == null)
  1329. {
  1330. return;
  1331. }
  1332. if (HasListHeader && ShowListHeader)
  1333. {
  1334. _flattenedItems.Add(new ItemTuple() { ItemType = ItemType.ListHeader, Item = ListHeader });
  1335. }
  1336. foreach (object group in ItemsSource)
  1337. {
  1338. object groupRequiringFooter = null;
  1339. if (IsFlatList)
  1340. {
  1341. _flattenedItems.Add(new ItemTuple() { ItemType = ItemType.Item, Group = group, Item = group });
  1342. }
  1343. else
  1344. {
  1345. IEnumerable itemEnumerable = (IEnumerable) group;
  1346. IEnumerator itemEnumerator = itemEnumerable.GetEnumerator();
  1347. bool hasItems = itemEnumerator.MoveNext();
  1348. if (hasItems || displayAllGroups)
  1349. {
  1350. if (groupHeaderExists)
  1351. {
  1352. _flattenedItems.Add(new ItemTuple() { ItemType = ItemType.GroupHeader, Group = group, Item = group });
  1353. }
  1354. if (groupFooterExists)
  1355. {
  1356. groupRequiringFooter = group;
  1357. }
  1358. if (_firstGroup == null)
  1359. {
  1360. _firstGroup = group;
  1361. }
  1362. }
  1363. if (hasItems)
  1364. {
  1365. while (hasItems)
  1366. {
  1367. _flattenedItems.Add(new ItemTuple() { ItemType = ItemType.Item, Group = group, Item = itemEnumerator.Current });
  1368. hasItems = itemEnumerator.MoveNext();
  1369. }
  1370. }
  1371. if (groupRequiringFooter != null)
  1372. {
  1373. _flattenedItems.Add(new ItemTuple() { ItemType = ItemType.GroupFooter, Group = group, Item = group });
  1374. groupRequiringFooter = null;
  1375. }
  1376. AddGroupNotifyCollectionChanged(group);
  1377. }
  1378. }
  1379. _rootCollection = ItemsSource as INotifyCollectionChanged;
  1380. if (_rootCollection != null)
  1381. {
  1382. _rootCollection.CollectionChanged += OnRootCollectionChanged;
  1383. }
  1384. if (HasListFooter && ShowListFooter)
  1385. {
  1386. _flattenedItems.Add(new ItemTuple() { ItemType = ItemType.ListFooter, Item = ListFooter });
  1387. }
  1388. if (_verticalScrollbar != null)
  1389. {
  1390. _verticalScrollbar.Maximum = _flattenedItems.Count;
  1391. }
  1392. }
  1393. private void AddGroupNotifyCollectionChanged(object group)
  1394. {
  1395. INotifyCollectionChanged groupNCC = group as INotifyCollectionChanged;
  1396. if (groupNCC != null)
  1397. {
  1398. _groupCollections.Add(groupNCC);
  1399. groupNCC.CollectionChanged += OnGroupCollectionChanged;
  1400. }
  1401. }
  1402. private void RemoveGroupNotifyCollectionChanged(object group)
  1403. {
  1404. INotifyCollectionChanged groupNCC = group as INotifyCollectionChanged;
  1405. if (groupNCC != null)
  1406. {
  1407. _groupCollections.Remove(groupNCC);
  1408. groupNCC.CollectionChanged -= OnGroupCollectionChanged;
  1409. }
  1410. }
  1411. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
  1412. void OnRootCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  1413. {
  1414. int headerOffset = HasListHeader && ShowListHeader ? 1 : 0;
  1415. if (IsFlatList)
  1416. {
  1417. switch (e.Action)
  1418. {
  1419. case NotifyCollectionChangedAction.Add:
  1420. OnAdd(headerOffset + e.NewStartingIndex, ItemType.Item, null, e.NewItems);
  1421. break;
  1422. case NotifyCollectionChangedAction.Remove:
  1423. OnRemove(headerOffset + e.OldStartingIndex, e.OldItems.Count);
  1424. break;
  1425. case NotifyCollectionChangedAction.Replace:
  1426. if (e.NewItems.Count != 1)
  1427. {
  1428. throw new NotSupportedException();
  1429. }
  1430. OnReplace(headerOffset + e.NewStartingIndex, e.NewItems[0]);
  1431. break;
  1432. case NotifyCollectionChangedAction.Reset:
  1433. _flattenedItems = null;
  1434. EnsureData();
  1435. break;
  1436. }
  1437. }
  1438. else
  1439. {
  1440. bool displayAllGroups = DisplayAllGroups;
  1441. int groupHeaderOffset = GroupHeaderTemplate != null ? 1 : 0;
  1442. int groupFooterOffset = GroupFooterTemplate != null ? 1 : 0;
  1443. switch (e.Action)
  1444. {
  1445. case NotifyCollectionChangedAction.Add:
  1446. {
  1447. IList groupList = (IList)ItemsSource;
  1448. object groupAtIndex = e.NewStartingIndex < groupList.Count ? groupList[e.NewStartingIndex] : null;
  1449. int offset = groupAtIndex != null ? GetGroupOffset(groupAtIndex) : groupList.Count;
  1450. foreach (object group in e.NewItems)
  1451. {
  1452. AddGroupNotifyCollectionChanged(group);
  1453. IList itemsList = GetItemsInGroup(group);
  1454. if (itemsList.Count > 0 || displayAllGroups)
  1455. {
  1456. if (groupHeaderOffset == 1)
  1457. {
  1458. OnAdd(offset, ItemType.GroupHeader, group, new object[] { group });
  1459. ++offset;
  1460. }
  1461. if (itemsList.Count > 0)
  1462. {
  1463. OnAdd(offset, ItemType.Item, group, itemsList);
  1464. offset += itemsList.Count;
  1465. }
  1466. if (groupFooterOffset == 1)
  1467. {
  1468. OnAdd(offset, ItemType.GroupFooter, group, new object[] { group });
  1469. ++offset;
  1470. }
  1471. }
  1472. }
  1473. }
  1474. break;
  1475. case NotifyCollectionChangedAction.Remove:
  1476. {
  1477. foreach (object group in e.OldItems)
  1478. {
  1479. RemoveGroupNotifyCollectionChanged(group);
  1480. IList itemsList = GetItemsInGroup(group);
  1481. int offset = GetGroupOffset(group);
  1482. int count = itemsList.Count;
  1483. if (displayAllGroups || itemsList.Count > 0)
  1484. {
  1485. count += groupHeaderOffset + groupFooterOffset;
  1486. }
  1487. OnRemove(offset, count);
  1488. }
  1489. }
  1490. break;
  1491. case NotifyCollectionChangedAction.Replace:
  1492. throw new NotSupportedException();
  1493. case NotifyCollectionChangedAction.Reset:
  1494. _flattenedItems = null;
  1495. EnsureData();
  1496. break;
  1497. }
  1498. }
  1499. ResetMinMax();
  1500. Balance();
  1501. if (BounceBack(true))
  1502. {
  1503. // The group could have been inserted at the beginning of the list, when
  1504. // there are not enough items to fill the screen.
  1505. Balance();
  1506. }
  1507. }
  1508. private static IList GetItemsInGroup(object group)
  1509. {
  1510. IList itemsList = group as IList;
  1511. if (itemsList != null)
  1512. {
  1513. return itemsList;
  1514. }
  1515. List<object> items = new List<object>();
  1516. IEnumerator itemEnum = ((IEnumerable)group).GetEnumerator();
  1517. bool hasItems = itemEnum.MoveNext();
  1518. while (hasItems)
  1519. {
  1520. items.Add(itemEnum.Current);
  1521. hasItems = itemEnum.MoveNext();
  1522. }
  1523. return items;
  1524. }
  1525. /// <summary>
  1526. /// Returns true if the group has no items
  1527. /// </summary>
  1528. /// <param name="group">The group to check.</param>
  1529. /// <returns>True if the group has no items.</returns>
  1530. private static bool IsGroupEmpty(object group)
  1531. {
  1532. IList itemsList = group as IList;
  1533. if (itemsList != null)
  1534. {
  1535. return itemsList.Count == 0;
  1536. }
  1537. IEnumerator itemEnum = ((IEnumerable)group).GetEnumerator();
  1538. return !itemEnum.MoveNext();
  1539. }
  1540. void OnGroupCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  1541. {
  1542. object group = sender;
  1543. int groupHeaderOffset = GroupHeaderTemplate != null ? 1 : 0;
  1544. int groupFooterOffset = GroupFooterTemplate != null ? 1 : 0;
  1545. bool displayAllGroups = DisplayAllGroups;
  1546. int itemsInGroupCount;
  1547. int offset = GetGroupOffset(group, out itemsInGroupCount);
  1548. IList groupList = group as IList;
  1549. if (!displayAllGroups && groupList != null && e.Action == NotifyCollectionChangedAction.Add && groupList.Count == e.NewItems.Count)
  1550. {
  1551. if (groupHeaderOffset == 1)
  1552. {
  1553. OnAdd(offset, ItemType.GroupHeader, group, new object[] { group });
  1554. }
  1555. if (groupFooterOffset == 1)
  1556. {
  1557. OnAdd(offset + groupHeaderOffset, ItemType.GroupFooter, group, new object[] { group });
  1558. }
  1559. }
  1560. offset += groupHeaderOffset;
  1561. switch (e.Action)
  1562. {
  1563. case NotifyCollectionChangedAction.Add:
  1564. OnAdd(e.NewStartingIndex + offset, ItemType.Item, group, e.NewItems);
  1565. break;
  1566. case NotifyCollectionChangedAction.Remove:
  1567. {
  1568. int count = e.OldItems.Count;
  1569. if (IsGroupEmpty(group) && !displayAllGroups)
  1570. {
  1571. // The last items in the group are going away; remove the group, too.
  1572. offset -= groupHeaderOffset;
  1573. count += groupHeaderOffset + groupFooterOffset;
  1574. }
  1575. OnRemove(e.OldStartingIndex + offset, count);
  1576. }
  1577. break;
  1578. case NotifyCollectionChangedAction.Replace:
  1579. if (e.NewItems.Count != 1)
  1580. {
  1581. throw new NotSupportedException();
  1582. }
  1583. OnReplace(offset + e.NewStartingIndex, e.NewItems[0]);
  1584. break;
  1585. case NotifyCollectionChangedAction.Reset:
  1586. if (DisplayAllGroups)
  1587. {
  1588. OnRemove(offset, itemsInGroupCount);
  1589. }
  1590. else if (itemsInGroupCount > 0)
  1591. {
  1592. OnRemove(offset - groupHeaderOffset, itemsInGroupCount + groupHeaderOffset + groupFooterOffset);
  1593. }
  1594. break;
  1595. }
  1596. ResetMinMax();
  1597. Balance();
  1598. if (BounceBack(true))
  1599. {
  1600. // The group could have been inserted at the beginning of the list, when
  1601. // there are not enough items to fill the screen.
  1602. Balance();
  1603. }
  1604. }
  1605. private int GetGroupOffset(object group)
  1606. {
  1607. int listHeaderOffset = HasListHeader && ShowListHeader ? 1 : 0;
  1608. // int listFooterOffset = HasListFooter && ShowListFooter ? 1 : 0;
  1609. bool displayAll = DisplayAllGroups;
  1610. int groupHeaderOffset = GroupHeaderTemplate != null ? 1 : 0;
  1611. int groupFooterOffset = GroupFooterTemplate != null ? 1 : 0;
  1612. int offset = listHeaderOffset;
  1613. foreach (var g in ItemsSource)
  1614. {
  1615. if (g.Equals(group))
  1616. {
  1617. break;
  1618. }
  1619. int groupCount = 0;
  1620. var groupList = g as IList;
  1621. if (groupList != null)
  1622. {
  1623. groupCount = groupList.Count;
  1624. }
  1625. else
  1626. {
  1627. var groupEnum = g as IEnumerable;
  1628. if (groupEnum != null)
  1629. {
  1630. IEnumerator enumerator = groupEnum.GetEnumerator();
  1631. while (enumerator.MoveNext())
  1632. {
  1633. ++groupCount;
  1634. }
  1635. }
  1636. }
  1637. if (displayAll || groupCount > 0)
  1638. {
  1639. offset += groupHeaderOffset + groupFooterOffset;
  1640. }
  1641. offset += groupCount;
  1642. }
  1643. return offset;
  1644. }
  1645. // Returns the first offset after all preceding groups. This could be
  1646. // the position of the group header, the first group item (if there is
  1647. // no header) or the place where the group should be inserted.
  1648. // We have to count the items this way because this is used when clearing
  1649. // a group, and the items will already have been removed from the group so
  1650. // we can't get the count from there.
  1651. private int GetGroupOffset(object group, out int itemsInGroupCount)
  1652. {
  1653. int offset = GetGroupOffset(group);
  1654. int lastGroupOffset = offset;
  1655. itemsInGroupCount = 0;
  1656. while (lastGroupOffset < _flattenedItems.Count && group.Equals(_flattenedItems[lastGroupOffset].Group))
  1657. {
  1658. if (_flattenedItems[lastGroupOffset].ItemType == ItemType.Item)
  1659. {
  1660. ++itemsInGroupCount;
  1661. }
  1662. ++lastGroupOffset;
  1663. }
  1664. return offset;
  1665. }
  1666. private void OnAdd(int startingIndex, ItemType itemType, object group, IList newItems)
  1667. {
  1668. int resolvedLastIndex = _resolvedFirstIndex + _resolvedCount - 1;
  1669. // Perform the Add operation
  1670. ItemTuple[] newData = new ItemTuple[newItems.Count];
  1671. for (int index = 0; index < newItems.Count; ++index)
  1672. {
  1673. newData[index] = new ItemTuple() { ItemType = itemType, Group = group, Item = newItems[index] };
  1674. }
  1675. if (startingIndex <= _resolvedFirstIndex || startingIndex > resolvedLastIndex)
  1676. {
  1677. // If the operation is completely outside the bounds of the resolved items, then it can just happen.
  1678. if (startingIndex <= _resolvedFirstIndex)
  1679. {
  1680. if (_resolvedCount > 0)
  1681. {
  1682. _resolvedFirstIndex += newItems.Count;
  1683. }
  1684. else
  1685. {
  1686. _resolvedFirstIndex = 0;
  1687. }
  1688. }
  1689. }
  1690. else if (startingIndex < _screenFirstIndex)
  1691. {
  1692. int removeCount = startingIndex - _resolvedFirstIndex + 1;
  1693. while (removeCount-- > 0)
  1694. {
  1695. RecycleFirst();
  1696. }
  1697. _resolvedFirstIndex += newItems.Count;
  1698. }
  1699. else
  1700. {
  1701. int removeCount = _resolvedCount - startingIndex + _resolvedFirstIndex;
  1702. while (removeCount-- > 0)
  1703. {
  1704. RecycleLast();
  1705. }
  1706. }
  1707. // Perform the Add operation
  1708. _flattenedItems.InsertRange(startingIndex, newData);
  1709. }
  1710. private void OnRemove(int startingIndex, int count)
  1711. {
  1712. int endIndex = startingIndex + count - 1;
  1713. int resolvedLastIndex = _resolvedFirstIndex + _resolvedCount - 1;
  1714. // If the operation is completely outside the bounds of the resolved items,
  1715. // then it can just happen.
  1716. if (endIndex < _resolvedFirstIndex || startingIndex > resolvedLastIndex)
  1717. {
  1718. if (startingIndex < _resolvedFirstIndex)
  1719. {
  1720. if (startingIndex + count - 1 < _resolvedFirstIndex)
  1721. {
  1722. _resolvedFirstIndex -= count;
  1723. }
  1724. }
  1725. }
  1726. else
  1727. {
  1728. if (startingIndex >= _screenFirstIndex)
  1729. {
  1730. // The deletion point is either on the screen or after the screen.
  1731. int removeCount = resolvedLastIndex - startingIndex + 1;
  1732. while (removeCount-- > 0)
  1733. {
  1734. RecycleLast();
  1735. }
  1736. }
  1737. else
  1738. {
  1739. // The deletion starts before the screen
  1740. int recycleCount = Math.Min(_resolvedCount, startingIndex - _resolvedFirstIndex + 1);
  1741. while (recycleCount-- > 0)
  1742. {
  1743. RecycleFirst();
  1744. }
  1745. _resolvedFirstIndex -= count;
  1746. }
  1747. }
  1748. // Perform the remove operation
  1749. _flattenedItems.RemoveRange(startingIndex, count);
  1750. }
  1751. /// <summary>
  1752. /// Replaces a single object in a group
  1753. /// </summary>
  1754. /// <param name="index">The global index of the item.</param>
  1755. /// <param name="item">The new item.</param>
  1756. private void OnReplace(int index, object item)
  1757. {
  1758. int resolvedLastIndex = _resolvedFirstIndex + _resolvedCount - 1;
  1759. if (index >= _resolvedFirstIndex && index <= resolvedLastIndex)
  1760. {
  1761. if (index < _resolvedFirstIndex)
  1762. {
  1763. int count = _resolvedFirstIndex - index + 1;
  1764. while (count-- > 0)
  1765. {
  1766. RecycleFirst();
  1767. }
  1768. }
  1769. else
  1770. {
  1771. int count = resolvedLastIndex - index + 1;
  1772. while (count-- > 0)
  1773. {
  1774. RecycleLast();
  1775. }
  1776. }
  1777. }
  1778. _flattenedItems[index].Item = item;
  1779. }
  1780. private void ResetMinMax()
  1781. {
  1782. _minimumPanelScroll = float.MinValue;
  1783. _maximumPanelScroll = float.MaxValue;
  1784. }
  1785. private void AnimatePanel(Duration duration, IEasingFunction ease, double to)
  1786. {
  1787. // Be sure not to run past the first or last items
  1788. double newTo = GetCoercedScrollPosition(to, IsBouncy);
  1789. if (to != newTo)
  1790. {
  1791. // Adjust the duration
  1792. double originalDelta = Math.Max(Math.Abs(_panningTransform.Y - to), 1);
  1793. double modifiedDelta = Math.Abs(_panningTransform.Y - newTo);
  1794. double factor = modifiedDelta / originalDelta;
  1795. // If factor > 0, the edge has been detected, but it hasn't gone too far yet, so readjust the animation.
  1796. // If factor < 0, somehow scrolling has gone past the edge already, so snap back.
  1797. duration = factor <= 1 && factor >= 0 && duration.HasTimeSpan ? new Duration(TimeSpan.FromMilliseconds(duration.TimeSpan.Milliseconds * factor)) : _panDuration;
  1798. to = newTo;
  1799. }
  1800. double from = _panningTransform.Y;
  1801. StopAnimation();
  1802. CompositionTarget.Rendering += AnimationPerFrameCallback;
  1803. _panelAnimation.Duration = duration;
  1804. _panelAnimation.EasingFunction = ease;
  1805. _panelAnimation.From = from;
  1806. _panelAnimation.To = to;
  1807. _panelStoryboard.Begin();
  1808. _panelStoryboard.SeekAlignedToLastTick(TimeSpan.Zero);
  1809. _isAnimating = true;
  1810. }
  1811. private void BrakeIfGoingTooFar()
  1812. {
  1813. if (_isAnimating && _panelAnimation.To.HasValue)
  1814. {
  1815. double to = _panelAnimation.To.Value;
  1816. double newTo = GetCoercedScrollPosition(to, IsBouncy);
  1817. if (newTo != _panelAnimation.To.Value)
  1818. {
  1819. double originalDelta = Math.Max(_panelAnimation.To.Value - _panelAnimation.From.Value, 1);
  1820. double remainingDelta = newTo - _panningTransform.Y;
  1821. double factor = remainingDelta / originalDelta;
  1822. // If factor > 0, the edge has been detected, but it hasn't gone too far yet, so readjust the animation.
  1823. // If factor < 0, somehow scrolling has gone past the edge already, so snap back.
  1824. Duration duration = factor <= 1 && factor >= 0 && _panelAnimation.Duration.HasTimeSpan ? new Duration(TimeSpan.FromMilliseconds(_panelAnimation.Duration.TimeSpan.Milliseconds * factor)) : _panDuration;
  1825. AnimatePanel(duration, _panelAnimation.EasingFunction, newTo);
  1826. }
  1827. }
  1828. }
  1829. void AnimationPerFrameCallback(object sender, EventArgs e)
  1830. {
  1831. Balance();
  1832. }
  1833. void PanelStoryboardCompleted(object sender, EventArgs e)
  1834. {
  1835. CompositionTarget.Rendering -= AnimationPerFrameCallback;
  1836. if (_scrollingTowards != -1)
  1837. {
  1838. // We are scrolling in a direction towards an item we did not know the position of.
  1839. // We aren't there yet, so keep going in the same direction.
  1840. ScrollTowards();
  1841. return;
  1842. }
  1843. _isAnimating = false;
  1844. IsFlicking = false;
  1845. }
  1846. private void StopAnimation()
  1847. {
  1848. _panelStoryboard.Stop();
  1849. CompositionTarget.Rendering -= AnimationPerFrameCallback;
  1850. _isAnimating = false;
  1851. }
  1852. private bool BounceBack(bool immediately)
  1853. {
  1854. if (_panningTransform == null || _resolvedCount == 0)
  1855. {
  1856. return false;
  1857. }
  1858. double to = _panningTransform.Y;
  1859. double top = Canvas.GetTop(FirstResolved.ContentPresenter);
  1860. double newTo = top > -to ? -top : GetCoercedScrollPosition(to, false);
  1861. if (to != newTo)
  1862. {
  1863. if (immediately)
  1864. {
  1865. _panningTransform.Y = newTo;
  1866. }
  1867. else
  1868. {
  1869. AnimatePanel(_panDuration, _panEase, newTo);
  1870. }
  1871. return true;
  1872. }
  1873. return false;
  1874. }
  1875. /// <summary>
  1876. /// Instantly jump to the selected group.
  1877. /// </summary>
  1878. /// <param name="group">The group to jump to</param>
  1879. public void ScrollToGroup(object group)
  1880. {
  1881. double scrollTarget = 0;
  1882. bool foundTarget = false;
  1883. if (group == null)
  1884. {
  1885. return;
  1886. }
  1887. // First check to see if group is already visible
  1888. for (int index = _resolvedFirstIndex; index < _resolvedFirstIndex + _resolvedCount; ++index)
  1889. {
  1890. ContentPresenter cp = _flattenedItems[index].ContentPresenter;
  1891. if (cp.Content != null && cp.Content.Equals(group) && _flattenedItems[index].ItemType == ItemType.GroupHeader)
  1892. {
  1893. scrollTarget = -Canvas.GetTop(cp);
  1894. foundTarget = true;
  1895. break;
  1896. }
  1897. ++index;
  1898. }
  1899. // Just jump to it, and replace the entire list.
  1900. if (!foundTarget)
  1901. {
  1902. int newIndex = GetGroupOffset(group);
  1903. if (newIndex == -1)
  1904. {
  1905. return;
  1906. }
  1907. if (newIndex != -1)
  1908. {
  1909. RecycleAllItems();
  1910. CollapseRecycledElements();
  1911. _resolvedFirstIndex = newIndex;
  1912. scrollTarget = 0;
  1913. ResetMinMax();
  1914. }
  1915. }
  1916. scrollTarget = GetCoercedScrollPosition(scrollTarget, false);
  1917. StopAnimation();
  1918. _panningTransform.Y = scrollTarget;
  1919. Balance();
  1920. // Special case first group to include header
  1921. if (group.Equals(_firstGroup))
  1922. {
  1923. _panningTransform.Y = _maximumPanelScroll;
  1924. }
  1925. BounceBack(true);
  1926. }
  1927. private double BounceDistance { get { return ActualHeight / 4; } }
  1928. private double GetCoercedScrollPosition(double value, bool isBouncy)
  1929. {
  1930. double bounceFactor = isBouncy ? BounceDistance : 0;
  1931. return Math.Max(_minimumPanelScroll - bounceFactor, Math.Min(_maximumPanelScroll + bounceFactor, value));
  1932. }
  1933. /// <summary>
  1934. /// Scroll in the direction of an item that has not been resolved.
  1935. /// </summary>
  1936. private void ScrollTowards()
  1937. {
  1938. if (_scrollingTowards == -1)
  1939. {
  1940. return;
  1941. }
  1942. double jump = _scrollingTowards < _resolvedFirstIndex ? 10000000 : -10000000;
  1943. double newPosition = GetCoercedScrollPosition(_panningTransform.Y + jump, false);
  1944. double delta = newPosition - _panningTransform.Y;
  1945. double seconds = Math.Abs(delta) / MaximumFlickVelocity;
  1946. IsFlicking = true;
  1947. AnimatePanel(new Duration(TimeSpan.FromSeconds(seconds)), null, newPosition);
  1948. }
  1949. private int GetFlattenedIndex(object item)
  1950. {
  1951. int count = _flattenedItems.Count;
  1952. for (int index = 0; index < count; ++index)
  1953. {
  1954. if (item == _flattenedItems[index].Item)
  1955. {
  1956. return index;
  1957. }
  1958. }
  1959. return -1;
  1960. }
  1961. private int GetResolvedIndex(object item, out ContentPresenter contentPresenter)
  1962. {
  1963. if (_resolvedCount > 0)
  1964. {
  1965. for (int index = _resolvedFirstIndex; index < _resolvedFirstIndex + _resolvedCount; ++index)
  1966. {
  1967. if (_flattenedItems[index].Item == item)
  1968. {
  1969. contentPresenter = _flattenedItems[index].ContentPresenter;
  1970. return index;
  1971. }
  1972. }
  1973. }
  1974. contentPresenter = null;
  1975. return -1;
  1976. }
  1977. private enum ItemType
  1978. {
  1979. Unknown,
  1980. Item,
  1981. GroupHeader,
  1982. GroupFooter,
  1983. ListHeader,
  1984. ListFooter
  1985. }
  1986. private class ItemTuple
  1987. {
  1988. public ItemType ItemType;
  1989. public object Group;
  1990. public object Item;
  1991. public ContentPresenter ContentPresenter;
  1992. }
  1993. // private void ComputeResolvedFirstAndCount(out int first, out int count)
  1994. // {
  1995. // first = -1; count = 0;
  1996. // bool done = false;
  1997. // for (int index = 0; index < _flattenedItems.Count; ++index)
  1998. // {
  1999. // if (_flattenedItems[index].ContentPresenter != null)
  2000. // {
  2001. // if (done)
  2002. // {
  2003. // throw new InvalidOperationException();
  2004. // }
  2005. // if (first == -1)
  2006. // {
  2007. // first = index;
  2008. // }
  2009. // ++count;
  2010. // }
  2011. // else if (first != -1)
  2012. // {
  2013. // done = true;
  2014. // }
  2015. // }
  2016. // if (first == -1)
  2017. // {
  2018. // first = 0;
  2019. // }
  2020. // Debug.WriteLine("Resolved first {0} count {1}", first, count);
  2021. // }
  2022. }
  2023. }