VirtualizingWrapPanel.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735
  1. using System;
  2. using System.Collections.Specialized;
  3. using System.ComponentModel;
  4. using System.Windows;
  5. using System.Windows.Controls;
  6. using System.Windows.Controls.Primitives;
  7. using System.Windows.Media;
  8. namespace MediaBrowser.UI.Controls
  9. {
  10. /// <summary>
  11. /// http://www.codeproject.com/Articles/75847/Virtualizing-WrapPanel
  12. /// Positions child elements in sequential position from left to right, breaking content
  13. /// to the next line at the edge of the containing box. Subsequent ordering happens
  14. /// sequentially from top to bottom or from right to left, depending on the value of
  15. /// the Orientation property.
  16. /// </summary>
  17. [DefaultProperty("Orientation")]
  18. public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
  19. {
  20. /// <summary>
  21. /// Identifies the ItemHeight dependency property.
  22. /// </summary>
  23. public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(100.0, new PropertyChangedCallback(VirtualizingWrapPanel.OnAppearancePropertyChanged)));
  24. /// <summary>
  25. /// Identifies the Orientation dependency property.
  26. /// </summary>
  27. public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(VirtualizingWrapPanel), new PropertyMetadata(Orientation.Horizontal, new PropertyChangedCallback(VirtualizingWrapPanel.OnAppearancePropertyChanged)));
  28. /// <summary>
  29. /// Identifies the ItemWidth dependency property.
  30. /// </summary>
  31. public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(100.0, new PropertyChangedCallback(VirtualizingWrapPanel.OnAppearancePropertyChanged)));
  32. /// <summary>
  33. /// Identifies the ScrollStep dependency property.
  34. /// </summary>
  35. public static readonly DependencyProperty ScrollStepProperty = DependencyProperty.Register("ScrollStep", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(10.0, new PropertyChangedCallback(VirtualizingWrapPanel.OnAppearancePropertyChanged)));
  36. private bool canHorizontallyScroll;
  37. private bool canVerticallyScroll;
  38. private Size contentExtent = new Size(0.0, 0.0);
  39. private Point contentOffset = default(Point);
  40. private ScrollViewer scrollOwner;
  41. private Size viewport = new Size(0.0, 0.0);
  42. private int previousItemCount;
  43. /// <summary>
  44. /// Gets or sets a value that specifies the height of all items that are
  45. /// contained within a VirtualizingWrapPanel. This is a dependency property.
  46. /// </summary>
  47. public double ItemHeight
  48. {
  49. get
  50. {
  51. return (double)base.GetValue(VirtualizingWrapPanel.ItemHeightProperty);
  52. }
  53. set
  54. {
  55. base.SetValue(VirtualizingWrapPanel.ItemHeightProperty, value);
  56. }
  57. }
  58. /// <summary>
  59. /// Gets or sets a value that specifies the width of all items that are
  60. /// contained within a VirtualizingWrapPanel. This is a dependency property.
  61. /// </summary>
  62. public double ItemWidth
  63. {
  64. get
  65. {
  66. return (double)base.GetValue(VirtualizingWrapPanel.ItemWidthProperty);
  67. }
  68. set
  69. {
  70. base.SetValue(VirtualizingWrapPanel.ItemWidthProperty, value);
  71. }
  72. }
  73. /// <summary>
  74. /// Gets or sets a value that specifies the dimension in which child
  75. /// content is arranged. This is a dependency property.
  76. /// </summary>
  77. public Orientation Orientation
  78. {
  79. get
  80. {
  81. return (Orientation)base.GetValue(VirtualizingWrapPanel.OrientationProperty);
  82. }
  83. set
  84. {
  85. base.SetValue(VirtualizingWrapPanel.OrientationProperty, value);
  86. }
  87. }
  88. /// <summary>
  89. /// Gets or sets a value that indicates whether scrolling on the horizontal axis is possible.
  90. /// </summary>
  91. public bool CanHorizontallyScroll
  92. {
  93. get
  94. {
  95. return this.canHorizontallyScroll;
  96. }
  97. set
  98. {
  99. if (this.canHorizontallyScroll != value)
  100. {
  101. this.canHorizontallyScroll = value;
  102. base.InvalidateMeasure();
  103. }
  104. }
  105. }
  106. /// <summary>
  107. /// Gets or sets a value that indicates whether scrolling on the vertical axis is possible.
  108. /// </summary>
  109. public bool CanVerticallyScroll
  110. {
  111. get
  112. {
  113. return this.canVerticallyScroll;
  114. }
  115. set
  116. {
  117. if (this.canVerticallyScroll != value)
  118. {
  119. this.canVerticallyScroll = value;
  120. base.InvalidateMeasure();
  121. }
  122. }
  123. }
  124. /// <summary>
  125. /// Gets or sets a ScrollViewer element that controls scrolling behavior.
  126. /// </summary>
  127. public ScrollViewer ScrollOwner
  128. {
  129. get
  130. {
  131. return this.scrollOwner;
  132. }
  133. set
  134. {
  135. this.scrollOwner = value;
  136. }
  137. }
  138. /// <summary>
  139. /// Gets the vertical offset of the scrolled content.
  140. /// </summary>
  141. public double VerticalOffset
  142. {
  143. get
  144. {
  145. return this.contentOffset.Y;
  146. }
  147. }
  148. /// <summary>
  149. /// Gets the vertical size of the viewport for this content.
  150. /// </summary>
  151. public double ViewportHeight
  152. {
  153. get
  154. {
  155. return this.viewport.Height;
  156. }
  157. }
  158. /// <summary>
  159. /// Gets the horizontal size of the viewport for this content.
  160. /// </summary>
  161. public double ViewportWidth
  162. {
  163. get
  164. {
  165. return this.viewport.Width;
  166. }
  167. }
  168. /// <summary>
  169. /// Gets or sets a value for mouse wheel scroll step.
  170. /// </summary>
  171. public double ScrollStep
  172. {
  173. get
  174. {
  175. return (double)base.GetValue(VirtualizingWrapPanel.ScrollStepProperty);
  176. }
  177. set
  178. {
  179. base.SetValue(VirtualizingWrapPanel.ScrollStepProperty, value);
  180. }
  181. }
  182. /// <summary>
  183. /// Gets the vertical size of the extent.
  184. /// </summary>
  185. public double ExtentHeight
  186. {
  187. get
  188. {
  189. return this.contentExtent.Height;
  190. }
  191. }
  192. /// <summary>
  193. /// Gets the horizontal size of the extent.
  194. /// </summary>
  195. public double ExtentWidth
  196. {
  197. get
  198. {
  199. return this.contentExtent.Width;
  200. }
  201. }
  202. /// <summary>
  203. /// Gets the horizontal offset of the scrolled content.
  204. /// </summary>
  205. public double HorizontalOffset
  206. {
  207. get
  208. {
  209. return this.contentOffset.X;
  210. }
  211. }
  212. /// <summary>
  213. /// Scrolls down within content by one logical unit.
  214. /// </summary>
  215. public void LineDown()
  216. {
  217. this.SetVerticalOffset(this.VerticalOffset + this.ScrollStep);
  218. }
  219. /// <summary>
  220. /// Scrolls left within content by one logical unit.
  221. /// </summary>
  222. public void LineLeft()
  223. {
  224. this.SetHorizontalOffset(this.HorizontalOffset - this.ScrollStep);
  225. }
  226. /// <summary>
  227. /// Scrolls right within content by one logical unit.
  228. /// </summary>
  229. public void LineRight()
  230. {
  231. this.SetHorizontalOffset(this.HorizontalOffset + this.ScrollStep);
  232. }
  233. /// <summary>
  234. /// Scrolls up within content by one logical unit.
  235. /// </summary>
  236. public void LineUp()
  237. {
  238. this.SetVerticalOffset(this.VerticalOffset - this.ScrollStep);
  239. }
  240. /// <summary>
  241. /// Forces content to scroll until the coordinate space of a Visual object is visible.
  242. /// </summary>
  243. public Rect MakeVisible(Visual visual, Rect rectangle)
  244. {
  245. this.MakeVisible(visual as UIElement);
  246. return rectangle;
  247. }
  248. /// <summary>
  249. /// Scrolls down within content after a user clicks the wheel button on a mouse.
  250. /// </summary>
  251. public void MouseWheelDown()
  252. {
  253. this.SetVerticalOffset(this.VerticalOffset + this.ScrollStep);
  254. }
  255. /// <summary>
  256. /// Scrolls left within content after a user clicks the wheel button on a mouse.
  257. /// </summary>
  258. public void MouseWheelLeft()
  259. {
  260. this.SetHorizontalOffset(this.HorizontalOffset - this.ScrollStep);
  261. }
  262. /// <summary>
  263. /// Scrolls right within content after a user clicks the wheel button on a mouse.
  264. /// </summary>
  265. public void MouseWheelRight()
  266. {
  267. this.SetHorizontalOffset(this.HorizontalOffset + this.ScrollStep);
  268. }
  269. /// <summary>
  270. /// Scrolls up within content after a user clicks the wheel button on a mouse.
  271. /// </summary>
  272. public void MouseWheelUp()
  273. {
  274. this.SetVerticalOffset(this.VerticalOffset - this.ScrollStep);
  275. }
  276. /// <summary>
  277. /// Scrolls down within content by one page.
  278. /// </summary>
  279. public void PageDown()
  280. {
  281. this.SetVerticalOffset(this.VerticalOffset + this.ViewportHeight);
  282. }
  283. /// <summary>
  284. /// Scrolls left within content by one page.
  285. /// </summary>
  286. public void PageLeft()
  287. {
  288. this.SetHorizontalOffset(this.HorizontalOffset - this.ViewportHeight);
  289. }
  290. /// <summary>
  291. /// Scrolls right within content by one page.
  292. /// </summary>
  293. public void PageRight()
  294. {
  295. this.SetHorizontalOffset(this.HorizontalOffset + this.ViewportHeight);
  296. }
  297. /// <summary>
  298. /// Scrolls up within content by one page.
  299. /// </summary>
  300. public void PageUp()
  301. {
  302. this.SetVerticalOffset(this.VerticalOffset - this.viewport.Height);
  303. }
  304. /// <summary>
  305. /// Sets the amount of vertical offset.
  306. /// </summary>
  307. public void SetVerticalOffset(double offset)
  308. {
  309. if (offset < 0.0 || this.ViewportHeight >= this.ExtentHeight)
  310. {
  311. offset = 0.0;
  312. }
  313. else
  314. {
  315. if (offset + this.ViewportHeight >= this.ExtentHeight)
  316. {
  317. offset = this.ExtentHeight - this.ViewportHeight;
  318. }
  319. }
  320. this.contentOffset.Y = offset;
  321. if (this.ScrollOwner != null)
  322. {
  323. this.ScrollOwner.InvalidateScrollInfo();
  324. }
  325. base.InvalidateMeasure();
  326. }
  327. /// <summary>
  328. /// Sets the amount of horizontal offset.
  329. /// </summary>
  330. public void SetHorizontalOffset(double offset)
  331. {
  332. if (offset < 0.0 || this.ViewportWidth >= this.ExtentWidth)
  333. {
  334. offset = 0.0;
  335. }
  336. else
  337. {
  338. if (offset + this.ViewportWidth >= this.ExtentWidth)
  339. {
  340. offset = this.ExtentWidth - this.ViewportWidth;
  341. }
  342. }
  343. this.contentOffset.X = offset;
  344. if (this.ScrollOwner != null)
  345. {
  346. this.ScrollOwner.InvalidateScrollInfo();
  347. }
  348. base.InvalidateMeasure();
  349. }
  350. /// <summary>
  351. /// Note: Works only for vertical.
  352. /// </summary>
  353. internal void PageLast()
  354. {
  355. this.contentOffset.Y = this.ExtentHeight;
  356. if (this.ScrollOwner != null)
  357. {
  358. this.ScrollOwner.InvalidateScrollInfo();
  359. }
  360. base.InvalidateMeasure();
  361. }
  362. /// <summary>
  363. /// Note: Works only for vertical.
  364. /// </summary>
  365. internal void PageFirst()
  366. {
  367. this.contentOffset.Y = 0.0;
  368. if (this.ScrollOwner != null)
  369. {
  370. this.ScrollOwner.InvalidateScrollInfo();
  371. }
  372. base.InvalidateMeasure();
  373. }
  374. /// <summary>
  375. /// When items are removed, remove the corresponding UI if necessary.
  376. /// </summary>
  377. /// <param name="sender"></param>
  378. /// <param name="args"></param>
  379. protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
  380. {
  381. switch (args.Action)
  382. {
  383. case NotifyCollectionChangedAction.Remove:
  384. case NotifyCollectionChangedAction.Replace:
  385. case NotifyCollectionChangedAction.Move:
  386. base.RemoveInternalChildRange(args.Position.Index, args.ItemUICount);
  387. return;
  388. case NotifyCollectionChangedAction.Reset:
  389. {
  390. ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
  391. if (itemsControl != null)
  392. {
  393. if (this.previousItemCount != itemsControl.Items.Count)
  394. {
  395. if (this.Orientation == Orientation.Horizontal)
  396. {
  397. this.SetVerticalOffset(0.0);
  398. }
  399. else
  400. {
  401. this.SetHorizontalOffset(0.0);
  402. }
  403. }
  404. this.previousItemCount = itemsControl.Items.Count;
  405. }
  406. return;
  407. }
  408. default:
  409. return;
  410. }
  411. }
  412. /// <summary>
  413. /// Measure the children.
  414. /// </summary>
  415. /// <param name="availableSize">The available size.</param>
  416. /// <returns>The desired size.</returns>
  417. protected override Size MeasureOverride(Size availableSize)
  418. {
  419. this.InvalidateScrollInfo(availableSize);
  420. int firstVisibleIndex;
  421. int lastVisibleIndex;
  422. if (this.Orientation == Orientation.Horizontal)
  423. {
  424. this.GetVerticalVisibleRange(out firstVisibleIndex, out lastVisibleIndex);
  425. }
  426. else
  427. {
  428. this.GetHorizontalVisibleRange(out firstVisibleIndex, out lastVisibleIndex);
  429. }
  430. UIElementCollection children = base.Children;
  431. IItemContainerGenerator generator = base.ItemContainerGenerator;
  432. if (generator != null)
  433. {
  434. GeneratorPosition startPos = generator.GeneratorPositionFromIndex(firstVisibleIndex);
  435. int childIndex = (startPos.Offset == 0) ? startPos.Index : (startPos.Index + 1);
  436. if (childIndex == -1)
  437. {
  438. this.RefreshOffset();
  439. }
  440. using (generator.StartAt(startPos, GeneratorDirection.Forward, true))
  441. {
  442. int itemIndex = firstVisibleIndex;
  443. while (itemIndex <= lastVisibleIndex)
  444. {
  445. bool newlyRealized;
  446. UIElement child = generator.GenerateNext(out newlyRealized) as UIElement;
  447. if (newlyRealized)
  448. {
  449. if (childIndex >= children.Count)
  450. {
  451. base.AddInternalChild(child);
  452. }
  453. else
  454. {
  455. base.InsertInternalChild(childIndex, child);
  456. }
  457. generator.PrepareItemContainer(child);
  458. }
  459. if (child != null)
  460. {
  461. child.Measure(new Size(this.ItemWidth, this.ItemHeight));
  462. }
  463. itemIndex++;
  464. childIndex++;
  465. }
  466. }
  467. this.CleanUpChildren(firstVisibleIndex, lastVisibleIndex);
  468. }
  469. if (IsCloseTo(availableSize.Height, double.PositiveInfinity) || IsCloseTo(availableSize.Width, double.PositiveInfinity))
  470. {
  471. return base.MeasureOverride(availableSize);
  472. }
  473. var itemsControl = ItemsControl.GetItemsOwner(this);
  474. var numItems = itemsControl.Items.Count;
  475. var width = availableSize.Width;
  476. var height = availableSize.Height;
  477. if (Orientation == Orientation.Vertical)
  478. {
  479. var numRows = Math.Floor(availableSize.Height / ItemHeight);
  480. height = numRows * ItemHeight;
  481. var requiredColumns = Math.Ceiling(numItems / numRows);
  482. width = Math.Min(requiredColumns * ItemWidth, width);
  483. }
  484. else
  485. {
  486. var numColumns = Math.Floor(availableSize.Width / ItemWidth);
  487. width = numColumns * ItemWidth;
  488. //if (numItems > 0 && numItems < numColumns)
  489. //{
  490. // width = Math.Min(numColumns, numItems) * ItemWidth;
  491. //}
  492. var requiredRows = Math.Ceiling(numItems / numColumns);
  493. height = Math.Min(requiredRows * ItemHeight, height);
  494. }
  495. return new Size(width, height);
  496. }
  497. /// <summary>
  498. /// Arranges the children.
  499. /// </summary>
  500. /// <param name="finalSize">The available size.</param>
  501. /// <returns>The used size.</returns>
  502. protected override Size ArrangeOverride(Size finalSize)
  503. {
  504. bool isHorizontal = this.Orientation == Orientation.Horizontal;
  505. this.InvalidateScrollInfo(finalSize);
  506. int i = 0;
  507. foreach (object item in base.Children)
  508. {
  509. this.ArrangeChild(isHorizontal, finalSize, i++, item as UIElement);
  510. }
  511. return finalSize;
  512. }
  513. private static void OnAppearancePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  514. {
  515. UIElement panel = d as UIElement;
  516. if (panel != null)
  517. {
  518. panel.InvalidateMeasure();
  519. }
  520. }
  521. private void MakeVisible(UIElement element)
  522. {
  523. ItemContainerGenerator generator = base.ItemContainerGenerator.GetItemContainerGeneratorForPanel(this);
  524. if (element != null && generator != null)
  525. {
  526. for (int itemIndex = generator.IndexFromContainer(element); itemIndex == -1; itemIndex = generator.IndexFromContainer(element))
  527. {
  528. element = element.ParentOfType<UIElement>();
  529. }
  530. ScrollViewer scrollViewer = element.ParentOfType<ScrollViewer>();
  531. if (scrollViewer != null)
  532. {
  533. GeneralTransform elementTransform = element.TransformToVisual(scrollViewer);
  534. Rect elementRectangle = elementTransform.TransformBounds(new Rect(new Point(0.0, 0.0), element.RenderSize));
  535. if (this.Orientation == Orientation.Horizontal)
  536. {
  537. var padding = ItemHeight / 3;
  538. if (elementRectangle.Bottom > this.ViewportHeight)
  539. {
  540. this.SetVerticalOffset(this.contentOffset.Y + elementRectangle.Bottom - this.ViewportHeight + padding);
  541. return;
  542. }
  543. if (elementRectangle.Top < 0.0)
  544. {
  545. this.SetVerticalOffset(this.contentOffset.Y + elementRectangle.Top - padding);
  546. return;
  547. }
  548. }
  549. else
  550. {
  551. var padding = ItemWidth / 3;
  552. if (elementRectangle.Right > this.ViewportWidth)
  553. {
  554. this.SetHorizontalOffset(this.contentOffset.X + elementRectangle.Right - this.ViewportWidth + padding);
  555. return;
  556. }
  557. if (elementRectangle.Left < 0.0)
  558. {
  559. this.SetHorizontalOffset(this.contentOffset.X + elementRectangle.Left - padding);
  560. }
  561. }
  562. }
  563. }
  564. }
  565. private void GetVerticalVisibleRange(out int firstVisibleItemIndex, out int lastVisibleItemIndex)
  566. {
  567. int childrenPerRow = this.GetVerticalChildrenCountPerRow(this.contentExtent);
  568. firstVisibleItemIndex = (int)Math.Floor(this.VerticalOffset / this.ItemHeight) * childrenPerRow;
  569. lastVisibleItemIndex = (int)Math.Ceiling((this.VerticalOffset + this.ViewportHeight) / this.ItemHeight) * childrenPerRow - 1;
  570. this.AdjustVisibleRange(ref firstVisibleItemIndex, ref lastVisibleItemIndex);
  571. }
  572. private void GetHorizontalVisibleRange(out int firstVisibleItemIndex, out int lastVisibleItemIndex)
  573. {
  574. int childrenPerRow = this.GetHorizontalChildrenCountPerRow(this.contentExtent);
  575. firstVisibleItemIndex = (int)Math.Floor(this.HorizontalOffset / this.ItemWidth) * childrenPerRow;
  576. lastVisibleItemIndex = (int)Math.Ceiling((this.HorizontalOffset + this.ViewportWidth) / this.ItemWidth) * childrenPerRow - 1;
  577. this.AdjustVisibleRange(ref firstVisibleItemIndex, ref lastVisibleItemIndex);
  578. }
  579. private void AdjustVisibleRange(ref int firstVisibleItemIndex, ref int lastVisibleItemIndex)
  580. {
  581. firstVisibleItemIndex--;
  582. lastVisibleItemIndex++;
  583. ItemsControl itemsControl = ItemsControl.GetItemsOwner(this);
  584. if (itemsControl != null)
  585. {
  586. if (firstVisibleItemIndex < 0)
  587. {
  588. firstVisibleItemIndex = 0;
  589. }
  590. if (lastVisibleItemIndex >= itemsControl.Items.Count)
  591. {
  592. lastVisibleItemIndex = itemsControl.Items.Count - 1;
  593. }
  594. }
  595. }
  596. private void CleanUpChildren(int minIndex, int maxIndex)
  597. {
  598. UIElementCollection children = base.Children;
  599. IItemContainerGenerator generator = base.ItemContainerGenerator;
  600. for (int i = children.Count - 1; i >= 0; i--)
  601. {
  602. GeneratorPosition pos = new GeneratorPosition(i, 0);
  603. int itemIndex = generator.IndexFromGeneratorPosition(pos);
  604. if (itemIndex < minIndex || itemIndex > maxIndex)
  605. {
  606. generator.Remove(pos, 1);
  607. base.RemoveInternalChildRange(i, 1);
  608. }
  609. }
  610. }
  611. private void ArrangeChild(bool isHorizontal, Size finalSize, int index, UIElement child)
  612. {
  613. if (child == null)
  614. {
  615. return;
  616. }
  617. int count = isHorizontal ? this.GetVerticalChildrenCountPerRow(finalSize) : this.GetHorizontalChildrenCountPerRow(finalSize);
  618. int itemIndex = base.ItemContainerGenerator.IndexFromGeneratorPosition(new GeneratorPosition(index, 0));
  619. int row = isHorizontal ? (itemIndex / count) : (itemIndex % count);
  620. int column = isHorizontal ? (itemIndex % count) : (itemIndex / count);
  621. Rect rect = new Rect((double)column * this.ItemWidth, (double)row * this.ItemHeight, this.ItemWidth, this.ItemHeight);
  622. if (isHorizontal)
  623. {
  624. rect.Y -= this.VerticalOffset;
  625. }
  626. else
  627. {
  628. rect.X -= this.HorizontalOffset;
  629. }
  630. child.Arrange(rect);
  631. }
  632. private void InvalidateScrollInfo(Size availableSize)
  633. {
  634. ItemsControl ownerItemsControl = ItemsControl.GetItemsOwner(this);
  635. if (ownerItemsControl != null)
  636. {
  637. Size extent = this.GetExtent(availableSize, ownerItemsControl.Items.Count);
  638. if (extent != this.contentExtent)
  639. {
  640. this.contentExtent = extent;
  641. this.RefreshOffset();
  642. }
  643. if (availableSize != this.viewport)
  644. {
  645. this.viewport = availableSize;
  646. this.InvalidateScrollOwner();
  647. }
  648. }
  649. }
  650. private void RefreshOffset()
  651. {
  652. if (this.Orientation == Orientation.Horizontal)
  653. {
  654. this.SetVerticalOffset(this.VerticalOffset);
  655. return;
  656. }
  657. this.SetHorizontalOffset(this.HorizontalOffset);
  658. }
  659. private void InvalidateScrollOwner()
  660. {
  661. if (this.ScrollOwner != null)
  662. {
  663. this.ScrollOwner.InvalidateScrollInfo();
  664. }
  665. }
  666. private Size GetExtent(Size availableSize, int itemCount)
  667. {
  668. if (this.Orientation == Orientation.Horizontal)
  669. {
  670. int childrenPerRow = this.GetVerticalChildrenCountPerRow(availableSize);
  671. return new Size((double)childrenPerRow * this.ItemWidth, this.ItemHeight * Math.Ceiling((double)itemCount / (double)childrenPerRow));
  672. }
  673. int childrenPerRow2 = this.GetHorizontalChildrenCountPerRow(availableSize);
  674. return new Size(this.ItemWidth * Math.Ceiling((double)itemCount / (double)childrenPerRow2), (double)childrenPerRow2 * this.ItemHeight);
  675. }
  676. private int GetVerticalChildrenCountPerRow(Size availableSize)
  677. {
  678. int childrenCountPerRow;
  679. if (availableSize.Width == double.PositiveInfinity)
  680. {
  681. childrenCountPerRow = base.Children.Count;
  682. }
  683. else
  684. {
  685. childrenCountPerRow = Math.Max(1, (int)Math.Floor(availableSize.Width / this.ItemWidth));
  686. }
  687. return childrenCountPerRow;
  688. }
  689. private int GetHorizontalChildrenCountPerRow(Size availableSize)
  690. {
  691. int childrenCountPerRow;
  692. if (availableSize.Height == double.PositiveInfinity)
  693. {
  694. childrenCountPerRow = base.Children.Count;
  695. }
  696. else
  697. {
  698. childrenCountPerRow = Math.Max(1, (int)Math.Floor(availableSize.Height / this.ItemHeight));
  699. }
  700. return childrenCountPerRow;
  701. }
  702. private static bool IsCloseTo(double value1, double value2)
  703. {
  704. return AreClose(value1, value2);
  705. }
  706. private static bool AreClose(double value1, double value2)
  707. {
  708. if (value1 == value2)
  709. {
  710. return true;
  711. }
  712. double num = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * 2.2204460492503131E-16;
  713. double num2 = value1 - value2;
  714. return -num < num2 && num > num2;
  715. }
  716. }
  717. }