/// <summary> /// A copy of the <see cref="WrapPanel.LayoutLine"/> method, but using the <see cref="ItemProvider"/> /// via the <see cref="GetItem"/> method for item retrieval. /// </summary> protected void LayoutLine(PointF pos, LineMeasurement line) { float offset = 0; for (int i = line.StartIndex; i <= line.EndIndex; i++) { FrameworkElement layoutChild = GetItem(i, ItemProvider, true); SizeF desiredChildSize = layoutChild.DesiredSize; SizeF size; PointF location; if (Orientation == Orientation.Horizontal) { size = new SizeF(desiredChildSize.Width, line.TotalExtendsInNonOrientationDirection); location = new PointF(pos.X + offset, pos.Y); ArrangeChildVertical(layoutChild, layoutChild.VerticalAlignment, ref location, ref size); offset += desiredChildSize.Width; } else { size = new SizeF(line.TotalExtendsInNonOrientationDirection, desiredChildSize.Height); location = new PointF(pos.X, pos.Y + offset); ArrangeChildHorizontal(layoutChild, layoutChild.HorizontalAlignment, ref location, ref size); offset += desiredChildSize.Height; } layoutChild.Arrange(new RectangleF(location.X, location.Y, size.Width, size.Height)); _arrangedItems[i] = layoutChild; } }
protected void LayoutLine(IList <FrameworkElement> children, PointF pos, LineMeasurement line) { float offset = 0; for (int i = line.StartIndex; i <= line.EndIndex; i++) { FrameworkElement layoutChild = children[i]; SizeF desiredChildSize = layoutChild.DesiredSize; SizeF size; PointF location; if (Orientation == Orientation.Horizontal) { size = new SizeF(desiredChildSize.Width, line.TotalExtendsInNonOrientationDirection); location = new PointF(pos.X + offset, pos.Y); ArrangeChildVertical(layoutChild, layoutChild.VerticalAlignment, ref location, ref size); offset += desiredChildSize.Width; } else { size = new SizeF(line.TotalExtendsInNonOrientationDirection, desiredChildSize.Height); location = new PointF(pos.X, pos.Y + offset); ArrangeChildHorizontal(layoutChild, layoutChild.HorizontalAlignment, ref location, ref size); offset += desiredChildSize.Height; } layoutChild.Arrange(SharpDXExtensions.CreateRectangleF(location, size)); } }
public virtual bool FocusPageRight() { if (Orientation == Orientation.Vertical) { FrameworkElement currentElement = GetFocusedElementOrChild(); if (currentElement == null) { return(false); } IList <FrameworkElement> visibleChildren = GetVisibleChildren(); if (visibleChildren.Count == 0) { return(false); } IList <LineMeasurement> lines = new List <LineMeasurement>(_arrangedLines); if (lines.Count == 0) { return(false); } int lastVisibleLineIndex = _actualLastVisibleLineIndex; CalcHelper.Bound(ref lastVisibleLineIndex, 0, lines.Count - 1); LineMeasurement lastVisibleLine = lines[lastVisibleLineIndex]; float limitPosition = ActualPosition.X + (float)ActualWidth; // Initialize as if an element inside our visible range is focused - then, we must move to the first element for (int childIndex = lastVisibleLine.StartIndex; childIndex <= lastVisibleLine.EndIndex; childIndex++) { FrameworkElement child = visibleChildren[childIndex]; if (child == null) { return(false); } if (!InVisualPath(child, currentElement)) { continue; } // One of the elements at the bottom is focused - move one page down limitPosition = child.ActualPosition.X + (float)ActualWidth; break; } int lastMinusOne = lastVisibleLineIndex - 1; CalcHelper.Bound(ref lastMinusOne, 0, lines.Count - 1); FrameworkElement nextElement; while ((nextElement = FindNextFocusElement(lines.Skip(lastMinusOne).SelectMany( line => visibleChildren.Skip(line.StartIndex).Take(line.EndIndex - line.StartIndex + 1)), currentElement.ActualBounds, MoveFocusDirection.Right)) != null && (nextElement.ActualBounds.Right < limitPosition - DELTA_DOUBLE)) { currentElement = nextElement; } return(currentElement.TrySetFocus(true)); } return(false); }
protected override SizeF CalculateInnerDesiredSize(SizeF totalSize) { FrameworkElementCollection children = Children; lock (Children.SyncRoot) { if (_newItemProvider != null) { if (children.Count > 0) { children.Clear(false); } if (_itemProvider != null) { MPF.TryCleanupAndDispose(_itemProvider); } _itemProvider = _newItemProvider; _newItemProvider = null; _updateRenderOrder = true; } _assumedLineExtendsInNonOrientationDirection = 0; IItemProvider itemProvider = ItemProvider; if (itemProvider == null) { return(base.CalculateInnerDesiredSize(totalSize)); } int numItems = itemProvider.NumItems; if (numItems == 0) { return(new SizeF()); } // CalculateInnerDesiredSize is called before ArrangeChildren! // under the precondition that all items use the same template and are equally sized // calulate just one line to find number of items and required size of a line LineMeasurement exemplaryLine = _firstArrangedLineIndex < 0 ? CalculateLine(0, totalSize, false) : _arrangedLines[_firstArrangedLineIndex]; _assumedLineExtendsInNonOrientationDirection = exemplaryLine.TotalExtendsInNonOrientationDirection; var itemsPerLine = exemplaryLine.EndIndex - exemplaryLine.StartIndex + 1; var estimatedExtendsInNonOrientationDirection = (float)Math.Ceiling((float)numItems / itemsPerLine) * _assumedLineExtendsInNonOrientationDirection; return(Orientation == Orientation.Horizontal ? new SizeF(exemplaryLine.TotalExtendsInOrientationDirection, estimatedExtendsInNonOrientationDirection) : new SizeF(estimatedExtendsInNonOrientationDirection, exemplaryLine.TotalExtendsInOrientationDirection)); } }
protected override SizeF CalculateInnerDesiredSize(SizeF totalSize) { IList <FrameworkElement> visibleChildren = GetVisibleChildren(); int numVisibleChildren = visibleChildren.Count; if (numVisibleChildren == 0) { return(new SizeF()); } float totalDesiredWidth = 0; float totalDesiredHeight = 0; int index = 0; while (index < numVisibleChildren) { LineMeasurement line = CalculateLine(visibleChildren, index, totalSize, false); if (line.EndIndex < line.StartIndex) { // Element doesn't fit break; } switch (Orientation) { case Orientation.Horizontal: if (line.TotalExtendsInOrientationDirection > totalDesiredWidth) { totalDesiredWidth = line.TotalExtendsInOrientationDirection; } totalDesiredHeight += line.TotalExtendsInNonOrientationDirection; break; case Orientation.Vertical: if (line.TotalExtendsInOrientationDirection > totalDesiredHeight) { totalDesiredHeight = line.TotalExtendsInOrientationDirection; } totalDesiredWidth += line.TotalExtendsInNonOrientationDirection; break; } index = line.EndIndex + 1; } return(new SizeF(totalDesiredWidth, totalDesiredHeight)); }
protected void AddFocusedElementRange(IList <FrameworkElement> availableElements, RectangleF?startingRect, int firstLineIndex, int lastLineIndex, int linesBefore, int linesAfter, ICollection <FrameworkElement> outElements) { IList <LineMeasurement> lines = new List <LineMeasurement>(_arrangedLines); int numLines = lines.Count; if (numLines == 0) { return; } CalcHelper.Bound(ref firstLineIndex, 0, numLines - 1); CalcHelper.Bound(ref lastLineIndex, 0, numLines - 1); if (linesBefore > 0) { // Find children before the first index which have focusable elements. int formerNumElements = outElements.Count; for (int lineIndex = firstLineIndex - 1; lineIndex >= 0; lineIndex--) { LineMeasurement line = lines[lineIndex]; for (int childIndex = line.StartIndex; childIndex <= line.EndIndex; childIndex++) { FrameworkElement fe = availableElements[childIndex]; fe.AddPotentialFocusableElements(startingRect, outElements); } if (formerNumElements == outElements.Count) { continue; } // Found focusable elements linesBefore--; if (linesBefore == 0) { break; } formerNumElements = outElements.Count; } } for (int lineIndex = firstLineIndex; lineIndex <= lastLineIndex; lineIndex++) { LineMeasurement line = lines[lineIndex]; for (int childIndex = line.StartIndex; childIndex <= line.EndIndex; childIndex++) { FrameworkElement fe = availableElements[childIndex]; fe.AddPotentialFocusableElements(startingRect, outElements); } } if (linesAfter > 0) { // Find children after the last index which have focusable elements. int formerNumElements = outElements.Count; for (int lineIndex = lastLineIndex + 1; lineIndex < lines.Count; lineIndex++) { LineMeasurement line = lines[lineIndex]; for (int childIndex = line.StartIndex; childIndex <= line.EndIndex; childIndex++) { FrameworkElement fe = availableElements[childIndex]; fe.AddPotentialFocusableElements(startingRect, outElements); } if (formerNumElements == outElements.Count) { continue; } // Found focusable elements linesAfter--; if (linesAfter == 0) { break; } formerNumElements = outElements.Count; } } }
protected virtual void ArrangeChildren() { bool fireScrolled = false; _totalHeight = 0; _totalWidth = 0; IList <FrameworkElement> visibleChildren = GetVisibleChildren(); int numVisibleChildren = visibleChildren.Count; if (numVisibleChildren > 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; } _arrangedLines = new List <LineMeasurement>(); int index = 0; while (index < numVisibleChildren) { LineMeasurement line = CalculateLine(visibleChildren, index, _innerRect.Size, false); _arrangedLines.Add(line); index = line.EndIndex + 1; } // 1) Calculate scroll indices if (_doScroll) { // Calculate last visible child float spaceLeft = actualExtendsInNonOrientationDirection; if (invertLayouting) { CalcHelper.Bound(ref _actualLastVisibleLineIndex, 0, _arrangedLines.Count - 1); _actualFirstVisibleLineIndex = _actualLastVisibleLineIndex + 1; while (_actualFirstVisibleLineIndex > 0) { LineMeasurement line = _arrangedLines[_actualFirstVisibleLineIndex - 1]; spaceLeft -= line.TotalExtendsInNonOrientationDirection; if (spaceLeft + DELTA_DOUBLE < 0) { break; } _actualFirstVisibleLineIndex--; } if (spaceLeft > 0) { // Correct the last scroll index to fill the available space int maxArrangedLine = _arrangedLines.Count - 1; while (_actualLastVisibleLineIndex < maxArrangedLine) { LineMeasurement line = _arrangedLines[_actualLastVisibleLineIndex + 1]; spaceLeft -= line.TotalExtendsInNonOrientationDirection; if (spaceLeft + DELTA_DOUBLE < 0) { break; // Found item which is not visible any more } _actualLastVisibleLineIndex++; } } } else { CalcHelper.Bound(ref _actualFirstVisibleLineIndex, 0, _arrangedLines.Count - 1); _actualLastVisibleLineIndex = _actualFirstVisibleLineIndex - 1; while (_actualLastVisibleLineIndex < _arrangedLines.Count - 1) { LineMeasurement line = _arrangedLines[_actualLastVisibleLineIndex + 1]; spaceLeft -= line.TotalExtendsInNonOrientationDirection; if (spaceLeft + DELTA_DOUBLE < 0) { break; } _actualLastVisibleLineIndex++; } if (spaceLeft > 0) { // Correct the first scroll index to fill the available space while (_actualFirstVisibleLineIndex > 0) { LineMeasurement line = _arrangedLines[_actualFirstVisibleLineIndex - 1]; spaceLeft -= line.TotalExtendsInNonOrientationDirection; if (spaceLeft + DELTA_DOUBLE < 0) { break; // Found item which is not visible any more } _actualFirstVisibleLineIndex--; } } } } else { _actualFirstVisibleLineIndex = 0; _actualLastVisibleLineIndex = _arrangedLines.Count - 1; } // 2) Calculate start position for (int i = 0; i < _actualFirstVisibleLineIndex; i++) { LineMeasurement line = _arrangedLines[i]; startPosition -= line.TotalExtendsInNonOrientationDirection; } // 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) { LayoutLine(visibleChildren, position, line); startPosition += line.TotalExtendsInNonOrientationDirection; if (Orientation == Orientation.Vertical) { position = new PointF(actualPosition.X + startPosition, actualPosition.Y); _totalWidth += line.TotalExtendsInNonOrientationDirection; } else { position = new PointF(actualPosition.X, actualPosition.Y + startPosition); _totalHeight += line.TotalExtendsInNonOrientationDirection; } } } else { _actualFirstVisibleLineIndex = 0; _actualLastVisibleLineIndex = -1; } if (fireScrolled) { InvokeScrolled(); } }
protected LineMeasurement CalculateLine(IList <FrameworkElement> children, int startIndex, SizeF?measureSize, bool invertLayoutDirection) { LineMeasurement result = LineMeasurement.Create(); if (invertLayoutDirection) { result.EndIndex = startIndex; } else { result.StartIndex = startIndex; } result.TotalExtendsInNonOrientationDirection = 0; int numChildren = children.Count; int directionOffset = invertLayoutDirection ? -1 : 1; float offsetInOrientationDirection = 0; float extendsInOrientationDirection = measureSize.HasValue ? GetExtendsInOrientationDirection(Orientation, measureSize.Value) : float.NaN; int currentIndex = startIndex; for (; invertLayoutDirection ? (currentIndex >= 0) : (currentIndex < numChildren); currentIndex += directionOffset) { FrameworkElement child = children[currentIndex]; SizeF desiredChildSize; if (measureSize.HasValue) { desiredChildSize = measureSize.Value; child.Measure(ref desiredChildSize); } else { desiredChildSize = child.DesiredSize; } float lastOffsetInOrientationDirection = offsetInOrientationDirection; offsetInOrientationDirection += GetExtendsInOrientationDirection(Orientation, desiredChildSize); if (!float.IsNaN(extendsInOrientationDirection) && offsetInOrientationDirection > extendsInOrientationDirection) { if (invertLayoutDirection) { result.StartIndex = currentIndex + 1; } else { result.EndIndex = currentIndex - 1; } result.TotalExtendsInOrientationDirection = lastOffsetInOrientationDirection; return(result); } if (desiredChildSize.Height > result.TotalExtendsInNonOrientationDirection) { result.TotalExtendsInNonOrientationDirection = GetExtendsInNonOrientationDirection(Orientation, desiredChildSize); } } if (invertLayoutDirection) { result.StartIndex = currentIndex + 1; } else { result.EndIndex = currentIndex - 1; } result.TotalExtendsInOrientationDirection = offsetInOrientationDirection; return(result); }
public override bool FocusPageDown() { IItemProvider itemProvider = ItemProvider; if (itemProvider == null) { return(base.FocusPageDown()); } if (Orientation == Orientation.Horizontal) { FrameworkElement currentElement = GetFocusedElementOrChild(); if (currentElement == null) { return(false); } if (itemProvider.NumItems == 0) { return(false); } IList <LineMeasurement> lines = new List <LineMeasurement>(_arrangedLines); if (lines.Count == 0) { return(false); } int lastVisibleLineIndex = _actualLastVisibleLineIndex; CalcHelper.Bound(ref lastVisibleLineIndex, 0, lines.Count - 1); LineMeasurement lastVisibleLine = lines[lastVisibleLineIndex]; float limitPosition = ActualPosition.Y + (float)ActualHeight; // Initialize as if an element inside our visible range is focused - then, we must move to the last element for (int childIndex = lastVisibleLine.StartIndex; childIndex <= lastVisibleLine.EndIndex; childIndex++) { FrameworkElement child = _arrangedItems[childIndex]; if (!InVisualPath(child, currentElement)) { continue; } // One of the elements at the bottom is focused - move one page down // so the current last visible line is then the first visible line // the new line and item to select might not yet have been arranged // calculate the position by assuming all lines have equal amount of items // go as many lines down as fill one page (minus 1) int numLinesToGoDown = (int)Math.Floor(ActualHeight / lastVisibleLine.TotalExtendsInNonOrientationDirection) - 1; int highestPossibleLineIndex = itemProvider.NumItems / (lastVisibleLine.EndIndex - lastVisibleLine.StartIndex + 1); if (lastVisibleLineIndex + numLinesToGoDown > highestPossibleLineIndex) { numLinesToGoDown = highestPossibleLineIndex - lastVisibleLineIndex; } int lineToScrollTo = lastVisibleLineIndex + numLinesToGoDown; SetScrollIndex(lineToScrollTo, false); // try to select a child at same horizontal position int childIndexToSelect = childIndex + ((lastVisibleLine.EndIndex - lastVisibleLine.StartIndex + 1) * numLinesToGoDown); if (childIndexToSelect > itemProvider.NumItems - 1) { childIndexToSelect = itemProvider.NumItems - 1; } FrameworkElement item = GetItem(childIndexToSelect, itemProvider, false); if (item != null) { item.SetFocusPrio = SetFocusPriority.Default; } return(true); } // select item on same page in last visible line at same horizontal position int lastMinusOne = lastVisibleLineIndex - 1; CalcHelper.Bound(ref lastMinusOne, 0, lines.Count - 1); FrameworkElement nextElement; while ((nextElement = FindNextFocusElement(lines.Skip(lastMinusOne).SelectMany( line => _arrangedItems.Skip(line.StartIndex).Take(line.EndIndex - line.StartIndex + 1)), currentElement.ActualBounds, MoveFocusDirection.Down)) != null && (nextElement.ActualBounds.Bottom < limitPosition + DELTA_DOUBLE)) { currentElement = nextElement; } return(currentElement.TrySetFocus(true)); } return(false); }
public override bool FocusPageUp() { IItemProvider itemProvider = ItemProvider; if (itemProvider == null) { return(base.FocusPageUp()); } if (Orientation == Orientation.Horizontal) { FrameworkElement currentElement = GetFocusedElementOrChild(); if (currentElement == null) { return(false); } if (itemProvider.NumItems == 0) { return(false); } IList <LineMeasurement> lines = new List <LineMeasurement>(_arrangedLines); if (lines.Count == 0) { return(false); } int firstVisibleLineIndex = _actualFirstVisibleLineIndex; LineMeasurement firstVisibleLine = lines[firstVisibleLineIndex]; float limitPosition = ActualPosition.Y; // Initialize as if an element inside our visible range is focused - then, we must move to the first element for (int childIndex = firstVisibleLine.StartIndex; childIndex <= firstVisibleLine.EndIndex; childIndex++) { FrameworkElement child = _arrangedItems[childIndex]; if (!InVisualPath(child, currentElement)) { continue; } // One of the topmost elements is focused - move one page up // a) how many lines to go up? int numLinesToGoUp = (int)Math.Floor(ActualHeight / firstVisibleLine.TotalExtendsInNonOrientationDirection) - 1; if (numLinesToGoUp < 1) { numLinesToGoUp = 1; } // b) what child to select? int childIndexToSelect = childIndex - ((firstVisibleLine.EndIndex - firstVisibleLine.StartIndex + 1) * numLinesToGoUp); if (childIndexToSelect < 0) { childIndexToSelect = 0; } // c) line index to scroll to int lineToScrollTo = firstVisibleLineIndex - numLinesToGoUp; if (lineToScrollTo < 0) { lineToScrollTo = 0; } SetScrollIndex(lineToScrollTo, true); FrameworkElement item = GetItem(childIndexToSelect, itemProvider, false); if (item != null) { item.SetFocusPrio = SetFocusPriority.Default; } return(true); } FrameworkElement nextElement; while ((nextElement = FindNextFocusElement(lines.Skip(firstVisibleLineIndex).Take(1).SelectMany( line => _arrangedItems.Skip(line.StartIndex).Take(line.EndIndex - line.StartIndex + 1)), currentElement.ActualBounds, MoveFocusDirection.Up)) != null && (nextElement.ActualPosition.Y > limitPosition - DELTA_DOUBLE)) { currentElement = nextElement; } return(currentElement.TrySetFocus(true)); } return(false); }
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(); } }