protected override void ArrangeChildren() { bool fireScrolled = false; lock (Children.SyncRoot) { if (ItemProvider == null) { base.ArrangeChildren(); return; } _totalHeight = 0; _totalWidth = 0; int numItems = ItemProvider.NumItems; if (numItems > 0) { PointF actualPosition = ActualPosition; SizeF actualSize = new SizeF((float)ActualWidth, (float)ActualHeight); // For Orientation == vertical, this is ActualHeight, for horizontal it is ActualWidth float actualExtendsInOrientationDirection = GetExtendsInOrientationDirection(Orientation, actualSize); // For Orientation == vertical, this is ActualWidth, for horizontal it is ActualHeight float actualExtendsInNonOrientationDirection = GetExtendsInNonOrientationDirection(Orientation, actualSize); // Hint: We cannot skip the arrangement of lines above _actualFirstVisibleLineIndex or below _actualLastVisibleLineIndex // because the rendering and focus system also needs the bounds of the currently invisible children float startPosition = 0; // If set to true, we'll check available space from the last to first visible child. // That is necessary if we want to scroll a specific child to the last visible position. bool invertLayouting = false; lock (_renderLock) if (_pendingScrollIndex.HasValue) { fireScrolled = true; int pendingSI = _pendingScrollIndex.Value; if (_scrollToFirst) { _actualFirstVisibleLineIndex = pendingSI; } else { _actualLastVisibleLineIndex = pendingSI; invertLayouting = true; } _pendingScrollIndex = null; } int itemsPerLine = 0; // if we haven't arranged any lines previously - items per line can't be calculated yet, but is not needed if (_firstArrangedLineIndex >= 0) { // to calculate the starting index of elements for a line we assume that every line (until the last) has the same number of items itemsPerLine = _arrangedLines[_firstArrangedLineIndex].EndIndex - _arrangedLines[_firstArrangedLineIndex].StartIndex + 1; _assumedLineExtendsInNonOrientationDirection = _arrangedLines[_firstArrangedLineIndex].TotalExtendsInNonOrientationDirection; } // scrolling may have set an invalid index for the first visible line if (_actualFirstVisibleLineIndex < 0) { _actualFirstVisibleLineIndex = 0; } if (itemsPerLine > 0) { int linesPerPage = (int)Math.Floor(actualExtendsInNonOrientationDirection / _assumedLineExtendsInNonOrientationDirection); int maxLineIndex = (int)Math.Ceiling((float)numItems / itemsPerLine); if (_actualFirstVisibleLineIndex > maxLineIndex - linesPerPage) { _actualFirstVisibleLineIndex = Math.Max(maxLineIndex - linesPerPage, 0); } } // clear values from previous arrange _arrangedItems = new FrameworkElement[numItems]; _arrangedLines.Clear(); _firstArrangedLineIndex = 0; _lastArrangedLineIndex = 0; // 1) Calculate scroll indices if (_doScroll) { // Calculate last visible child float spaceLeft = actualExtendsInNonOrientationDirection; if (invertLayouting) { if (_actualLastVisibleLineIndex == int.MaxValue) // when scroll to last item (END) was requested { _actualLastVisibleLineIndex = (int)Math.Ceiling((float)numItems / itemsPerLine) - 1; } _actualFirstVisibleLineIndex = _actualLastVisibleLineIndex + 1; int currentLineIndex = _actualLastVisibleLineIndex; _lastArrangedLineIndex = currentLineIndex; while (_arrangedLines.Count <= currentLineIndex) { _arrangedLines.Add(new LineMeasurement()); // add "unarranged lines" up to the last visible } int itemIndex = currentLineIndex * itemsPerLine; int additionalLinesBefore = 0; while (currentLineIndex >= 0 && additionalLinesBefore < NUM_ADD_MORE_FOCUS_LINES) { LineMeasurement line = CalculateLine(itemIndex, _innerRect.Size, false); _arrangedLines[currentLineIndex] = line; _firstArrangedLineIndex = currentLineIndex; currentLineIndex--; itemIndex = line.StartIndex - itemsPerLine; spaceLeft -= line.TotalExtendsInNonOrientationDirection; if (spaceLeft + DELTA_DOUBLE < 0) { additionalLinesBefore++; } else { _actualFirstVisibleLineIndex--; } } // now add NUM_ADD_MORE_FOCUS_LINES after last visible itemIndex = _arrangedLines[_lastArrangedLineIndex].EndIndex + 1; int additionalLinesAfterwards = 0; while (itemIndex < numItems && additionalLinesAfterwards < NUM_ADD_MORE_FOCUS_LINES) { LineMeasurement line = CalculateLine(itemIndex, _innerRect.Size, false); _arrangedLines.Add(line); _lastArrangedLineIndex = _arrangedLines.Count - 1; itemIndex = line.EndIndex + 1; additionalLinesAfterwards++; } } else { _actualLastVisibleLineIndex = _actualFirstVisibleLineIndex - 1; _firstArrangedLineIndex = Math.Max(_actualFirstVisibleLineIndex - NUM_ADD_MORE_FOCUS_LINES, 0); int currentLineIndex = _firstArrangedLineIndex; // add "unarranges lines" up until where we start while (_arrangedLines.Count < currentLineIndex) { _arrangedLines.Add(new LineMeasurement()); } int itemIndex = currentLineIndex * itemsPerLine; int additionalLinesAfterwards = 0; while (itemIndex < numItems && additionalLinesAfterwards < NUM_ADD_MORE_FOCUS_LINES) { LineMeasurement line = CalculateLine(itemIndex, _innerRect.Size, false); _arrangedLines.Add(line); _lastArrangedLineIndex = currentLineIndex; currentLineIndex++; itemIndex = line.EndIndex + 1; if (currentLineIndex > _actualFirstVisibleLineIndex) { spaceLeft -= line.TotalExtendsInNonOrientationDirection; if (spaceLeft + DELTA_DOUBLE < 0) { additionalLinesAfterwards++; } else { _actualLastVisibleLineIndex++; } } } } } else { _actualFirstVisibleLineIndex = 0; _actualLastVisibleLineIndex = _arrangedLines.Count - 1; } // now we know items per line for sure so just calculate it itemsPerLine = _arrangedLines[_firstArrangedLineIndex].EndIndex - _arrangedLines[_firstArrangedLineIndex].StartIndex + 1; _assumedLineExtendsInNonOrientationDirection = _arrangedLines[_firstArrangedLineIndex].TotalExtendsInNonOrientationDirection; // 2) Calculate start position (so the first visible line starts at 0) startPosition -= (_actualFirstVisibleLineIndex - _firstArrangedLineIndex) * _assumedLineExtendsInNonOrientationDirection; // 3) Arrange children if (Orientation == Orientation.Vertical) { _totalHeight = actualExtendsInOrientationDirection; } else { _totalWidth = actualExtendsInOrientationDirection; } PointF position = Orientation == Orientation.Vertical ? new PointF(actualPosition.X + startPosition, actualPosition.Y) : new PointF(actualPosition.X, actualPosition.Y + startPosition); foreach (LineMeasurement line in _arrangedLines.Skip(_firstArrangedLineIndex).Take(_lastArrangedLineIndex - _firstArrangedLineIndex + 1)) { LayoutLine(position, line); startPosition += line.TotalExtendsInNonOrientationDirection; if (Orientation == Orientation.Vertical) { position = new PointF(actualPosition.X + startPosition, actualPosition.Y); } else { position = new PointF(actualPosition.X, actualPosition.Y + startPosition); } } // estimate the desired size var estimatedExtendsInNonOrientationDirection = (float)Math.Ceiling((float)numItems / itemsPerLine) * _assumedLineExtendsInNonOrientationDirection; if (Orientation == Orientation.Horizontal) { _totalHeight = estimatedExtendsInNonOrientationDirection; } else { _totalWidth = estimatedExtendsInNonOrientationDirection; } // keep one more item, because we did use it in CalcLine (and need always one more to find the last item not fitting on the line) // -> if we dont, it will always be newlyCreated and we keep calling Arrange since the new item recursively sets the parent invalid _itemProvider.Keep(_arrangedLines[_firstArrangedLineIndex].StartIndex, _arrangedLines[_lastArrangedLineIndex].EndIndex + 1); } else { _actualFirstVisibleLineIndex = 0; _actualLastVisibleLineIndex = -1; } } if (fireScrolled) { InvokeScrolled(); } }