private int GetAnchorIndex( Size availableSize, bool isWrapping, double minItemSpacing, string layoutId) { int anchorIndex = -1; Point anchorPosition = default; if (!IsVirtualizingContext) { // Non virtualizing host, start generating from the element 0 anchorIndex = ((IVirtualizingLayoutContextOverrides)m_context).ItemCountCore() > 0 ? 0 : -1; } else { bool isRealizationWindowConnected = m_elementManager.IsWindowConnected(RealizationRect, OM.ScrollOrientation, m_scrollOrientationSameAsFlow); // Item spacing and size in non-virtualizing direction change can cause elements to reflow // and get a new column position. In that case we need the anchor to be positioned in the // correct column. bool needAnchorColumnRevaluation = isWrapping && ( OM.Minor(m_lastAvailableSize) != OM.Minor(availableSize) || m_lastItemSpacing != minItemSpacing || m_collectionChangePending); var suggestedAnchorIndex = m_context.RecommendedAnchorIndex; bool isAnchorSuggestionValid = suggestedAnchorIndex >= 0 && m_elementManager.IsDataIndexRealized(suggestedAnchorIndex); if (isAnchorSuggestionValid) { anchorIndex = m_algorithmCallbacks.Algorithm_GetAnchorForTargetElement( suggestedAnchorIndex, availableSize, m_context).Index; if (m_elementManager.IsDataIndexRealized(anchorIndex)) { var anchorBounds = m_elementManager.GetLayoutBoundsForDataIndex(anchorIndex); if (needAnchorColumnRevaluation) { // We were provided a valid anchor, but its position might be incorrect because for example it is in // the wrong column. We do know that the anchor is the first element in the row, so we can force the minor position // to start at 0. anchorPosition = OM.MinorMajorPoint(0, OM.MajorStart(anchorBounds)); } else { anchorPosition = new Point(anchorBounds.X, anchorBounds.Y); } } else { // It is possible to end up in a situation during a collection change where GetAnchorForTargetElement returns an index // which is not in the realized range. Eg. insert one item at index 0 for a grid layout. // SuggestedAnchor will be 1 (used to be 0) and GetAnchorForTargetElement will return 0 (left most item in row). However 0 is not in the // realized range yet. In this case we realize the gap between the target anchor and the suggested anchor. int firstRealizedDataIndex = m_elementManager.GetDataIndexFromRealizedRangeIndex(0); Debug.Assert(anchorIndex < firstRealizedDataIndex); for (int i = firstRealizedDataIndex - 1; i >= anchorIndex; --i) { m_elementManager.EnsureElementRealized(false /*forward*/, i, layoutId); } var anchorBounds = m_elementManager.GetLayoutBoundsForDataIndex(suggestedAnchorIndex); anchorPosition = OM.MinorMajorPoint(0, OM.MajorStart(anchorBounds)); } } else if (needAnchorColumnRevaluation || !isRealizationWindowConnected) { // The anchor is based on the realization window because a connected ItemsRepeater might intersect the realization window // but not the visible window. In that situation, we still need to produce a valid anchor. var anchorInfo = m_algorithmCallbacks.Algorithm_GetAnchorForRealizationRect(availableSize, m_context); anchorIndex = anchorInfo.Index; anchorPosition = OM.MinorMajorPoint(0, anchorInfo.Offset); } else { // No suggestion - just pick first in realized range anchorIndex = m_elementManager.GetDataIndexFromRealizedRangeIndex(0); var firstElementBounds = m_elementManager.GetLayoutBoundsForRealizedIndex(0); anchorPosition = new Point(firstElementBounds.X, firstElementBounds.Y); } } Debug.Assert(anchorIndex == -1 || m_elementManager.IsIndexValidInData(anchorIndex)); m_firstRealizedDataIndexInsideRealizationWindow = m_lastRealizedDataIndexInsideRealizationWindow = anchorIndex; if (m_elementManager.IsIndexValidInData(anchorIndex)) { if (!m_elementManager.IsDataIndexRealized(anchorIndex)) { // Disconnected, throw everything and create new anchor m_elementManager.ClearRealizedRange(); var anchor = m_context.GetOrCreateElementAt(anchorIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); m_elementManager.Add(anchor, anchorIndex); } var anchorElement = m_elementManager.GetRealizedElement(anchorIndex); var desiredSize = MeasureElement(anchorElement, anchorIndex, availableSize, m_context); var layoutBounds = new Rect(anchorPosition.X, anchorPosition.Y, desiredSize.Width, desiredSize.Height); m_elementManager.SetLayoutBoundsForDataIndex(anchorIndex, layoutBounds); } else { // Throw everything away m_elementManager.ClearRealizedRange(); } // TODO: Perhaps we can track changes in the property setter m_lastAvailableSize = availableSize; m_lastItemSpacing = minItemSpacing; return(anchorIndex); }