// Figure out the extent of the layout by getting the number of items remaining // above and below the realized elements and getting an estimation based on // average item heights seen so far. private Rect EstimateExtent(VirtualizingLayoutContext context, Size availableSize) { double averageHeight = m_totalHeightForEstimation / m_numItemsUsedForEstimation; Rect extent = new Rect(0, 0, availableSize.Width, context.ItemCount * averageHeight); if (context.ItemCount > 0 && m_realizedElementBounds.Count > 0) { extent.Y = m_firstRealizedDataIndex == 0 ? m_realizedElementBounds[0].Y : m_realizedElementBounds[0].Y - (m_firstRealizedDataIndex - 1) * averageHeight; int lastRealizedIndex = m_firstRealizedDataIndex + m_realizedElementBounds.Count; if (lastRealizedIndex == context.ItemCount - 1) { var lastBounds = m_realizedElementBounds[m_realizedElementBounds.Count - 1]; extent.Y = lastBounds.Bottom; } else { var lastBounds = m_realizedElementBounds[m_realizedElementBounds.Count - 1]; int lastRealizedDataIndex = m_firstRealizedDataIndex + m_realizedElementBounds.Count; int numItemsAfterLastRealizedIndex = context.ItemCount - lastRealizedDataIndex; extent.Height = lastBounds.Bottom + numItemsAfterLastRealizedIndex * averageHeight - extent.Y; } } DebugTrace("Extent " + extent + " with average height " + averageHeight); return(extent); }
protected override Rect GetExtent( Size availableSize, VirtualizingLayoutContext context, UIElement firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, UIElement lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds) { var extent = base.GetExtent( availableSize, context, firstRealized, firstRealizedItemIndex, firstRealizedLayoutBounds, lastRealized, lastRealizedItemIndex, lastRealizedLayoutBounds); return(GetExtentFunc != null?GetExtentFunc( availableSize, context, firstRealized, firstRealizedItemIndex, firstRealizedLayoutBounds, lastRealized, lastRealizedItemIndex, lastRealizedLayoutBounds, extent) : extent); }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { var realizationRect = context.RealizationRect; int itemCount = context.ItemCount; int firstRealizedIndex = FirstRealizedIndexInRect(realizationRect, itemCount); int lastRealizedIndex = LastRealizedIndexInRect(realizationRect, itemCount); Debug.WriteLine("Measure:" + realizationRect.ToString()); // Viewport + Buffer Rect. for (int currentIndex = firstRealizedIndex; currentIndex <= lastRealizedIndex; currentIndex++) { var element = context.GetOrCreateElementAt(currentIndex); element.Measure(new Size(ItemWidth, ItemHeight)); } // Anchor var anchorIndex = context.RecommendedAnchorIndex; if (anchorIndex >= 0) { var anchorElement = context.GetOrCreateElementAt(anchorIndex); anchorElement.Measure(new Size(ItemWidth, ItemHeight)); } return(new Size(ItemWidth, context.ItemCount * ItemHeight)); }
protected override void UninitializeForContextCore(VirtualizingLayoutContext context) { base.UninitializeForContextCore(context); // clear any state context.LayoutState = null; }
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) { var realizationRect = context.RealizationRect; int itemCount = context.ItemCount; int firstRealizedIndex = FirstRealizedIndexInRect(realizationRect, itemCount); int lastRealizedIndex = LastRealizedIndexInRect(realizationRect, itemCount); Debug.WriteLine("Arrange:" + realizationRect.ToString()); // Viewport + Buffer Rect. for (int currentIndex = firstRealizedIndex; currentIndex <= lastRealizedIndex; currentIndex++) { var element = context.GetOrCreateElementAt(currentIndex); var arrangeRect = new Rect(0, currentIndex * ItemHeight, ItemWidth, ItemHeight); element.Arrange(arrangeRect); Debug.WriteLine(" Arrange:" + currentIndex + " :" + arrangeRect); } // Anchor var anchorIndex = context.RecommendedAnchorIndex; if (anchorIndex >= 0) { var anchor = context.GetOrCreateElementAt(anchorIndex); var arrangeRect = new Rect(0, anchorIndex * ItemHeight, ItemWidth, ItemHeight); anchor.Arrange(arrangeRect); Debug.WriteLine(" Arrange:" + anchorIndex + " :" + arrangeRect); } return(finalSize); }
// The data collection has changed, since we are maintaining the bounds of elements // in the viewport, we will update the list to account for the collection change. protected override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args) { InvalidateMeasure(); if (m_realizedElementBounds.Count > 0) { switch (args.Action) { case NotifyCollectionChangedAction.Add: OnItemsAdded(args.NewStartingIndex, args.NewItems.Count); break; case NotifyCollectionChangedAction.Replace: OnItemsRemoved(args.OldStartingIndex, args.OldItems.Count); OnItemsAdded(args.NewStartingIndex, args.NewItems.Count); break; case NotifyCollectionChangedAction.Remove: OnItemsRemoved(args.OldStartingIndex, args.OldItems.Count); break; case NotifyCollectionChangedAction.Reset: m_realizedElementBounds.Clear(); m_firstRealizedDataIndex = 0; break; default: throw new NotImplementedException(); } } }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { var viewport = context.RealizationRect; if (availableSize.Width != m_lastAvailableWidth || cachedBoundsInvalid) { UpdateCachedBounds(availableSize); m_lastAvailableWidth = availableSize.Width; } // Initialize column offsets int numColumns = (int)(availableSize.Width / Width); if (m_columnOffsets.Count == 0) { for (int i = 0; i < numColumns; i++) { m_columnOffsets.Add(0); } } m_firstIndex = GetStartIndex(viewport); int currentIndex = m_firstIndex; double nextOffset = -1.0; // Measure items from start index to when we hit the end of the viewport. while (currentIndex < context.ItemCount && nextOffset < viewport.Bottom) { var child = context.GetOrCreateElementAt(currentIndex); child.Measure(new Size(Width, availableSize.Height)); if (currentIndex >= m_cachedBounds.Count) { // We do not have bounds for this index. Lay it out and cache it. int columnIndex = GetIndexOfLowestColumn(m_columnOffsets, out nextOffset); m_cachedBounds.Add(new Rect(columnIndex * Width, nextOffset, Width, child.DesiredSize.Height)); m_columnOffsets[columnIndex] += child.DesiredSize.Height; } else { if (currentIndex + 1 == m_cachedBounds.Count) { // Last element. Use the next offset. GetIndexOfLowestColumn(m_columnOffsets, out nextOffset); } else { nextOffset = m_cachedBounds[currentIndex + 1].Top; } } m_lastIndex = currentIndex; currentIndex++; } var extent = GetExtentSize(availableSize); return(extent); }
protected override void UninitializeForContextCore(VirtualizingLayoutContext context) { base.UninitializeForContextCore(context); if (OnDetatchedFunc != null) { OnDetatchedFunc(context); } }
protected override void InitializeForContextCore(VirtualizingLayoutContext context) { base.InitializeForContextCore(context); if (OnAttachedFunc != null) { OnAttachedFunc(context); } }
protected override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args) { // The data collection has changed, so the bounds of all the indices are not valid anymore. // We need to re-evaluate all the bounds and cache them during the next measure. m_cachedBounds.Clear(); m_firstIndex = m_lastIndex = 0; cachedBoundsInvalid = true; InvalidateMeasure(); }
protected override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args) { // データ収集が変更されたため、すべてのインデックスの境界が無効になりました。 // すべての境界を再評価し、次のメジャーでキャッシュする必要があります。. this.m_cachedBounds.Clear(); this.m_firstIndex = this.m_lastIndex = 0; this.cachedBoundsInvalid = false; this.InvalidateMeasure(); }
// Figure out which index to use as the anchor and start laying out around. private int GetStartIndex(VirtualizingLayoutContext context, Size availableSize) { int startDataIndex = -1; var anchorIndex = context.RecommendedAnchorIndex; bool isAnchorValid = anchorIndex != -1; if (isAnchorValid) { if (IsRealized(anchorIndex)) { startDataIndex = anchorIndex; } else { ClearRealizedRange(); startDataIndex = anchorIndex; } } else { // find first realized element that is visible in the viewport. startDataIndex = GetFirstRealizedDataIndexInViewport(context.RealizationRect); if (startDataIndex < 0) { startDataIndex = EstimateIndexForViewport(context.RealizationRect, context.ItemCount); ClearRealizedRange(); } } // We have an anchorIndex, realize and measure it and // figure out its bounds. if (startDataIndex != -1 & context.ItemCount > 0) { if (m_realizedElementBounds.Count == 0) { m_firstRealizedDataIndex = startDataIndex; } var newAnchor = EnsureRealized(startDataIndex); DebugTrace("Measuring start index " + startDataIndex); var desiredSize = MeasureElement(context, startDataIndex, availableSize); var bounds = new Rect( 0, newAnchor ? (m_totalHeightForEstimation / m_numItemsUsedForEstimation) * startDataIndex : GetCachedBoundsForDataIndex(startDataIndex).Y, availableSize.Width, desiredSize.Height); SetCachedBoundsForDataIndex(startDataIndex, bounds); } return(startDataIndex); }
protected override void InitializeForContextCore(VirtualizingLayoutContext context) { base.InitializeForContextCore(context); if (!(context.LayoutState is ActivityFeedLayoutState state)) { // 理論的には)レイアウトは複数の要素で同時に使用される可能性があるので、必要な状態を保存します。 // 実際には、Xbox Activity Feed の場合は、おそらく単一のインスタンスしかありません。 context.LayoutState = new ActivityFeedLayoutState(); } }
protected override void InitializeForContextCore(VirtualizingLayoutContext context) { base.InitializeForContextCore(context); if (!(context.LayoutState is ActivityFeedLayoutState state)) { // Store any state we might need since (in theory) the layout could be in use by multiple // elements simultaneously // In reality for the Xbox Activity Feed there's probably only a single instance. context.LayoutState = new ActivityFeedLayoutState(); } }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { var itemCount = context.ItemCount; for (int i = 0; i < context.ItemCount; i++) { var container = context.GetOrCreateElementAt(i); container.Measure(new Size(ItemSize, ItemSize)); } return(availableSize); }
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) { if (this.m_cachedBounds.Count > 0) { for (int index = this.m_firstIndex; index <= this.m_lastIndex; index++) { var child = context.GetOrCreateElementAt(index); child.Arrange(this.m_cachedBounds[index]); } } return(finalSize); }
private void UnifyRowWidth(VirtualizingLayoutContext context, Size availableSize, double unifedRowWidthLimit) { if (this._rowCachedBounds.Count > 0) { double previousWidthDiff = 0; var biggestXOffSet = this._rowXOffSet.Max(); for (int row = 0; row < this._rowCachedBounds.Count; row++) { var rowItems = this._rowCachedBounds[row]; var currentXOffSet = 0d; var nextRowIndex = row + 1; var xOffsettDif = 0d; var currentMaxChildHeight = rowItems.Count > 0 ? rowItems.Max(x => x.Height) : this._rowHeight[row]; var rowBaseHeight = this._rowHeight[row]; if (this._rowXOffSet[row] < biggestXOffSet) { var diff = biggestXOffSet - this._rowXOffSet[row]; xOffsettDif = diff / rowItems.Count; } // Childs werden neu berechnet Höhe , Breite for (int index = 0; index < rowItems.Count; index++) { var oldRectangle = rowItems[index]; var childWidth = oldRectangle.Width; var currentDesiredWidth = childWidth + xOffsettDif; var childIndex = this.FindIndexOfRowChild(row, index); var child = context.GetOrCreateElementAt(childIndex); child.Measure(new Size(currentDesiredWidth, availableSize.Height)); var childMaxWidth = (double)child.GetValue(FrameworkElement.MaxWidthProperty); if (currentDesiredWidth <= childMaxWidth) { oldRectangle.Width = currentDesiredWidth; oldRectangle.X = currentXOffSet; rowItems[index] = oldRectangle; } currentXOffSet += currentDesiredWidth + this.ColumnSpacing; } this._rowXOffSet[row] = currentXOffSet; } } }
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) { // walk through the cache of containers and arrange var state = context.LayoutState as ActivityFeedLayoutState; var virtualContext = context as VirtualizingLayoutContext; int currentIndex = state.FirstRealizedIndex; foreach (var arrangeRect in state.LayoutRects) { var container = virtualContext.GetOrCreateElementAt(currentIndex); container.Arrange(arrangeRect); currentIndex++; } return(finalSize); }
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) { var itemCount = context.ItemCount; var radius = GetCircleRadius(finalSize) - ItemSize / 2.0; var angleIncrement = 2 * Math.PI / itemCount; var angle = 0.0; for (int i = 0; i < context.ItemCount; i++) { var container = context.GetOrCreateElementAt(i); var x = Math.Sin(angle) * radius - ItemSize / 2.0 + finalSize.Width / 2.0; var y = -Math.Cos(angle) * radius - ItemSize / 2.0 + finalSize.Height / 2.0; container.Arrange(new Rect(x, y, ItemSize, ItemSize)); angle += angleIncrement; } return(finalSize); }
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) { DebugTrace("ArrangeOverride: Viewport" + context.RealizationRect); for (int realizationIndex = 0; realizationIndex < m_realizedElementBounds.Count; realizationIndex++) { int currentDataIndex = m_firstRealizedDataIndex + realizationIndex; DebugTrace("Arranging " + currentDataIndex); // Arrange the child. If any alignment needs to be done, it // can be done here. var child = context.GetOrCreateElementAt(currentDataIndex); var arrangeBounds = m_realizedElementBounds[realizationIndex]; arrangeBounds.X -= m_lastExtent.X; arrangeBounds.Y -= m_lastExtent.Y; child.Arrange(arrangeBounds); } return(finalSize); }
private Size MeasureElement(VirtualizingLayoutContext context, int index, Size availableSize) { var child = context.GetOrCreateElementAt(index); child.Measure(availableSize); int estimationBufferIndex = index % m_estimationBuffer.Count; bool alreadyMeasured = m_estimationBuffer[estimationBufferIndex] != 0; if (!alreadyMeasured) { m_numItemsUsedForEstimation++; } m_totalHeightForEstimation -= m_estimationBuffer[estimationBufferIndex]; m_totalHeightForEstimation += child.DesiredSize.Height; m_estimationBuffer[estimationBufferIndex] = child.DesiredSize.Height; return(child.DesiredSize); }
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) { if (this._rowCachedBounds.Count > 0) { for (int row = this.m_firstIndex; row <= this._rowCachedBounds.Count - 1; row++) { var rowItems = this._rowCachedBounds[row]; for (int index = 0; index < rowItems.Count; index++) { var childIndex = this.FindIndexOfRowChild(row, index); var child = context.GetOrCreateElementAt(childIndex); var rect = rowItems[index]; child.Arrange(rect); } } } return(finalSize); }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { // In the Xbox scenario there isn't a user grabbing the window and resizing it. Otherwise, it // might be useful to skip a full layout here and return a cached size until some condition is met // such as having enough room to fit another column. That would require extra work like // tracking some additional state, comparing the virtualizing rect with the cached value. var virtualizingContext = context as VirtualizingLayoutContext; var realizationRect = virtualizingContext.RealizationRect; // Determine which rows need to be realized var firstRowIndex = Math.Max((int)(realizationRect.Y / (this.MinItemSize.Height + this.RowSpacing)) - 1, 0); var lastRowIndex = Math.Min((int)(realizationRect.Bottom / (this.MinItemSize.Height + this.RowSpacing)) + 1, (int)(context.ItemCount / 3)); // Determine which items fall on those rows and determine the rect for each item var state = context.LayoutState as ActivityFeedLayoutState; state.LayoutRects.Clear(); state.FirstRealizedIndex = firstRowIndex * 3; for (int rowIndex = firstRowIndex; rowIndex < lastRowIndex; rowIndex++) { int firstItemIndex = rowIndex * 3; var boundsForCurrentRow = CalculateLayoutBoundsForRow(rowIndex); for (int columnIndex = 0; columnIndex < 3; columnIndex++) { var index = firstItemIndex + columnIndex; var rect = boundsForCurrentRow[index % 3]; var container = virtualizingContext.GetOrCreateElementAt(index); container.Measure(new Size(boundsForCurrentRow[columnIndex].Width, boundsForCurrentRow[columnIndex].Height)); state.LayoutRects.Add(boundsForCurrentRow[columnIndex]); } } // estimate the extent by finding the last item and getting its bottom/right position var extentHeight = ((int)(context.ItemCount / 3) - 1) * (this.MinItemSize.Height + this.RowSpacing) + this.MinItemSize.Height; // Report a desired size for the layout return(new Size(this.MinItemSize.Width * 4 + this.ItemSpacing * 2, extentHeight)); }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { var viewport = context.RealizationRect; DebugTrace("MeasureOverride: Viewport " + viewport); // Remove bounds for elements that are now outside the viewport. // Proactive recycling elements means we can reuse it during this measure pass again. RemoveCachedBoundsOutsideViewport(viewport); // Find the index of the element to start laying out from - the anchor int startIndex = GetStartIndex(context, availableSize); // Measure and layout elements starting from the start index, forward and backward. Generate(context, availableSize, startIndex, forward: true); Generate(context, availableSize, startIndex, forward: false); // Estimate the extent size. Note that this can have a non 0 origin. m_lastExtent = EstimateExtent(context, availableSize); context.LayoutOrigin = new Point(m_lastExtent.X, m_lastExtent.Y); return(new Size(m_lastExtent.Width, m_lastExtent.Height)); }
protected override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args) { // The data collection has changed, so the bounds of all the indices are not valid anymore. // We need to re-evaluate all the bounds and cache them during the next measure. switch (args.Action) { case NotifyCollectionChangedAction.Replace: this.cachedBoundsInvalid = true; this.InvalidateArrange(); break; default: this._rowCachedBounds.Clear(); this._rowHeight.Clear(); this._rowXOffSet.Clear(); this.m_cachedBounds.Clear(); this.m_firstIndex = this.m_lastIndex = 0; this.cachedBoundsInvalid = true; this.InvalidateMeasure(); break; } }
private void Generate(VirtualizingLayoutContext context, Size availableSize, int anchorDataIndex, bool forward) { // Generate forward or backward from anchorIndex until we hit the end of the viewport int step = forward ? 1 : -1; int previousDataIndex = anchorDataIndex; int currentDataIndex = previousDataIndex + step; var viewport = context.RealizationRect; while (IsDataIndexValid(currentDataIndex, context.ItemCount) && ShouldContinueFillingUpSpace(previousDataIndex, forward, viewport)) { EnsureRealized(currentDataIndex); DebugTrace("Measuring " + currentDataIndex); var desiredSize = MeasureElement(context, currentDataIndex, availableSize); var previousBounds = GetCachedBoundsForDataIndex(previousDataIndex); Rect currentBounds = new Rect(0, forward ? previousBounds.Y + previousBounds.Height : previousBounds.Y - desiredSize.Height, availableSize.Width, desiredSize.Height); SetCachedBoundsForDataIndex(currentDataIndex, currentBounds); previousDataIndex = currentDataIndex; currentDataIndex += step; } }
protected override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) { return(ArrangeLayoutFunc != null?ArrangeLayoutFunc(finalSize, context) : default(Size)); }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { return(MeasureLayoutFunc != null?MeasureLayoutFunc(availableSize, context) : default(Size)); }
protected override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { if (this.MinItemSize == Size.Empty) { var firstElement = context.GetOrCreateElementAt(0); firstElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); // setting the member value directly to skip invalidating layout this._minItemSize = firstElement.DesiredSize; } // Determine which rows need to be realized. We know every row will have the same height and // only contain 3 items. Use that to determine the index for the first and last item that // will be within that realization rect. var firstRowIndex = Math.Max( (int)(context.RealizationRect.Y / (this.MinItemSize.Height + this.RowSpacing)) - 1, 0); var lastRowIndex = Math.Min( (int)(context.RealizationRect.Bottom / (this.MinItemSize.Height + this.RowSpacing)) + 1, (int)(context.ItemCount / 3)); // Determine which items will appear on those rows and what the rect will be for each item var state = context.LayoutState as ActivityFeedLayoutState; state.LayoutRects.Clear(); // Save the index of the first realized item. We'll use it as a starting point during arrange. state.FirstRealizedIndex = firstRowIndex * 3; // ideal item width that will expand/shrink to fill available space double desiredItemWidth = Math.Max(this.MinItemSize.Width, (availableSize.Width - this.ColumnSpacing * 3) / 4); // Foreach item between the first and last index, // Call GetElementOrCreateElementAt which causes an element to either be realized or retrieved // from a recycle pool // Measure the element using an appropriate size // // Any element that was previously realized which we don't retrieve in this pass (via a call to // GetElementOrCreateAt) will be automatically cleared and set aside for later re-use. // Note: While this work fine, it does mean that more elements than are required may be // created because it isn't until after our MeasureOverride completes that the unused elements // will be recycled and available to use. We could avoid this by choosing to track the first/last // index from the previous layout pass. The diff between the previous range and current range // would represent the elements that we can pre-emptively make available for re-use by calling // context.RecycleElement(element). for (int rowIndex = firstRowIndex; rowIndex < lastRowIndex; rowIndex++) { int firstItemIndex = rowIndex * 3; var boundsForCurrentRow = CalculateLayoutBoundsForRow(rowIndex, desiredItemWidth); for (int columnIndex = 0; columnIndex < 3; columnIndex++) { var index = firstItemIndex + columnIndex; var rect = boundsForCurrentRow[index % 3]; var container = context.GetOrCreateElementAt(index); container.Measure( new Size(boundsForCurrentRow[columnIndex].Width, boundsForCurrentRow[columnIndex].Height)); state.LayoutRects.Add(boundsForCurrentRow[columnIndex]); } } // Calculate and return the size of all the content (realized or not) by figuring out // what the bottom/right position of the last item would be. var extentHeight = ((int)(context.ItemCount / 3) - 1) * (this.MinItemSize.Height + this.RowSpacing) + this.MinItemSize.Height; // Report this as the desired size for the layout return(new Size(desiredItemWidth * 4 + this.ColumnSpacing * 2, extentHeight)); }
protected override FlowLayoutAnchorInfo GetAnchorForRealizationRect(Size availableSize, VirtualizingLayoutContext context) { var anchorInfo = base.GetAnchorForRealizationRect(availableSize, context); return(GetAnchorForRealizationRectFunc != null?GetAnchorForRealizationRectFunc(availableSize, context, anchorInfo) : anchorInfo); }