一、 UI  上两个扩展

  1. public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
  2. {
  3. #region Fields
  4.  
  5. UIElementCollection _children;
  6. ItemsControl _itemsControl;
  7. IItemContainerGenerator _generator;
  8. private Point _offset = new Point(, );
  9. private Size _extent = new Size(, );
  10. private Size _viewport = new Size(, );
  11. private int firstIndex = ;
  12. private Size childSize;
  13. private Size _pixelMeasuredViewport = new Size(, );
  14. Dictionary<UIElement, Rect> _realizedChildLayout = new Dictionary<UIElement, Rect>();
  15. WrapPanelAbstraction _abstractPanel;
  16.  
  17. #endregion
  18.  
  19. #region Properties
  20.  
  21. private Size ChildSlotSize
  22. {
  23. get
  24. {
  25. return new Size(ItemWidth, ItemHeight);
  26. }
  27. }
  28.  
  29. #endregion
  30.  
  31. #region Dependency Properties
  32.  
  33. [TypeConverter(typeof(LengthConverter))]
  34. public double ItemHeight
  35. {
  36. get
  37. {
  38. return (double)base.GetValue(ItemHeightProperty);
  39. }
  40. set
  41. {
  42. base.SetValue(ItemHeightProperty, value);
  43. }
  44. }
  45.  
  46. [TypeConverter(typeof(LengthConverter))]
  47. public double ItemWidth
  48. {
  49. get
  50. {
  51. return (double)base.GetValue(ItemWidthProperty);
  52. }
  53. set
  54. {
  55. base.SetValue(ItemWidthProperty, value);
  56. }
  57. }
  58.  
  59. public Orientation Orientation
  60. {
  61. get { return (Orientation)GetValue(OrientationProperty); }
  62. set { SetValue(OrientationProperty, value); }
  63. }
  64.  
  65. public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));
  66. public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));
  67. public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner(typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(Orientation.Horizontal));
  68.  
  69. #endregion
  70.  
  71. #region Methods
  72.  
  73. public void SetFirstRowViewItemIndex(int index)
  74. {
  75. SetVerticalOffset((index) / Math.Floor((_viewport.Width) / childSize.Width));
  76. SetHorizontalOffset((index) / Math.Floor((_viewport.Height) / childSize.Height));
  77. }
  78.  
  79. private void Resizing(object sender, EventArgs e)
  80. {
  81. if (_viewport.Width != )
  82. {
  83. int firstIndexCache = firstIndex;
  84. _abstractPanel = null;
  85. MeasureOverride(_viewport);
  86. SetFirstRowViewItemIndex(firstIndex);
  87. firstIndex = firstIndexCache;
  88. }
  89. }
  90.  
  91. public int GetFirstVisibleSection()
  92. {
  93. int section;
  94. var maxSection = _abstractPanel.Max(x => x.Section);
  95. if (Orientation == Orientation.Horizontal)
  96. {
  97. section = (int)_offset.Y;
  98. }
  99. else
  100. {
  101. section = (int)_offset.X;
  102. }
  103. if (section > maxSection)
  104. section = maxSection;
  105. return section;
  106. }
  107.  
  108. public int GetFirstVisibleIndex()
  109. {
  110. int section = GetFirstVisibleSection();
  111. var item = _abstractPanel.Where(x => x.Section == section).FirstOrDefault();
  112. if (item != null)
  113. return item._index;
  114. return ;
  115. }
  116.  
  117. private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
  118. {
  119. for (int i = _children.Count - ; i >= ; i--)
  120. {
  121. GeneratorPosition childGeneratorPos = new GeneratorPosition(i, );
  122. int itemIndex = _generator.IndexFromGeneratorPosition(childGeneratorPos);
  123. if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
  124. {
  125. _generator.Remove(childGeneratorPos, );
  126. RemoveInternalChildRange(i, );
  127. }
  128. }
  129. }
  130.  
  131. private void ComputeExtentAndViewport(Size pixelMeasuredViewportSize, int visibleSections)
  132. {
  133. if (Orientation == Orientation.Horizontal)
  134. {
  135. _viewport.Height = visibleSections;
  136. _viewport.Width = pixelMeasuredViewportSize.Width;
  137. }
  138. else
  139. {
  140. _viewport.Width = visibleSections;
  141. _viewport.Height = pixelMeasuredViewportSize.Height;
  142. }
  143.  
  144. if (Orientation == Orientation.Horizontal)
  145. {
  146. _extent.Height = _abstractPanel.SectionCount + ViewportHeight - ;
  147.  
  148. }
  149. else
  150. {
  151. _extent.Width = _abstractPanel.SectionCount + ViewportWidth - ;
  152. }
  153. _owner.InvalidateScrollInfo();
  154. }
  155.  
  156. private void ResetScrollInfo()
  157. {
  158. _offset.X = ;
  159. _offset.Y = ;
  160. }
  161.  
  162. private int GetNextSectionClosestIndex(int itemIndex)
  163. {
  164. var abstractItem = _abstractPanel[itemIndex];
  165. if (abstractItem.Section < _abstractPanel.SectionCount - )
  166. {
  167. var ret = _abstractPanel.
  168. Where(x => x.Section == abstractItem.Section + ).
  169. OrderBy(x => Math.Abs(x.SectionIndex - abstractItem.SectionIndex)).
  170. First();
  171. return ret._index;
  172. }
  173. else
  174. return itemIndex;
  175. }
  176.  
  177. private int GetLastSectionClosestIndex(int itemIndex)
  178. {
  179. var abstractItem = _abstractPanel[itemIndex];
  180. if (abstractItem.Section > )
  181. {
  182. var ret = _abstractPanel.
  183. Where(x => x.Section == abstractItem.Section - ).
  184. OrderBy(x => Math.Abs(x.SectionIndex - abstractItem.SectionIndex)).
  185. First();
  186. return ret._index;
  187. }
  188. else
  189. return itemIndex;
  190. }
  191.  
  192. private void NavigateDown()
  193. {
  194. var gen = _generator.GetItemContainerGeneratorForPanel(this);
  195. UIElement selected = (UIElement)Keyboard.FocusedElement;
  196. int itemIndex = gen.IndexFromContainer(selected);
  197. int depth = ;
  198. while (itemIndex == -)
  199. {
  200. selected = (UIElement)VisualTreeHelper.GetParent(selected);
  201. itemIndex = gen.IndexFromContainer(selected);
  202. depth++;
  203. }
  204. DependencyObject next = null;
  205. if (Orientation == Orientation.Horizontal)
  206. {
  207. int nextIndex = GetNextSectionClosestIndex(itemIndex);
  208. next = gen.ContainerFromIndex(nextIndex);
  209. while (next == null)
  210. {
  211. SetVerticalOffset(VerticalOffset + );
  212. UpdateLayout();
  213. next = gen.ContainerFromIndex(nextIndex);
  214. }
  215. }
  216. else
  217. {
  218. if (itemIndex == _abstractPanel._itemCount - )
  219. return;
  220. next = gen.ContainerFromIndex(itemIndex + );
  221. while (next == null)
  222. {
  223. SetHorizontalOffset(HorizontalOffset + );
  224. UpdateLayout();
  225. next = gen.ContainerFromIndex(itemIndex + );
  226. }
  227. }
  228. while (depth != )
  229. {
  230. next = VisualTreeHelper.GetChild(next, );
  231. depth--;
  232. }
  233. (next as UIElement).Focus();
  234. }
  235.  
  236. private void NavigateLeft()
  237. {
  238. var gen = _generator.GetItemContainerGeneratorForPanel(this);
  239.  
  240. UIElement selected = (UIElement)Keyboard.FocusedElement;
  241. int itemIndex = gen.IndexFromContainer(selected);
  242. int depth = ;
  243. while (itemIndex == -)
  244. {
  245. selected = (UIElement)VisualTreeHelper.GetParent(selected);
  246. itemIndex = gen.IndexFromContainer(selected);
  247. depth++;
  248. }
  249. DependencyObject next = null;
  250. if (Orientation == Orientation.Vertical)
  251. {
  252. int nextIndex = GetLastSectionClosestIndex(itemIndex);
  253. next = gen.ContainerFromIndex(nextIndex);
  254. while (next == null)
  255. {
  256. SetHorizontalOffset(HorizontalOffset - );
  257. UpdateLayout();
  258. next = gen.ContainerFromIndex(nextIndex);
  259. }
  260. }
  261. else
  262. {
  263. if (itemIndex == )
  264. return;
  265. next = gen.ContainerFromIndex(itemIndex - );
  266. while (next == null)
  267. {
  268. SetVerticalOffset(VerticalOffset - );
  269. UpdateLayout();
  270. next = gen.ContainerFromIndex(itemIndex - );
  271. }
  272. }
  273. while (depth != )
  274. {
  275. next = VisualTreeHelper.GetChild(next, );
  276. depth--;
  277. }
  278. (next as UIElement).Focus();
  279. }
  280.  
  281. private void NavigateRight()
  282. {
  283. var gen = _generator.GetItemContainerGeneratorForPanel(this);
  284. UIElement selected = (UIElement)Keyboard.FocusedElement;
  285. int itemIndex = gen.IndexFromContainer(selected);
  286. int depth = ;
  287. while (itemIndex == -)
  288. {
  289. selected = (UIElement)VisualTreeHelper.GetParent(selected);
  290. itemIndex = gen.IndexFromContainer(selected);
  291. depth++;
  292. }
  293. DependencyObject next = null;
  294. if (Orientation == Orientation.Vertical)
  295. {
  296. int nextIndex = GetNextSectionClosestIndex(itemIndex);
  297. next = gen.ContainerFromIndex(nextIndex);
  298. while (next == null)
  299. {
  300. SetHorizontalOffset(HorizontalOffset + );
  301. UpdateLayout();
  302. next = gen.ContainerFromIndex(nextIndex);
  303. }
  304. }
  305. else
  306. {
  307. if (itemIndex == _abstractPanel._itemCount - )
  308. return;
  309. next = gen.ContainerFromIndex(itemIndex + );
  310. while (next == null)
  311. {
  312. SetVerticalOffset(VerticalOffset + );
  313. UpdateLayout();
  314. next = gen.ContainerFromIndex(itemIndex + );
  315. }
  316. }
  317. while (depth != )
  318. {
  319. next = VisualTreeHelper.GetChild(next, );
  320. depth--;
  321. }
  322. (next as UIElement).Focus();
  323. }
  324.  
  325. private void NavigateUp()
  326. {
  327. var gen = _generator.GetItemContainerGeneratorForPanel(this);
  328. UIElement selected = (UIElement)Keyboard.FocusedElement;
  329. int itemIndex = gen.IndexFromContainer(selected);
  330. int depth = ;
  331. while (itemIndex == -)
  332. {
  333. selected = (UIElement)VisualTreeHelper.GetParent(selected);
  334. itemIndex = gen.IndexFromContainer(selected);
  335. depth++;
  336. }
  337. DependencyObject next = null;
  338. if (Orientation == Orientation.Horizontal)
  339. {
  340. int nextIndex = GetLastSectionClosestIndex(itemIndex);
  341. next = gen.ContainerFromIndex(nextIndex);
  342. while (next == null)
  343. {
  344. SetVerticalOffset(VerticalOffset - );
  345. UpdateLayout();
  346. next = gen.ContainerFromIndex(nextIndex);
  347. }
  348. }
  349. else
  350. {
  351. if (itemIndex == )
  352. return;
  353. next = gen.ContainerFromIndex(itemIndex - );
  354. while (next == null)
  355. {
  356. SetHorizontalOffset(HorizontalOffset - );
  357. UpdateLayout();
  358. next = gen.ContainerFromIndex(itemIndex - );
  359. }
  360. }
  361. while (depth != )
  362. {
  363. next = VisualTreeHelper.GetChild(next, );
  364. depth--;
  365. }
  366. (next as UIElement).Focus();
  367. }
  368.  
  369. #endregion
  370.  
  371. #region Override
  372.  
  373. protected override void OnKeyDown(KeyEventArgs e)
  374. {
  375. switch (e.Key)
  376. {
  377. case Key.Down:
  378. NavigateDown();
  379. e.Handled = true;
  380. break;
  381. case Key.Left:
  382. NavigateLeft();
  383. e.Handled = true;
  384. break;
  385. case Key.Right:
  386. NavigateRight();
  387. e.Handled = true;
  388. break;
  389. case Key.Up:
  390. NavigateUp();
  391. e.Handled = true;
  392. break;
  393. default:
  394. base.OnKeyDown(e);
  395. break;
  396. }
  397. }
  398.  
  399. protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
  400. {
  401. base.OnItemsChanged(sender, args);
  402. _abstractPanel = null;
  403. ResetScrollInfo();
  404. }
  405.  
  406. protected override void OnInitialized(EventArgs e)
  407. {
  408. this.SizeChanged += new SizeChangedEventHandler(this.Resizing);
  409. base.OnInitialized(e);
  410. _itemsControl = ItemsControl.GetItemsOwner(this);
  411. _children = InternalChildren;
  412. _generator = ItemContainerGenerator;
  413. }
  414.  
  415. protected override Size MeasureOverride(Size availableSize)
  416. {
  417. if (_itemsControl == null || _itemsControl.Items.Count == )
  418. return availableSize;
  419. if (_abstractPanel == null)
  420. _abstractPanel = new WrapPanelAbstraction(_itemsControl.Items.Count);
  421.  
  422. _pixelMeasuredViewport = availableSize;
  423.  
  424. _realizedChildLayout.Clear();
  425.  
  426. Size realizedFrameSize = availableSize;
  427.  
  428. int itemCount = _itemsControl.Items.Count;
  429. int firstVisibleIndex = GetFirstVisibleIndex();
  430.  
  431. GeneratorPosition startPos = _generator.GeneratorPositionFromIndex(firstVisibleIndex);
  432.  
  433. int childIndex = (startPos.Offset == ) ? startPos.Index : startPos.Index + ;
  434. int current = firstVisibleIndex;
  435. int visibleSections = ;
  436. using (_generator.StartAt(startPos, GeneratorDirection.Forward, true))
  437. {
  438. bool stop = false;
  439. bool isHorizontal = Orientation == Orientation.Horizontal;
  440. double currentX = ;
  441. double currentY = ;
  442. double maxItemSize = ;
  443. int currentSection = GetFirstVisibleSection();
  444. while (current < itemCount)
  445. {
  446. bool newlyRealized;
  447.  
  448. // Get or create the child
  449. UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement;
  450. if (newlyRealized)
  451. {
  452. // Figure out if we need to insert the child at the end or somewhere in the middle
  453. if (childIndex >= _children.Count)
  454. {
  455. base.AddInternalChild(child);
  456. }
  457. else
  458. {
  459. base.InsertInternalChild(childIndex, child);
  460. }
  461. _generator.PrepareItemContainer(child);
  462. child.Measure(ChildSlotSize);
  463. }
  464. else
  465. {
  466. // The child has already been created, let's be sure it's in the right spot
  467. Debug.Assert(child == _children[childIndex], "Wrong child was generated");
  468. }
  469. childSize = child.DesiredSize;
  470. Rect childRect = new Rect(new Point(currentX, currentY), childSize);
  471. if (isHorizontal)
  472. {
  473. maxItemSize = Math.Max(maxItemSize, childRect.Height);
  474. if (childRect.Right > realizedFrameSize.Width) //wrap to a new line
  475. {
  476. currentY = currentY + maxItemSize;
  477. currentX = ;
  478. maxItemSize = childRect.Height;
  479. childRect.X = currentX;
  480. childRect.Y = currentY;
  481. currentSection++;
  482. visibleSections++;
  483. }
  484. if (currentY > realizedFrameSize.Height)
  485. stop = true;
  486. currentX = childRect.Right;
  487. }
  488. else
  489. {
  490. maxItemSize = Math.Max(maxItemSize, childRect.Width);
  491. if (childRect.Bottom > realizedFrameSize.Height) //wrap to a new column
  492. {
  493. currentX = currentX + maxItemSize;
  494. currentY = ;
  495. maxItemSize = childRect.Width;
  496. childRect.X = currentX;
  497. childRect.Y = currentY;
  498. currentSection++;
  499. visibleSections++;
  500. }
  501. if (currentX > realizedFrameSize.Width)
  502. stop = true;
  503. currentY = childRect.Bottom;
  504. }
  505. _realizedChildLayout.Add(child, childRect);
  506. _abstractPanel.SetItemSection(current, currentSection);
  507.  
  508. if (stop)
  509. break;
  510. current++;
  511. childIndex++;
  512. }
  513. }
  514. CleanUpItems(firstVisibleIndex, current - );
  515.  
  516. ComputeExtentAndViewport(availableSize, visibleSections);
  517.  
  518. return availableSize;
  519. }
  520. protected override Size ArrangeOverride(Size finalSize)
  521. {
  522. if (_children != null)
  523. {
  524. foreach (UIElement child in _children)
  525. {
  526. var layoutInfo = _realizedChildLayout[child];
  527. child.Arrange(layoutInfo);
  528. }
  529. }
  530. return finalSize;
  531. }
  532.  
  533. #endregion
  534.  
  535. #region IScrollInfo Members
  536.  
  537. private bool _canHScroll = false;
  538. public bool CanHorizontallyScroll
  539. {
  540. get { return _canHScroll; }
  541. set { _canHScroll = value; }
  542. }
  543.  
  544. private bool _canVScroll = false;
  545. public bool CanVerticallyScroll
  546. {
  547. get { return _canVScroll; }
  548. set { _canVScroll = value; }
  549. }
  550.  
  551. public double ExtentHeight
  552. {
  553. get { return _extent.Height; }
  554. }
  555.  
  556. public double ExtentWidth
  557. {
  558. get { return _extent.Width; }
  559. }
  560.  
  561. public double HorizontalOffset
  562. {
  563. get { return _offset.X; }
  564. }
  565.  
  566. public double VerticalOffset
  567. {
  568. get { return _offset.Y; }
  569. }
  570.  
  571. public void LineDown()
  572. {
  573. if (Orientation == Orientation.Vertical)
  574. SetVerticalOffset(VerticalOffset + );
  575. else
  576. SetVerticalOffset(VerticalOffset + );
  577. }
  578.  
  579. public void LineLeft()
  580. {
  581. if (Orientation == Orientation.Horizontal)
  582. SetHorizontalOffset(HorizontalOffset - );
  583. else
  584. SetHorizontalOffset(HorizontalOffset - );
  585. }
  586.  
  587. public void LineRight()
  588. {
  589. if (Orientation == Orientation.Horizontal)
  590. SetHorizontalOffset(HorizontalOffset + );
  591. else
  592. SetHorizontalOffset(HorizontalOffset + );
  593. }
  594.  
  595. public void LineUp()
  596. {
  597. if (Orientation == Orientation.Vertical)
  598. SetVerticalOffset(VerticalOffset - );
  599. else
  600. SetVerticalOffset(VerticalOffset - );
  601. }
  602.  
  603. public Rect MakeVisible(Visual visual, Rect rectangle)
  604. {
  605. var gen = (ItemContainerGenerator)_generator.GetItemContainerGeneratorForPanel(this);
  606. var element = (UIElement)visual;
  607. int itemIndex = gen.IndexFromContainer(element);
  608. while (itemIndex == -)
  609. {
  610. element = (UIElement)VisualTreeHelper.GetParent(element);
  611. itemIndex = gen.IndexFromContainer(element);
  612. }
  613. int section = _abstractPanel[itemIndex].Section;
  614. Rect elementRect = _realizedChildLayout[element];
  615. if (Orientation == Orientation.Horizontal)
  616. {
  617. double viewportHeight = _pixelMeasuredViewport.Height;
  618. if (elementRect.Bottom > viewportHeight)
  619. _offset.Y += ;
  620. else if (elementRect.Top < )
  621. _offset.Y -= ;
  622. }
  623. else
  624. {
  625. double viewportWidth = _pixelMeasuredViewport.Width;
  626. if (elementRect.Right > viewportWidth)
  627. _offset.X += ;
  628. else if (elementRect.Left < )
  629. _offset.X -= ;
  630. }
  631. InvalidateMeasure();
  632. return elementRect;
  633. }
  634.  
  635. public void MouseWheelDown()
  636. {
  637. PageDown();
  638. }
  639.  
  640. public void MouseWheelLeft()
  641. {
  642. PageLeft();
  643. }
  644.  
  645. public void MouseWheelRight()
  646. {
  647. PageRight();
  648. }
  649.  
  650. public void MouseWheelUp()
  651. {
  652. PageUp();
  653. }
  654.  
  655. public void PageDown()
  656. {
  657. SetVerticalOffset(VerticalOffset + _viewport.Height * 0.8);
  658. }
  659.  
  660. public void PageLeft()
  661. {
  662. SetHorizontalOffset(HorizontalOffset - _viewport.Width * 0.8);
  663. }
  664.  
  665. public void PageRight()
  666. {
  667. SetHorizontalOffset(HorizontalOffset + _viewport.Width * 0.8);
  668. }
  669.  
  670. public void PageUp()
  671. {
  672. SetVerticalOffset(VerticalOffset - _viewport.Height * 0.8);
  673. }
  674.  
  675. private ScrollViewer _owner;
  676. public ScrollViewer ScrollOwner
  677. {
  678. get { return _owner; }
  679. set { _owner = value; }
  680. }
  681.  
  682. public void SetHorizontalOffset(double offset)
  683. {
  684. if (offset < || _viewport.Width >= _extent.Width)
  685. {
  686. offset = ;
  687. }
  688. else
  689. {
  690. if (offset + _viewport.Width >= _extent.Width)
  691. {
  692. offset = _extent.Width - _viewport.Width;
  693. }
  694. }
  695.  
  696. _offset.X = offset;
  697.  
  698. if (_owner != null)
  699. _owner.InvalidateScrollInfo();
  700.  
  701. InvalidateMeasure();
  702. firstIndex = GetFirstVisibleIndex();
  703. }
  704.  
  705. public void SetVerticalOffset(double offset)
  706. {
  707. if (offset < || _viewport.Height >= _extent.Height)
  708. {
  709. offset = ;
  710. }
  711. else
  712. {
  713. if (offset + _viewport.Height >= _extent.Height)
  714. {
  715. offset = _extent.Height - _viewport.Height;
  716. }
  717. }
  718.  
  719. _offset.Y = offset;
  720.  
  721. if (_owner != null)
  722. _owner.InvalidateScrollInfo();
  723.  
  724. //_trans.Y = -offset;
  725.  
  726. InvalidateMeasure();
  727. firstIndex = GetFirstVisibleIndex();
  728. }
  729.  
  730. public double ViewportHeight
  731. {
  732. get { return _viewport.Height; }
  733. }
  734.  
  735. public double ViewportWidth
  736. {
  737. get { return _viewport.Width; }
  738. }
  739.  
  740. #endregion
  741.  
  742. #region helper data structures
  743.  
  744. class ItemAbstraction
  745. {
  746. public ItemAbstraction(WrapPanelAbstraction panel, int index)
  747. {
  748. _panel = panel;
  749. _index = index;
  750. }
  751.  
  752. WrapPanelAbstraction _panel;
  753.  
  754. public readonly int _index;
  755.  
  756. int _sectionIndex = -;
  757. public int SectionIndex
  758. {
  759. get
  760. {
  761. if (_sectionIndex == -)
  762. {
  763. return _index % _panel._averageItemsPerSection - ;
  764. }
  765. return _sectionIndex;
  766. }
  767. set
  768. {
  769. if (_sectionIndex == -)
  770. _sectionIndex = value;
  771. }
  772. }
  773.  
  774. int _section = -;
  775. public int Section
  776. {
  777. get
  778. {
  779. if (_section == -)
  780. {
  781. return _index / _panel._averageItemsPerSection;
  782. }
  783. return _section;
  784. }
  785. set
  786. {
  787. if (_section == -)
  788. _section = value;
  789. }
  790. }
  791. }
  792.  
  793. class WrapPanelAbstraction : IEnumerable<ItemAbstraction>
  794. {
  795. public WrapPanelAbstraction(int itemCount)
  796. {
  797. List<ItemAbstraction> items = new List<ItemAbstraction>(itemCount);
  798. for (int i = ; i < itemCount; i++)
  799. {
  800. ItemAbstraction item = new ItemAbstraction(this, i);
  801. items.Add(item);
  802. }
  803.  
  804. Items = new ReadOnlyCollection<ItemAbstraction>(items);
  805. _averageItemsPerSection = itemCount;
  806. _itemCount = itemCount;
  807. }
  808.  
  809. public readonly int _itemCount;
  810. public int _averageItemsPerSection;
  811. private int _currentSetSection = -;
  812. private int _currentSetItemIndex = -;
  813. private int _itemsInCurrentSecction = ;
  814. private object _syncRoot = new object();
  815.  
  816. public int SectionCount
  817. {
  818. get
  819. {
  820. int ret = _currentSetSection + ;
  821. if (_currentSetItemIndex + < Items.Count)
  822. {
  823. int itemsLeft = Items.Count - _currentSetItemIndex;
  824. ret += itemsLeft / _averageItemsPerSection + ;
  825. }
  826. return ret;
  827. }
  828. }
  829.  
  830. private ReadOnlyCollection<ItemAbstraction> Items { get; set; }
  831.  
  832. public void SetItemSection(int index, int section)
  833. {
  834. lock (_syncRoot)
  835. {
  836. if (section <= _currentSetSection + && index == _currentSetItemIndex + )
  837. {
  838. _currentSetItemIndex++;
  839. Items[index].Section = section;
  840. if (section == _currentSetSection + )
  841. {
  842. _currentSetSection = section;
  843. if (section > )
  844. {
  845. _averageItemsPerSection = (index) / (section);
  846. }
  847. _itemsInCurrentSecction = ;
  848. }
  849. else
  850. _itemsInCurrentSecction++;
  851. Items[index].SectionIndex = _itemsInCurrentSecction - ;
  852. }
  853. }
  854. }
  855.  
  856. public ItemAbstraction this[int index]
  857. {
  858. get { return Items[index]; }
  859. }
  860.  
  861. #region IEnumerable<ItemAbstraction> Members
  862.  
  863. public IEnumerator<ItemAbstraction> GetEnumerator()
  864. {
  865. return Items.GetEnumerator();
  866. }
  867.  
  868. #endregion
  869.  
  870. #region IEnumerable Members
  871.  
  872. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  873. {
  874. return GetEnumerator();
  875. }
  876.  
  877. #endregion
  878. }
  879.  
  880. #endregion
  881. }

VirtualizingWrapPanel

来源:http://www.codeproject.com/Articles/75847/Virtualizing-WrapPanel

  1. // class from: https://github.com/samueldjack/VirtualCollection/blob/master/VirtualCollection/VirtualCollection/VirtualizingWrapPanel.cs
  2. // MakeVisible() method from: http://www.switchonthecode.com/tutorials/wpf-tutorial-implementing-iscrollinfo
  3. public class VirtualLizingTilePanel : VirtualizingPanel, IScrollInfo
  4. {
  5. private const double ScrollLineAmount = 16.0;
  6.  
  7. private Size _extentSize;
  8. private Size _viewportSize;
  9. private Point _offset;
  10. private ItemsControl _itemsControl;
  11. private readonly Dictionary<UIElement, Rect> _childLayouts = new Dictionary<UIElement, Rect>();
  12.  
  13. public static readonly DependencyProperty ItemWidthProperty =
  14. DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualLizingTilePanel), new PropertyMetadata(1.0, HandleItemDimensionChanged));
  15.  
  16. public static readonly DependencyProperty ItemHeightProperty =
  17. DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualLizingTilePanel), new PropertyMetadata(1.0, HandleItemDimensionChanged));
  18.  
  19. private static readonly DependencyProperty VirtualItemIndexProperty =
  20. DependencyProperty.RegisterAttached("VirtualItemIndex", typeof(int), typeof(VirtualLizingTilePanel), new PropertyMetadata(-));
  21. private IRecyclingItemContainerGenerator _itemsGenerator;
  22.  
  23. private bool _isInMeasure;
  24.  
  25. private static int GetVirtualItemIndex(DependencyObject obj)
  26. {
  27. return (int)obj.GetValue(VirtualItemIndexProperty);
  28. }
  29.  
  30. private static void SetVirtualItemIndex(DependencyObject obj, int value)
  31. {
  32. obj.SetValue(VirtualItemIndexProperty, value);
  33. }
  34.  
  35. public double ItemHeight
  36. {
  37. get { return (double)GetValue(ItemHeightProperty); }
  38. set { SetValue(ItemHeightProperty, value); }
  39. }
  40.  
  41. public double ItemWidth
  42. {
  43. get { return (double)GetValue(ItemWidthProperty); }
  44. set { SetValue(ItemWidthProperty, value); }
  45. }
  46.  
  47. public VirtualLizingTilePanel()
  48. {
  49. if (!DesignerProperties.GetIsInDesignMode(this))
  50. {
  51. Dispatcher.BeginInvoke((Action)Initialize);
  52. }
  53. }
  54.  
  55. private void Initialize()
  56. {
  57. _itemsControl = ItemsControl.GetItemsOwner(this);
  58. _itemsGenerator = (IRecyclingItemContainerGenerator)ItemContainerGenerator;
  59.  
  60. InvalidateMeasure();
  61. }
  62.  
  63. protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
  64. {
  65. base.OnItemsChanged(sender, args);
  66.  
  67. InvalidateMeasure();
  68. }
  69.  
  70. protected override Size MeasureOverride(Size availableSize)
  71. {
  72. if (_itemsControl == null)
  73. {
  74. return availableSize;
  75. }
  76.  
  77. _isInMeasure = true;
  78. _childLayouts.Clear();
  79.  
  80. var extentInfo = GetExtentInfo(availableSize, ItemHeight);
  81.  
  82. EnsureScrollOffsetIsWithinConstrains(extentInfo);
  83.  
  84. var layoutInfo = GetLayoutInfo(availableSize, ItemHeight, extentInfo);
  85.  
  86. RecycleItems(layoutInfo);
  87.  
  88. // Determine where the first item is in relation to previously realized items
  89. var generatorStartPosition = _itemsGenerator.GeneratorPositionFromIndex(layoutInfo.FirstRealizedItemIndex);
  90.  
  91. var visualIndex = ;
  92.  
  93. var currentX = layoutInfo.FirstRealizedItemLeft;
  94. var currentY = layoutInfo.FirstRealizedLineTop;
  95.  
  96. using (_itemsGenerator.StartAt(generatorStartPosition, GeneratorDirection.Forward, true))
  97. {
  98. for (var itemIndex = layoutInfo.FirstRealizedItemIndex; itemIndex <= layoutInfo.LastRealizedItemIndex; itemIndex++, visualIndex++)
  99. {
  100. bool newlyRealized;
  101.  
  102. var child = (UIElement)_itemsGenerator.GenerateNext(out newlyRealized);
  103. SetVirtualItemIndex(child, itemIndex);
  104.  
  105. if (newlyRealized)
  106. {
  107. InsertInternalChild(visualIndex, child);
  108. }
  109. else
  110. {
  111. // check if item needs to be moved into a new position in the Children collection
  112. if (visualIndex < Children.Count)
  113. {
  114. if (Children[visualIndex] != child)
  115. {
  116. var childCurrentIndex = Children.IndexOf(child);
  117.  
  118. if (childCurrentIndex >= )
  119. {
  120. RemoveInternalChildRange(childCurrentIndex, );
  121. }
  122.  
  123. InsertInternalChild(visualIndex, child);
  124. }
  125. }
  126. else
  127. {
  128. // we know that the child can't already be in the children collection
  129. // because we've been inserting children in correct visualIndex order,
  130. // and this child has a visualIndex greater than the Children.Count
  131. AddInternalChild(child);
  132. }
  133. }
  134.  
  135. // only prepare the item once it has been added to the visual tree
  136. _itemsGenerator.PrepareItemContainer(child);
  137.  
  138. child.Measure(new Size(ItemWidth, ItemHeight));
  139.  
  140. _childLayouts.Add(child, new Rect(currentX, currentY, ItemWidth, ItemHeight));
  141.  
  142. if (currentX + ItemWidth * >= availableSize.Width)
  143. {
  144. // wrap to a new line
  145. currentY += ItemHeight;
  146. currentX = ;
  147. }
  148. else
  149. {
  150. currentX += ItemWidth;
  151. }
  152. }
  153. }
  154.  
  155. RemoveRedundantChildren();
  156. UpdateScrollInfo(availableSize, extentInfo);
  157.  
  158. var desiredSize = new Size(double.IsInfinity(availableSize.Width) ? : availableSize.Width,
  159. double.IsInfinity(availableSize.Height) ? : availableSize.Height);
  160.  
  161. _isInMeasure = false;
  162.  
  163. return desiredSize;
  164. }
  165.  
  166. private void EnsureScrollOffsetIsWithinConstrains(ExtentInfo extentInfo)
  167. {
  168. _offset.Y = Clamp(_offset.Y, , extentInfo.MaxVerticalOffset);
  169. }
  170.  
  171. private void RecycleItems(ItemLayoutInfo layoutInfo)
  172. {
  173. foreach (UIElement child in Children)
  174. {
  175. var virtualItemIndex = GetVirtualItemIndex(child);
  176.  
  177. if (virtualItemIndex < layoutInfo.FirstRealizedItemIndex || virtualItemIndex > layoutInfo.LastRealizedItemIndex)
  178. {
  179. var generatorPosition = _itemsGenerator.GeneratorPositionFromIndex(virtualItemIndex);
  180. if (generatorPosition.Index >= )
  181. {
  182. _itemsGenerator.Recycle(generatorPosition, );
  183. }
  184. }
  185.  
  186. SetVirtualItemIndex(child, -);
  187. }
  188. }
  189.  
  190. protected override Size ArrangeOverride(Size finalSize)
  191. {
  192. foreach (UIElement child in Children)
  193. {
  194. child.Arrange(_childLayouts[child]);
  195. }
  196.  
  197. return finalSize;
  198. }
  199.  
  200. private void UpdateScrollInfo(Size availableSize, ExtentInfo extentInfo)
  201. {
  202. _viewportSize = availableSize;
  203. _extentSize = new Size(availableSize.Width, extentInfo.ExtentHeight);
  204.  
  205. InvalidateScrollInfo();
  206. }
  207.  
  208. private void RemoveRedundantChildren()
  209. {
  210. // iterate backwards through the child collection because we're going to be
  211. // removing items from it
  212. for (var i = Children.Count - ; i >= ; i--)
  213. {
  214. var child = Children[i];
  215.  
  216. // if the virtual item index is -1, this indicates
  217. // it is a recycled item that hasn't been reused this time round
  218. if (GetVirtualItemIndex(child) == -)
  219. {
  220. RemoveInternalChildRange(i, );
  221. }
  222. }
  223. }
  224.  
  225. private ItemLayoutInfo GetLayoutInfo(Size availableSize, double itemHeight, ExtentInfo extentInfo)
  226. {
  227. if (_itemsControl == null)
  228. {
  229. return new ItemLayoutInfo();
  230. }
  231.  
  232. // we need to ensure that there is one realized item prior to the first visible item, and one after the last visible item,
  233. // so that keyboard navigation works properly. For example, when focus is on the first visible item, and the user
  234. // navigates up, the ListBox selects the previous item, and the scrolls that into view - and this triggers the loading of the rest of the items
  235. // in that row
  236.  
  237. var firstVisibleLine = (int)Math.Floor(VerticalOffset / itemHeight);
  238.  
  239. var firstRealizedIndex = Math.Max(extentInfo.ItemsPerLine * firstVisibleLine - , );
  240. var firstRealizedItemLeft = firstRealizedIndex % extentInfo.ItemsPerLine * ItemWidth - HorizontalOffset;
  241. var firstRealizedItemTop = (firstRealizedIndex / extentInfo.ItemsPerLine) * itemHeight - VerticalOffset;
  242.  
  243. var firstCompleteLineTop = (firstVisibleLine == ? firstRealizedItemTop : firstRealizedItemTop + ItemHeight);
  244. var completeRealizedLines = (int)Math.Ceiling((availableSize.Height - firstCompleteLineTop) / itemHeight);
  245.  
  246. var lastRealizedIndex = Math.Min(firstRealizedIndex + completeRealizedLines * extentInfo.ItemsPerLine + , _itemsControl.Items.Count - );
  247.  
  248. return new ItemLayoutInfo
  249. {
  250. FirstRealizedItemIndex = firstRealizedIndex,
  251. FirstRealizedItemLeft = firstRealizedItemLeft,
  252. FirstRealizedLineTop = firstRealizedItemTop,
  253. LastRealizedItemIndex = lastRealizedIndex,
  254. };
  255. }
  256.  
  257. private ExtentInfo GetExtentInfo(Size viewPortSize, double itemHeight)
  258. {
  259. if (_itemsControl == null)
  260. {
  261. return new ExtentInfo();
  262. }
  263.  
  264. var itemsPerLine = Math.Max((int)Math.Floor(viewPortSize.Width / ItemWidth), );
  265. var totalLines = (int)Math.Ceiling((double)_itemsControl.Items.Count / itemsPerLine);
  266. var extentHeight = Math.Max(totalLines * ItemHeight, viewPortSize.Height);
  267.  
  268. return new ExtentInfo
  269. {
  270. ItemsPerLine = itemsPerLine,
  271. TotalLines = totalLines,
  272. ExtentHeight = extentHeight,
  273. MaxVerticalOffset = extentHeight - viewPortSize.Height,
  274. };
  275. }
  276.  
  277. public void LineUp()
  278. {
  279. SetVerticalOffset(VerticalOffset - ScrollLineAmount);
  280. }
  281.  
  282. public void LineDown()
  283. {
  284. SetVerticalOffset(VerticalOffset + ScrollLineAmount);
  285. }
  286.  
  287. public void LineLeft()
  288. {
  289. SetHorizontalOffset(HorizontalOffset + ScrollLineAmount);
  290. }
  291.  
  292. public void LineRight()
  293. {
  294. SetHorizontalOffset(HorizontalOffset - ScrollLineAmount);
  295. }
  296.  
  297. public void PageUp()
  298. {
  299. SetVerticalOffset(VerticalOffset - ViewportHeight);
  300. }
  301.  
  302. public void PageDown()
  303. {
  304. SetVerticalOffset(VerticalOffset + ViewportHeight);
  305. }
  306.  
  307. public void PageLeft()
  308. {
  309. SetHorizontalOffset(HorizontalOffset + ItemWidth);
  310. }
  311.  
  312. public void PageRight()
  313. {
  314. SetHorizontalOffset(HorizontalOffset - ItemWidth);
  315. }
  316.  
  317. public void MouseWheelUp()
  318. {
  319. SetVerticalOffset(VerticalOffset - ScrollLineAmount * SystemParameters.WheelScrollLines);
  320. }
  321.  
  322. public void MouseWheelDown()
  323. {
  324. SetVerticalOffset(VerticalOffset + ScrollLineAmount * SystemParameters.WheelScrollLines);
  325. }
  326.  
  327. public void MouseWheelLeft()
  328. {
  329. SetHorizontalOffset(HorizontalOffset - ScrollLineAmount * SystemParameters.WheelScrollLines);
  330. }
  331.  
  332. public void MouseWheelRight()
  333. {
  334. SetHorizontalOffset(HorizontalOffset + ScrollLineAmount * SystemParameters.WheelScrollLines);
  335. }
  336.  
  337. public void SetHorizontalOffset(double offset)
  338. {
  339. if (_isInMeasure)
  340. {
  341. return;
  342. }
  343.  
  344. offset = Clamp(offset, , ExtentWidth - ViewportWidth);
  345. _offset = new Point(offset, _offset.Y);
  346.  
  347. InvalidateScrollInfo();
  348. InvalidateMeasure();
  349. }
  350.  
  351. public void SetVerticalOffset(double offset)
  352. {
  353. if (_isInMeasure)
  354. {
  355. return;
  356. }
  357.  
  358. offset = Clamp(offset, , ExtentHeight - ViewportHeight);
  359. _offset = new Point(_offset.X, offset);
  360.  
  361. InvalidateScrollInfo();
  362. InvalidateMeasure();
  363. }
  364.  
  365. public Rect MakeVisible(Visual visual, Rect rectangle)
  366. {
  367. if (rectangle.IsEmpty ||
  368. visual == null ||
  369. visual == this ||
  370. !IsAncestorOf(visual))
  371. {
  372. return Rect.Empty;
  373. }
  374.  
  375. rectangle = visual.TransformToAncestor(this).TransformBounds(rectangle);
  376.  
  377. var viewRect = new Rect(HorizontalOffset, VerticalOffset, ViewportWidth, ViewportHeight);
  378. rectangle.X += viewRect.X;
  379. rectangle.Y += viewRect.Y;
  380.  
  381. viewRect.X = CalculateNewScrollOffset(viewRect.Left, viewRect.Right, rectangle.Left, rectangle.Right);
  382. viewRect.Y = CalculateNewScrollOffset(viewRect.Top, viewRect.Bottom, rectangle.Top, rectangle.Bottom);
  383.  
  384. SetHorizontalOffset(viewRect.X);
  385. SetVerticalOffset(viewRect.Y);
  386. rectangle.Intersect(viewRect);
  387.  
  388. rectangle.X -= viewRect.X;
  389. rectangle.Y -= viewRect.Y;
  390.  
  391. return rectangle;
  392. }
  393.  
  394. private static double CalculateNewScrollOffset(double topView, double bottomView, double topChild, double bottomChild)
  395. {
  396. var offBottom = topChild < topView && bottomChild < bottomView;
  397. var offTop = bottomChild > bottomView && topChild > topView;
  398. var tooLarge = (bottomChild - topChild) > (bottomView - topView);
  399.  
  400. if (!offBottom && !offTop)
  401. return topView;
  402.  
  403. if ((offBottom && !tooLarge) || (offTop && tooLarge))
  404. return topChild;
  405.  
  406. return bottomChild - (bottomView - topView);
  407. }
  408.  
  409. public ItemLayoutInfo GetVisibleItemsRange()
  410. {
  411. return GetLayoutInfo(_viewportSize, ItemHeight, GetExtentInfo(_viewportSize, ItemHeight));
  412. }
  413.  
  414. public bool CanVerticallyScroll
  415. {
  416. get;
  417. set;
  418. }
  419.  
  420. public bool CanHorizontallyScroll
  421. {
  422. get;
  423. set;
  424. }
  425.  
  426. public double ExtentWidth
  427. {
  428. get { return _extentSize.Width; }
  429. }
  430.  
  431. public double ExtentHeight
  432. {
  433. get { return _extentSize.Height; }
  434. }
  435.  
  436. public double ViewportWidth
  437. {
  438. get { return _viewportSize.Width; }
  439. }
  440.  
  441. public double ViewportHeight
  442. {
  443. get { return _viewportSize.Height; }
  444. }
  445.  
  446. public double HorizontalOffset
  447. {
  448. get { return _offset.X; }
  449. }
  450.  
  451. public double VerticalOffset
  452. {
  453. get { return _offset.Y; }
  454. }
  455.  
  456. public ScrollViewer ScrollOwner
  457. {
  458. get;
  459. set;
  460. }
  461.  
  462. private void InvalidateScrollInfo()
  463. {
  464. if (ScrollOwner != null)
  465. {
  466. ScrollOwner.InvalidateScrollInfo();
  467. }
  468. }
  469.  
  470. private static void HandleItemDimensionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  471. {
  472. var wrapPanel = (d as VirtualLizingTilePanel);
  473.  
  474. if (wrapPanel != null)
  475. wrapPanel.InvalidateMeasure();
  476. }
  477.  
  478. private double Clamp(double value, double min, double max)
  479. {
  480. return Math.Min(Math.Max(value, min), max);
  481. }
  482.  
  483. internal class ExtentInfo
  484. {
  485. public int ItemsPerLine;
  486. public int TotalLines;
  487. public double ExtentHeight;
  488. public double MaxVerticalOffset;
  489. }
  490.  
  491. public class ItemLayoutInfo
  492. {
  493. public int FirstRealizedItemIndex;
  494. public double FirstRealizedLineTop;
  495. public double FirstRealizedItemLeft;
  496. public int LastRealizedItemIndex;
  497. }
  498. }

VirtualLizingTilePanel

来源:

// class from: https://github.com/samueldjack/VirtualCollection/blob/master/VirtualCollection/VirtualCollection/VirtualizingWrapPanel.cs
// MakeVisible() method from: http://www.switchonthecode.com/tutorials/wpf-tutorial-implementing-iscrollinfo

二、数据方面的一个处理

  1. /// <summary>
  2. /// 为ListBox支持数据虚拟化技术
  3. /// </summary>
  4. public class VirtualDataForListBox<T> : IDisposable, INotifyPropertyChanged where T : class
  5. {
  6. public event PropertyChangedEventHandler PropertyChanged;
  7.  
  8. private DelayHelper delay;
  9.  
  10. private ListBox listBox;
  11. /// <summary>
  12. /// 垂直滚动条
  13. /// </summary>
  14. private ScrollBar bar;
  15. /// <summary>
  16. /// 滚动视图
  17. /// </summary>
  18. private ScrollViewer viewer;
  19. /// <summary>
  20. /// 数据源
  21. /// </summary>
  22. private ObservableCollection<T> sources;
  23.  
  24. /// <summary>
  25. /// 是否已初始化完毕
  26. /// </summary>
  27. protected bool Inited { get; set; }
  28.  
  29. /// <summary>
  30. /// 偏移量
  31. /// </summary>
  32. protected double Offset { get; set; }
  33.  
  34. /// <summary>
  35. /// 偏移数量
  36. /// </summary>
  37. protected int OffsetCount { get; set; }
  38.  
  39. /// <summary>
  40. /// 偏移方向
  41. /// <para>True:向上</para>
  42. /// <para>False:向下</para>
  43. /// </summary>
  44. protected bool OffsetDirection { get; set; }
  45.  
  46. public Func<bool> CheckCanScrollToBottom;
  47.  
  48. #region 数据绑定
  49.  
  50. private ObservableCollection<T> virtualData;
  51.  
  52. /// <summary>
  53. /// 虚拟数据
  54. /// </summary>
  55. public ObservableCollection<T> VirtualData
  56. {
  57. get { return virtualData; }
  58. protected set
  59. {
  60. virtualData = value;
  61. if (this.PropertyChanged != null)
  62. {
  63. this.PropertyChanged(this, new PropertyChangedEventArgs(nameof(VirtualData)));
  64. }
  65. }
  66. }
  67.  
  68. #endregion
  69.  
  70. #region 配置参数
  71.  
  72. /// <summary>
  73. /// 初始化时最多加载的数据量
  74. /// <para>需要保证:如果数据未完全加载,ListBox一定可以出现滚动条</para>
  75. /// </summary>
  76. [DefaultValue()]
  77. public int InitLoadCount { get; set; }
  78.  
  79. /// <summary>
  80. /// 递增的数量值
  81. /// <para>滚动条滚动到两端时,每次自动加载的数据量</para>
  82. /// <para>子项数量超过容器的最大数量<paramref name="MaxCount"/>时,自动减少的数量</para>
  83. /// </summary>
  84. [DefaultValue()]
  85. public int IncreasingCount { get; set; }
  86.  
  87. /// <summary>
  88. /// 子项的最大数量
  89. /// </summary>
  90. [DefaultValue()]
  91. public int MaxCount { get; set; }
  92.  
  93. #endregion
  94.  
  95. /// <summary>
  96. /// 当前显示的虚拟数据起始索引
  97. /// </summary>
  98. protected int StartVirtualIndex { get; set; }
  99.  
  100. /// <summary>
  101. /// 当前显示的虚拟数据的终止索引
  102. /// </summary>
  103. protected int EndVirtualIndex { get; set; }
  104.  
  105. /// <summary>
  106. /// 忽略滚动条滚动事件
  107. /// </summary>
  108. protected bool IgnoreBarChanged { get; set; }
  109.  
  110. public VirtualDataForListBox(ListBox listBox, ObservableCollection<T> sources)
  111. {
  112. if (listBox == null || sources == null)
  113. throw new ArgumentException(" listBox or sources is null ");
  114.  
  115. this.delay = new DelayHelper(, DelayLayout);
  116.  
  117. this.Inited = false;
  118. this.Offset = ;
  119.  
  120. this.listBox = listBox;
  121. this.sources = sources;
  122.  
  123. this.InitLoadCount = ;
  124. this.IncreasingCount = ;
  125. this.MaxCount = ;
  126.  
  127. this.EndVirtualIndex = -;
  128. this.StartVirtualIndex = -;
  129.  
  130. this.VirtualData = new ObservableCollection<T>();
  131. }
  132.  
  133. /// <summary>
  134. /// 初始化
  135. /// </summary>
  136. public void Init()
  137. {
  138. if (this.Inited)
  139. return;
  140.  
  141. if (this.listBox == null)
  142. {
  143. LogHelper.Warning("数据虚拟化-初始化失败");
  144. return;
  145. }
  146.  
  147. // 监控滚动条
  148. this.bar = this.listBox.GetFirstChildT<ScrollBar, ListBoxItem>(t => t.Orientation == Orientation.Vertical);
  149. this.viewer = this.listBox.GetFirstChildT<ScrollViewer, ListBoxItem>(null);
  150.  
  151. if (this.bar == null || this.viewer == null)
  152. {
  153. LogHelper.Warning("数据虚拟化-初始化失败");
  154. return;
  155. }
  156.  
  157. // 绑定数据源
  158. this.listBox.SetBinding(ListBox.ItemsSourceProperty, new Binding(nameof(this.VirtualData)) { Source = this, });
  159.  
  160. this.ReloadEndData();
  161.  
  162. // 监控滚动条
  163. this.bar.ValueChanged += Bar_ValueChanged;
  164. // 监控滚动视图
  165. this.viewer.LayoutUpdated += Viewer_LayoutUpdated;
  166. // 监控数据源
  167. this.sources.CollectionChanged += Sources_CollectionChanged;
  168.  
  169. Inited = true;
  170. }
  171.  
  172. private void Viewer_LayoutUpdated(object sender, EventArgs e)
  173. {
  174. if (!this.Inited)
  175. return;
  176.  
  177. Console.WriteLine(" Viewer_LayoutUpdated ");
  178.  
  179. if (this.Offset == || this.IgnoreBarChanged)
  180. return;
  181.  
  182. this.delay.DelayAction();
  183. }
  184.  
  185. private void DelayLayout()
  186. {
  187. if (!this.Inited)
  188. return;
  189.  
  190. var view = new ViewDecorate(this.viewer);
  191.  
  192. view.DispatcherAction(() =>
  193. {
  194. if (this.Offset == )
  195. return;
  196.  
  197. try
  198. {
  199. this.IgnoreBarChanged = true;
  200.  
  201. double temp = ;
  202. // 向上
  203. if (this.OffsetDirection)
  204. {
  205. for (int i = ; i < this.OffsetCount && i < this.VirtualData.Count; i++)
  206. {
  207. temp += (this.listBox.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem).ActualHeight;
  208. }
  209. }
  210.  
  211. this.viewer.ScrollToVerticalOffset(this.Offset + temp);
  212. Console.WriteLine(" Viewer_LayoutUpdated ----------------------- Over ");
  213. }
  214. finally
  215. {
  216. this.Offset = ;
  217. this.IgnoreBarChanged = false;
  218. }
  219. });
  220. }
  221.  
  222. /// <summary>
  223. /// 滚动条滚动
  224. /// </summary>
  225. /// <param name="sender"></param>
  226. /// <param name="e"></param>
  227. private void Bar_ValueChanged(object sender, System.Windows.RoutedPropertyChangedEventArgs<double> e)
  228. {
  229. if (!this.Inited)
  230. return;
  231.  
  232. if (this.IgnoreBarChanged || this.Offset != )
  233. {
  234. e.Handled = true;
  235. return;
  236. }
  237.  
  238. try
  239. {
  240. this.IgnoreBarChanged = true;
  241.  
  242. const int count = ;
  243.  
  244. // 向下滚动到端部
  245. if (e.NewValue > e.OldValue && e.NewValue + count >= this.bar.Maximum)
  246. {
  247. TryScrollDown(e.NewValue - e.OldValue);
  248. }
  249. // 向上滚动到端部
  250. else if (e.NewValue < e.OldValue && e.NewValue - count <= )
  251. {
  252. TryScrollUp(e.OldValue - e.NewValue);
  253. }
  254. }
  255. finally
  256. {
  257. e.Handled = true;
  258. this.IgnoreBarChanged = false;
  259. }
  260. }
  261.  
  262. /// <summary>
  263. /// 数据源发生变化
  264. /// </summary>
  265. /// <param name="sender"></param>
  266. /// <param name="e"></param>
  267. private void Sources_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  268. {
  269. if (!this.Inited)
  270. return;
  271.  
  272. if (e.Action == NotifyCollectionChangedAction.Add)
  273. {
  274. // 新消息到达、尝试将滚动条滚动到底部
  275. this.MoveToBottom();
  276. }
  277. else if (e.Action == NotifyCollectionChangedAction.Remove)
  278. {
  279. this.IgnoreBarChanged = true;
  280.  
  281. // 移除旧数据
  282. foreach (var item in e.OldItems)
  283. {
  284. if (item is T)
  285. this.VirtualData.Remove(item as T);
  286. }
  287.  
  288. this.ReCalIndex();
  289.  
  290. if (this.StartVirtualIndex == - || this.EndVirtualIndex == -)
  291. {
  292. this.ReloadEndData();
  293. }
  294. else
  295. {
  296. if (this.VirtualData.Count < this.InitLoadCount)
  297. {
  298. // 数量过少、尝试填充数据
  299. this.LoadMoreData();
  300. }
  301. }
  302.  
  303. this.IgnoreBarChanged = false;
  304. }
  305. // 撤回消息
  306. else if (e.Action == NotifyCollectionChangedAction.Replace)
  307. {
  308. if (e.OldItems != null && e.OldItems.Count == && e.NewItems != null && e.NewItems.Count == )
  309. {
  310. var oldT = e.OldItems[] as T;
  311. var newT = e.NewItems[] as T;
  312. int index = this.VirtualData.IndexOf(oldT);
  313. if (index > -)
  314. {
  315. this.VirtualData[index] = newT;
  316. }
  317. }
  318.  
  319. }
  320. else if (e.Action == NotifyCollectionChangedAction.Reset)
  321. {
  322. this.IgnoreBarChanged = true;
  323.  
  324. this.ReloadEndData();
  325.  
  326. this.IgnoreBarChanged = false;
  327. }
  328. }
  329.  
  330. /// <summary>
  331. /// 将视图移动到某个索引的位置
  332. /// </summary>
  333. /// <param name="index"></param>
  334. public void MoveToIndex(int index)
  335. {
  336. if (!this.Inited)
  337. return;
  338.  
  339. if (index < || index >= this.sources.Count)
  340. return;
  341.  
  342. var t = this.sources[index];
  343. if (this.VirtualData.IndexOf(t) > -)
  344. {
  345. listBox.ScrollIntoView(t);
  346. return;
  347. }
  348.  
  349. int start = index - this.InitLoadCount;
  350. if (start < )
  351. start = ;
  352.  
  353. int end = index + this.InitLoadCount;
  354. if (end >= this.sources.Count)
  355. end = this.sources.Count - ;
  356.  
  357. int count = end - start + ;
  358. if (count == )
  359. return;
  360.  
  361. try
  362. {
  363. this.IgnoreBarChanged = true;
  364.  
  365. var list = this.sources.Skip(start).Take(count);
  366.  
  367. this.VirtualData.Clear();
  368.  
  369. foreach (var item in list)
  370. {
  371. this.VirtualData.Add(item);
  372. }
  373.  
  374. this.ReCalIndex();
  375.  
  376. listBox.ScrollIntoView(t);
  377. }
  378. finally
  379. {
  380. this.IgnoreBarChanged = false;
  381. }
  382. }
  383.  
  384. /// <summary>
  385. /// 将视图移动到底部
  386. /// </summary>
  387. public void MoveToBottom()
  388. {
  389. if (!this.Inited)
  390. return;
  391.  
  392. try
  393. {
  394. this.IgnoreBarChanged = true;
  395.  
  396. // 询问是否可以将滚动条滚动到底部
  397. if (this.CheckCanScrollToBottom != null && !this.CheckCanScrollToBottom())
  398. return;
  399.  
  400. // 超过最大显示容量、则重新加载末端数据
  401. if (this.StartVirtualIndex == - || this.sources.Count == || this.sources.Count - this.StartVirtualIndex > this.MaxCount)
  402. {
  403. this.ReloadEndData();
  404. return;
  405. }
  406.  
  407. // 没有需要加载的数据
  408. if (this.EndVirtualIndex == this.sources.Count - )
  409. {
  410. this.listBox.ScrollViewToBottom();
  411. return;
  412. }
  413.  
  414. // 平滑加载
  415. var count = this.EndVirtualIndex + ;
  416.  
  417. if (this.sources.Count > count)
  418. {
  419. var list = this.sources.Skip(count).ToList();
  420.  
  421. foreach (var item in list)
  422. {
  423. this.VirtualData.Add(item);
  424. }
  425.  
  426. this.ReCalIndex();
  427. this.listBox.ScrollViewToBottom();
  428. }
  429. }
  430. catch (Exception ex)
  431. {
  432. LogHelper.Execption(ex, "数据虚拟化");
  433. }
  434. finally
  435. {
  436. this.IgnoreBarChanged = false;
  437. }
  438. }
  439.  
  440. /// <summary>
  441. /// 重新计算索引值
  442. /// </summary>
  443. private void ReCalIndex()
  444. {
  445. if (this.VirtualData.Count > )
  446. {
  447. this.StartVirtualIndex = this.sources.IndexOf(this.VirtualData[]);
  448. this.EndVirtualIndex = this.sources.IndexOf(this.VirtualData[this.VirtualData.Count - ]);
  449.  
  450. if (this.StartVirtualIndex == - || this.EndVirtualIndex == - || this.EndVirtualIndex < this.StartVirtualIndex)
  451. {
  452. this.StartVirtualIndex = -;
  453. this.EndVirtualIndex = -;
  454. LogHelper.Warning("数据虚拟化-逻辑错误");
  455. }
  456. }
  457. else
  458. {
  459. this.StartVirtualIndex = -;
  460. this.EndVirtualIndex = -;
  461. }
  462. }
  463.  
  464. /// <summary>
  465. /// 重新初始化数据
  466. /// </summary>
  467. private void ReloadEndData()
  468. {
  469. if (this.VirtualData.Count > )
  470. {
  471. this.VirtualData.Clear();
  472.  
  473. this.EndVirtualIndex = -;
  474. this.StartVirtualIndex = -;
  475. }
  476.  
  477. if (this.sources != null && this.sources.Count > )
  478. {
  479. var list = this.sources.ListLastMaxCount(this.InitLoadCount);
  480.  
  481. if (list.Count > )
  482. {
  483. foreach (var item in list)
  484. {
  485. this.VirtualData.Add(item);
  486. }
  487.  
  488. this.ReCalIndex();
  489.  
  490. // 滚动条滚动到最底部
  491. this.listBox.ScrollViewToBottom();
  492. }
  493. }
  494. }
  495.  
  496. /// <summary>
  497. /// 删除数据时加载更多数据
  498. /// </summary>
  499. private void LoadMoreData()
  500. {
  501. List<T> data = this.sources.ListFindRangeWithMaxCount(this.StartVirtualIndex, this.InitLoadCount);
  502.  
  503. if (data.Count <= this.VirtualData.Count)
  504. {
  505. // 没有加载到更多数据
  506. return;
  507. }
  508.  
  509. int start = data.IndexOf(this.VirtualData[]);
  510. int end = data.LastIndexOf(this.VirtualData[this.VirtualData.Count - ]);
  511.  
  512. if (start == - || end == - || end < start)
  513. {
  514. LogHelper.Warning("数据虚拟化-逻辑错误");
  515. return;
  516. }
  517.  
  518. for (int i = ; i < data.Count; i++)
  519. {
  520. if (i < start)
  521. {
  522. this.VirtualData.Insert(i, data[i]);
  523. }
  524. else if (i > end)
  525. {
  526. this.VirtualData.Add(data[i]);
  527. }
  528. }
  529.  
  530. this.ReCalIndex();
  531. }
  532.  
  533. /// <summary>
  534. /// 向上滚动
  535. /// </summary>
  536. private void TryScrollUp(double offset)
  537. {
  538. // 没有数据了
  539. if (this.StartVirtualIndex == - || this.StartVirtualIndex == )
  540. return;
  541.  
  542. double tempOffset = this.viewer.ContentVerticalOffset;
  543. // 释放捕获的鼠标
  544. this.bar.Track.Thumb.ReleaseMouseCapture();
  545. this.bar.Track.DecreaseRepeatButton.ReleaseMouseCapture();
  546.  
  547. int tempCount = ;
  548.  
  549. var list = this.sources.ListLastMaxCount(this.StartVirtualIndex, this.IncreasingCount, false);
  550.  
  551. // list 为反序结果
  552. foreach (var item in list)
  553. {
  554. this.VirtualData.Insert(, item);
  555. tempCount++;
  556. }
  557.  
  558. if (this.VirtualData.Count > this.MaxCount)
  559. {
  560. for (int i = ; i < this.IncreasingCount; i++)
  561. {
  562. this.VirtualData.RemoveAt(this.VirtualData.Count - );
  563. }
  564. }
  565.  
  566. this.ReCalIndex();
  567.  
  568. this.OffsetDirection = true;
  569. this.OffsetCount = tempCount;
  570. this.Offset = tempOffset - offset;
  571.  
  572. if (this.Offset == )
  573. this.Offset = ;
  574. }
  575.  
  576. /// <summary>
  577. /// 向下滚动
  578. /// </summary>
  579. private void TryScrollDown(double offest)
  580. {
  581. // 没有数据了
  582. if (this.EndVirtualIndex == - || this.EndVirtualIndex == this.sources.Count - )
  583. return;
  584.  
  585. // 释放捕获的鼠标
  586. this.bar.Track.Thumb.ReleaseMouseCapture();
  587. this.bar.Track.IncreaseRepeatButton.ReleaseMouseCapture();
  588.  
  589. double tempOffset = this.viewer.ContentVerticalOffset;
  590.  
  591. var list = this.sources.Skip(this.EndVirtualIndex + ).Take(this.IncreasingCount);
  592.  
  593. foreach (var item in list)
  594. {
  595. this.VirtualData.Add(item);
  596. }
  597.  
  598. if (this.VirtualData.Count > this.MaxCount)
  599. {
  600. for (int i = ; i < this.IncreasingCount; i++)
  601. {
  602. tempOffset -= (this.listBox.ItemContainerGenerator.ContainerFromIndex() as ListBoxItem).ActualHeight;
  603. this.VirtualData.RemoveAt();
  604. }
  605. }
  606.  
  607. this.ReCalIndex();
  608.  
  609. this.OffsetDirection = false;
  610. this.OffsetCount = ;
  611. this.Offset = tempOffset + offest;
  612.  
  613. if (this.Offset == )
  614. this.Offset = ;
  615. }
  616.  
  617. public void Dispose()
  618. {
  619. if (!this.Inited)
  620. return;
  621.  
  622. this.Inited = false;
  623. this.VirtualData.Clear();
  624.  
  625. // 监控滚动条
  626. this.bar.ValueChanged -= Bar_ValueChanged;
  627. // 监控滚动视图
  628. this.viewer.LayoutUpdated -= Viewer_LayoutUpdated;
  629. // 监控数据源
  630. this.sources.CollectionChanged -= Sources_CollectionChanged;
  631.  
  632. this.CheckCanScrollToBottom = null;
  633.  
  634. this.delay.Dispose();
  635. }
  636. }

VirtualDataForListBox

该处理方式相当于根据滚动条的滚动适时增减items,当然该类的应用有一定的局限性,不过操作滚动条的方式还是具有借鉴意义的。

源码出处不详。

三、补充

启用UI虚拟化的两个附加属性:

1、ScrollViewer.CanContentScroll="True"

2、VirtualizingStackPanel.IsVirtualizing="True"

WPF 虚拟化 VirtualizingWrapPanel 和 VirtualLizingTilePanel的更多相关文章

  1. wpf 虚拟化操作异常

    根据这篇文章提供的方法会导致搜索变慢及有时候搜索不到 WPF中ItemsControl应用虚拟化时找到子元素的方法, 具体可以修改为下面代码: //Action action = () => / ...

  2. WPF之UI虚拟化

    在WPF应用程序开发过程中,大数据量的数据展现通常都要考虑性能问题.有下面一种常见的情况:原始数据源数据量很大,但是某一时刻数据容器中的可见元素个数是有限的,剩余大多数元素都处于不可见状态,如果一次性 ...

  3. WPF Virtualization

    WPF虚拟化技术分为UI 虚拟化和数据虚拟化 第一种方法被称为"UI 虚拟化".支持虚拟化用户界面的控件是足够聪明来创建只显示的是实际在屏幕上可见的数据项目所需的 UI 元素.例如 ...

  4. 【WPF】UI虚拟化之------自定义VirtualizingWrapPanel

    原文:[WPF]UI虚拟化之------自定义VirtualizingWrapPanel 前言 前几天QA报了一个关于OOM的bug,在排查的过程中发现,ListBox控件中被塞入了过多的Item,而 ...

  5. wpf 客户端【JDAgent桌面助手】开发详解(三) 瀑布流效果实现与UI虚拟化优化大数据显示

    目录区域: 业余开发的wpf 客户端终于完工了..晒晒截图 wpf 客户端[JDAgent桌面助手]开发详解-开篇 wpf 客户端[JDAgent桌面助手]详解(一)主窗口 圆形菜单... wpf 客 ...

  6. WPF ListBox/ListView/DataGrid 虚拟化时的滚动方式

    ListBox的滚动方式 分为像素滚动和列表项滚动 通过ListBox的附加属性ScrollViewer.CanContentScroll来设置.因此ListBox的默认模板中,含有ScrollVie ...

  7. WPF:间接支持虚拟化的ListBox

    /// <summary> /// 间接实现了虚拟化的ListBox /// 子项必须实现IVisible接口 /// 你可以在IsVisible发生改变时实现一系列自定义动作 /// 比 ...

  8. WPF 列表虚拟化时的滚动方式

    ListBox的滚动方式 分为像素滚动和列表项滚动 通过ListBox的附加属性ScrollViewer.CanContentScroll来设置.因此ListBox的默认模板中,含有ScrollVie ...

  9. WPF的UI虚拟化

    许多时候,我们的界面上会呈现大量的数据,如包含数千条记录的表格或包含数百张照片的相册.由于呈现UI是一件开销比较大的动作,一次性呈现数百张照片就目前的电脑性能来说是需要占用大量内存和时间的.因此需要对 ...

随机推荐

  1. 卡片抽奖插件 CardShow

    这个小项目(卡片秀)是一个卡片抽奖特效插件,用开源项目这样的词语让我多少有些羞愧,毕竟作为一个涉世未深的小伙子,用项目的标准衡量还有很大差距.不过该案例采用 jQuery 插件方式编写,提供配置参数并 ...

  2. 【开源】.Net 动态脚本引擎NScript

    开源地址: https://git.oschina.net/chejiangyi/NScript 开源QQ群: .net 开源基础服务  238543768 .Net 动态脚本引擎 NScript   ...

  3. 趣说游戏AI开发:曼哈顿街角的A*算法

    0x00 前言 请叫我标题党!请叫我标题党!请叫我标题党!因为下面的文字既不发生在美国曼哈顿,也不是一个讲述美国梦的故事.相反,这可能只是一篇没有那么枯燥的关于算法的文章.A星算法,这个在游戏寻路开发 ...

  4. javascript之闭包理解以及应用场景

    半个月没写博文了,最近一直在弄小程序,感觉也没啥好写的. 之前读了js权威指南,也写了篇博文,但是实话实说当初看闭包确实还是一头雾水.现在时隔一个多月(当然这一段时间还是一直有在看闭包的相关知识)理解 ...

  5. Node.js npm 详解

    一.npm简介 安装npm请阅读我之前的文章Hello Node中npm安装那一部分,不过只介绍了linux平台,如果是其它平台,有前辈写了更加详细的介绍. npm的全称:Node Package M ...

  6. web 前端(轮番插件)

    <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8& ...

  7. 使用Hudson搭建自动构建服务器

    环境: ubuntu1404_x64 说明: 使用hudson和git搭建自动构建服务器的简单示例 安装hudson及相关插件 安装hudson 安装命令如下: sudo sh -c "ec ...

  8. jquery.multiselect 多选下拉框实现

    第一步:链接下列文件,如果没有,到此网页下载 https://github.com/ehynds/jquery-ui-multiselect-widget,此插件基于jquery ,所以jquery的 ...

  9. ucos实时操作系统学习笔记——任务间通信(互斥锁)

    想讲一下ucos任务间通信中的mutex,感觉其设计挺巧妙,同sem一样使用的是event机制实现的,代码不每一行都分析,因为讲的没邵贝贝老师清楚,主要讲一下mutex的内核是如何实现的.可以理解互斥 ...

  10. C++的性能C#的产能?! - .Net Native 系列《三》:.NET Native部署测试方案及样例

    之前一文<c++的性能, c#的产能?!鱼和熊掌可以兼得,.NET NATIVE初窥> 获得很多朋友支持和鼓励,也更让我坚定做这项技术的推广者,希望能让更多的朋友了解这项技术,于是先从官方 ...