Example #1
0
        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();
            }
        }